├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md └── src ├── SharpDB.Driver ├── Binary │ └── BinaryExtensions.cs ├── BsonSerializer.cs ├── ISerializer.cs ├── Json │ └── JsonExtensions.cs ├── SharpDB.Driver.csproj ├── SharpDBClient.cs ├── SharpDBConnection.cs ├── SharpDBException.cs └── SharpDBTransaction.cs ├── SharpDB.Engine.Specs ├── App.config ├── DatabaseContext.cs ├── Features │ ├── GetUpdateDelete.feature │ └── GetUpdateDelete.feature.cs ├── Hooks.cs ├── Properties │ └── AssemblyInfo.cs ├── SharpDB.Engine.Specs.csproj ├── Steps │ ├── DatabaseGeneralSteps.cs │ └── GetUpdateDeleteSteps.cs └── packages.config ├── SharpDB.Engine.Tests ├── DBTests.cs ├── Properties │ └── AssemblyInfo.cs ├── SharpDB.Engine.Tests.csproj └── packages.config ├── SharpDB.Engine ├── Cache │ ├── ICacheProvider.cs │ └── MemoryCacheProvider.cs ├── DocumentLockedException.cs ├── Domain │ ├── Document.cs │ ├── DocumentKey.cs │ ├── DocumentRevision.cs │ ├── DocumentStore.cs │ └── Transaction.cs ├── IO │ ├── DatabaseFileReader.cs │ ├── DatabaseFileWriter.cs │ ├── IDatabaseReader.cs │ └── IDatabaseWriter.cs ├── KeyValueDatabase.cs ├── SharpDB.Engine.csproj ├── TransactionNotExistException.cs └── Utils │ └── ULongDescendingComparer.cs ├── SharpDB.Example ├── Program.cs └── SharpDB.Example.csproj ├── SharpDB.Server.sln ├── SharpDB.Server ├── Network │ └── Server.cs ├── Program.cs ├── Properties │ └── PublishProfiles │ │ └── FolderProfile.pubxml ├── ServerService.cs └── SharpDB.Server.csproj └── SharpDB.Shared ├── MessageType.cs ├── Protocol.cs └── SharpDB.Shared.csproj /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | If you are making a bug report, fill out the form below. If you are making a suggestion or something other than a bug report, then you may remove this boilerplate text. 2 | 3 | ### Describe your setup. 4 | Have you modified SharpDB in any way? What OS are you running on? What version of SharpDB and/or .NET Core/.NET Standard are you using? 5 | 6 | ### Describe the issue you are experiencing. 7 | Include details such as what you did, what happened, and what you think should have happened. 8 | 9 | ### Describe the steps to reproduce the problem. 10 | Here, you should tell us what we need to know in order to cause the same problem on our machines, so that we may debug it. This section is only needed if you are making a bug report. 11 | 12 | ### Anything else we need to know? 13 | 14 | If there is anything else that you think will help us solve the problem, please tell us here. 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | #ignore thumbnails created by windows 3 | Thumbs.db 4 | #Ignore files build by Visual Studio 5 | *.obj 6 | *.exe 7 | *.pdb 8 | *.user 9 | *.aps 10 | *.pch 11 | *.vspscc 12 | *_i.c 13 | *_p.c 14 | *.ncb 15 | *.suo 16 | *.tlb 17 | *.tlh 18 | *.bak 19 | *.cache 20 | *.ilk 21 | *.log 22 | *.orig 23 | [Bb]in 24 | [Dd]ebug*/ 25 | *.lib 26 | *.sbr 27 | obj/ 28 | [Rr]elease*/ 29 | _ReSharper*/ 30 | [Tt]est[Rr]esult* 31 | /src/.vs 32 | /src/packages 33 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | First of all, thank you for wanting to contribute to SharpDB! As an open-source project, we encourage the community to pitch in and help wherever they can; bug reports, pull requests and more! However, we're just going to leave a few guidelines here to make sure everything goes smoothly. 3 | 4 | # Bug Reports & issues 5 | Encountered a bug? Well don't sit there and deal with it, report it! We have an issue template that should automatically place itself in the issue description box when you create an issue. Fill that out and hit "Submit Issue". We'll then evaluate the bug and _try_ and replicate it on our machines. 6 | 7 | BUT WAIT! Sometimes, it's not a bug, but maybe a feature request... In which case remove the issue template and describe whatever the issue's about. 8 | 9 | # Pull requests 10 | Making pull requests is even easier! 11 | 1. Fork the repository 12 | 2. Clone your fork 13 | 3. Make changes to your fork 14 | 4. Create a pull request! 15 | 5. We'll then evaluate your changes, and either merge it or request changes to be made. 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © 2018 Ultz Limited & contributors 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SharpDB 2 | ===== 3 | 4 | SharpDB is C# based High Performance Key Value NoSQL Database with ACID Transaction. 5 | SharpDB is using [Multiversion concurrency control](http://en.wikipedia.org/wiki/Multiversion_concurrency_control) to achieve transactions. 6 | Because of using MVCC SharpDB is very fast in doing transaction (the downside is that SharpDB using a lof of space). 7 | 8 | For network protocl SharpDB is using [NetMQ](https://github.com/zeromq/netmq) which is C# port of ZeroMQ, which means SharpDB is using very fast on the network as well. 9 | 10 | ## Installation 11 | 12 | Packages of SharpDB is still not available, so download the code code and compile. 13 | 14 | After compiling run the following command to run the server: 15 | 16 | SharpDB.Server.exe run -name:test -port:5555 17 | 18 | You can change the name and port fields as you like. File called name.sdb will be created as the database file. 19 | 20 | You can also run the database as a service with the following commands (must run as administrator) 21 | 22 | SharpDB.Server.exe install -name:test -port:5555 23 | SharpDB.Server.exe start 24 | 25 | ## Using 26 | 27 | To use SharpDB from your application include the SharpDB.Driver assembly. 28 | Following is small example of using the SharpDB driver: 29 | 30 | [Test] 31 | public void UpdateGet() 32 | { 33 | using (SharpDBClient client = new SharpDBClient("tcp://127.0.0.1:5555")) 34 | { 35 | using (SharpDBConnection connection = client.GetConnection()) 36 | { 37 | Account newAccount = new Account(); 38 | newAccount.Name = "Hello"; 39 | newAccount.Id = 1; 40 | 41 | connection.Update(newAccount); 42 | 43 | Account storedAccount = connection.Get(1); 44 | 45 | Assert.AreEqual("Hello", storedAccount.Name); 46 | } 47 | } 48 | } 49 | 50 | and the account class: 51 | 52 | public class Account 53 | { 54 | public int Id { get; set; } 55 | 56 | public string Name { get; set; } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/SharpDB.Driver/Binary/BinaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Newtonsoft.Json.Linq; 6 | 7 | namespace SharpDB.Driver.Binary 8 | { 9 | public static class BinaryExtensions 10 | { 11 | public static byte[] GetBinary(this SharpDBConnection connection, byte[] documentIdBytes) 12 | { 13 | return connection.GetInternal(documentIdBytes); 14 | } 15 | 16 | public static void UpdateBinary(this SharpDBConnection connection, byte[] documentIdBytes, byte[] blob) 17 | { 18 | connection.UpdateInternal(documentIdBytes, blob); 19 | } 20 | 21 | public static void DeleteBinary(this SharpDBConnection connection, byte[] documentIdBytes) 22 | { 23 | connection.DeleteInternal(documentIdBytes); 24 | } 25 | 26 | public static byte[] GetBinary(this SharpDBTransaction transaction, byte[] documentIdBytes) 27 | { 28 | return transaction.Connection.GetInternal(documentIdBytes, transaction); 29 | } 30 | 31 | public static void UpdateBinary(this SharpDBTransaction transaction, byte[] documentIdBytes, byte[] blob) 32 | { 33 | transaction.Connection.UpdateInternal(documentIdBytes, blob, transaction); 34 | } 35 | 36 | public static void DeleteBinary(this SharpDBTransaction transaction, byte[] documentIdBytes) 37 | { 38 | transaction.Connection.DeleteInternal(documentIdBytes, transaction); 39 | } 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/SharpDB.Driver/BsonSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using Newtonsoft.Json; 7 | using Newtonsoft.Json.Bson; 8 | 9 | namespace SharpDB.Driver 10 | { 11 | public class BsonSerializer : ISerializer 12 | { 13 | private JsonSerializer m_serializer; 14 | private MemoryStream m_memoryStream; 15 | private BsonWriter m_bsonWriter; 16 | private BinaryWriter m_binaryWriter; 17 | 18 | public const byte NumberType = 0; 19 | public const byte StringType = 1; 20 | public const byte CustomType = 2; 21 | 22 | public BsonSerializer() 23 | { 24 | m_serializer = new JsonSerializer(); 25 | m_memoryStream = new MemoryStream(1024 * 1024); 26 | m_bsonWriter = new BsonWriter(m_memoryStream); 27 | m_binaryWriter = new BinaryWriter(m_memoryStream, Encoding.Unicode); 28 | } 29 | 30 | public virtual byte[] SerializeDocumentId(object documentId) 31 | { 32 | m_memoryStream.Position = 0; 33 | 34 | if (documentId is int) 35 | { 36 | // serializing the type of the document id in order to avoid case of 4 bytes string equal to int 37 | m_binaryWriter.Write(NumberType); 38 | m_binaryWriter.Write((int)documentId); 39 | } 40 | else if (documentId is long) 41 | { 42 | // serializing the type of the document id in order to avoid case of 8 bytes string equal to long 43 | m_binaryWriter.Write(NumberType); 44 | 45 | long number = (long) documentId; 46 | 47 | // to save space, if the number is less than the size of int we save int instead of long 48 | if (number < int.MaxValue) 49 | { 50 | m_binaryWriter.Write((int)number); 51 | } 52 | else 53 | { 54 | m_binaryWriter.Write(number); 55 | } 56 | } 57 | else if (documentId is string) 58 | { 59 | m_binaryWriter.Write(StringType); 60 | m_binaryWriter.Write((string)documentId); 61 | } 62 | else 63 | { 64 | m_binaryWriter.Write(CustomType); 65 | m_serializer.Serialize(m_bsonWriter, documentId); 66 | } 67 | 68 | byte[] buffer = new byte[m_memoryStream.Position]; 69 | Buffer.BlockCopy(m_memoryStream.GetBuffer(), 0, buffer, 0, (int)m_memoryStream.Position); 70 | 71 | return buffer; 72 | } 73 | 74 | public virtual byte[] SerializeDocument(T document) 75 | { 76 | m_memoryStream.Position = 0; 77 | m_serializer.Serialize(m_bsonWriter, document); 78 | 79 | byte[] blob = new byte[m_memoryStream.Position]; 80 | Buffer.BlockCopy(m_memoryStream.GetBuffer(), 0, blob, 0, (int)m_memoryStream.Position); 81 | 82 | return blob; 83 | } 84 | 85 | public virtual T DeserializeDocument(byte[] bytes) 86 | { 87 | using (var stream = new MemoryStream(bytes)) 88 | { 89 | using (BsonReader reader = new BsonReader(stream)) 90 | { 91 | return m_serializer.Deserialize(reader); 92 | } 93 | } 94 | } 95 | 96 | public virtual Newtonsoft.Json.Linq.JObject DeserializeToJObject(byte[] bytes) 97 | { 98 | using (var stream = new MemoryStream(bytes)) 99 | { 100 | using (BsonReader reader = new BsonReader(stream)) 101 | { 102 | return Newtonsoft.Json.Linq.JObject.Load(reader); 103 | } 104 | } 105 | } 106 | 107 | public virtual byte[] SerializeFronJObject(Newtonsoft.Json.Linq.JObject jobject) 108 | { 109 | m_memoryStream.Position = 0; 110 | 111 | jobject.WriteTo(m_bsonWriter); 112 | 113 | byte[] blob = new byte[m_memoryStream.Position]; 114 | Buffer.BlockCopy(m_memoryStream.GetBuffer(), 0, blob, 0, (int)m_memoryStream.Position); 115 | 116 | return blob; 117 | } 118 | 119 | public virtual void Dispose() 120 | { 121 | m_memoryStream.Dispose(); 122 | 123 | m_serializer = null; 124 | m_memoryStream = null; 125 | m_bsonWriter = null; 126 | m_binaryWriter = null; 127 | } 128 | 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/SharpDB.Driver/ISerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpDB.Driver 4 | { 5 | public interface ISerializer : IDisposable 6 | { 7 | byte[] SerializeDocumentId(object documentId); 8 | byte[] SerializeDocument(T document); 9 | T DeserializeDocument(byte[] bytes); 10 | } 11 | } -------------------------------------------------------------------------------- /src/SharpDB.Driver/Json/JsonExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Newtonsoft.Json.Linq; 6 | 7 | namespace SharpDB.Driver.Json 8 | { 9 | public static class JsonExtensions 10 | { 11 | public static JObject GetJObject(this SharpDBConnection connection, object documentId) 12 | { 13 | byte[] documentIdBytes = connection.Serializer.SerializeDocumentId(documentId); 14 | 15 | BsonSerializer serializer = connection.Serializer as BsonSerializer; 16 | 17 | byte[] blob = connection.GetInternal(documentIdBytes); 18 | 19 | return serializer.DeserializeToJObject(blob); 20 | } 21 | 22 | public static void UpdateJObject(this SharpDBConnection connection, JObject jobject) 23 | { 24 | JValue idToken = (JValue) jobject["Id"]; 25 | 26 | object documentId = idToken.Value; 27 | 28 | byte[] documentIdBytes = connection.Serializer.SerializeDocumentId(documentId); 29 | 30 | BsonSerializer serializer = connection.Serializer as BsonSerializer; 31 | 32 | byte[] blob = serializer.SerializeFronJObject(jobject); 33 | 34 | connection.UpdateInternal(documentIdBytes, blob); 35 | } 36 | 37 | public static void DeleteJObject(this SharpDBConnection connection, JObject jobject) 38 | { 39 | JValue idToken = (JValue)jobject["Id"]; 40 | 41 | DeleteJObject(connection, idToken); 42 | } 43 | 44 | public static void DeleteJObject(this SharpDBConnection connection, JValue idToken) 45 | { 46 | object documentId = idToken.Value; 47 | 48 | byte[] documentIdBytes = connection.Serializer.SerializeDocumentId(documentId); 49 | 50 | connection.DeleteInternal(documentIdBytes); 51 | } 52 | 53 | public static JObject GetJObject(this SharpDBTransaction transaction, object documentId) 54 | { 55 | byte[] documentIdBytes = transaction.Connection.Serializer.SerializeDocumentId(documentId); 56 | 57 | BsonSerializer serializer = transaction.Connection.Serializer as BsonSerializer; 58 | 59 | byte[] blob = transaction.Connection.GetInternal(documentIdBytes, transaction); 60 | 61 | return serializer.DeserializeToJObject(blob); 62 | } 63 | 64 | public static void UpdateJObject(this SharpDBTransaction transaction, JObject jobject) 65 | { 66 | JValue idToken = (JValue)jobject["Id"]; 67 | 68 | object documentId = idToken.Value; 69 | 70 | byte[] documentIdBytes = transaction.Connection.Serializer.SerializeDocumentId(documentId); 71 | 72 | BsonSerializer serializer = transaction.Connection.Serializer as BsonSerializer; 73 | 74 | byte[] blob = serializer.SerializeFronJObject(jobject); 75 | 76 | transaction.Connection.UpdateInternal(documentIdBytes, blob, transaction); 77 | } 78 | 79 | public static void DeleteJObject(this SharpDBTransaction transaction, JObject jobject) 80 | { 81 | JValue idToken = (JValue)jobject["Id"]; 82 | 83 | DeleteJObject(transaction, idToken); 84 | } 85 | 86 | public static void DeleteJObject(this SharpDBTransaction transaction, JValue idToken) 87 | { 88 | object documentId = idToken.Value; 89 | 90 | byte[] documentIdBytes = transaction.Connection.Serializer.SerializeDocumentId(documentId); 91 | 92 | transaction.Connection.DeleteInternal(documentIdBytes, transaction); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/SharpDB.Driver/SharpDB.Driver.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net40;netstandard2.0 5 | true 6 | https://github.com/Ultz-Limited/SharpDB/blob/master/LICENSE.md 7 | https://github.com/Ultz-Limited/SharpDB 8 | 9 | SharpDB is C# based High Performance Key Value NoSQL Database with ACID Transaction. SharpDB is using Multiversion concurrency control to achieve transactions. Because of using MVCC SharpDB is very fast in doing transaction (the downside is that SharpDB using a lof of space). 10 | For network protocl SharpDB is using NetMQ which is C# port of ZeroMQ, which means SharpDB is using very fast on the network as well. 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/SharpDB.Driver/SharpDBClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Concurrent; 4 | using System.Linq; 5 | using System.Text; 6 | using NetMQ; 7 | 8 | namespace SharpDB.Driver 9 | { 10 | public class SharpDBClient : IDisposable 11 | { 12 | private bool m_isDisposed = false; 13 | 14 | private ConcurrentBag m_connections = new ConcurrentBag(); 15 | 16 | public SharpDBClient(string connectionString) 17 | { 18 | ConnectionString = connectionString; 19 | SerializerFactory = () => new BsonSerializer(); 20 | } 21 | 22 | public Func SerializerFactory { get; set; } 23 | 24 | public string ConnectionString { get; private set; } 25 | 26 | public SharpDBConnection GetConnection() 27 | { 28 | NetMQSocket socket = new NetMQ.Sockets.RequestSocket(); 29 | // socket.Options.CopyMessages = false; 30 | socket.Options.Linger = TimeSpan.FromSeconds(5); 31 | socket.Connect(ConnectionString); 32 | 33 | var connection = new SharpDBConnection(this, socket, SerializerFactory()); 34 | m_connections.Add(connection); 35 | 36 | return connection; 37 | } 38 | 39 | internal void ReleaseConnection(SharpDBConnection connection) 40 | { 41 | connection.Socket.Dispose(); 42 | connection.Serializer.Dispose(); 43 | } 44 | 45 | public void Dispose() 46 | { 47 | lock (this) 48 | { 49 | if (!m_isDisposed) 50 | { 51 | SharpDBConnection connection; 52 | while (m_connections.TryTake(out connection)) 53 | { 54 | connection.Dispose(); 55 | } 56 | m_connections = null; 57 | m_isDisposed = true; 58 | } 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/SharpDB.Driver/SharpDBConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | using System.Reflection.Emit; 8 | using System.Text; 9 | using Common.Logging; 10 | using NetMQ; 11 | using Newtonsoft.Json; 12 | using Newtonsoft.Json.Bson; 13 | using SharpDB.Shared; 14 | 15 | namespace SharpDB.Driver 16 | { 17 | public class SharpDBConnection : IDisposable 18 | { 19 | private bool m_isDisposed; 20 | private SharpDBClient m_client; 21 | internal ILog Log { get; private set; } 22 | 23 | private IList m_transactions = new List(); 24 | 25 | internal SharpDBConnection(SharpDBClient client, NetMQSocket socket, ISerializer serializer) 26 | { 27 | Log = LogManager.GetLogger(this.GetType()); 28 | m_client = client; 29 | Socket = socket; 30 | Serializer = serializer; 31 | m_isDisposed = false; 32 | } 33 | 34 | internal NetMQSocket Socket { get; private set; } 35 | internal ISerializer Serializer { get; private set; } 36 | 37 | public void Dispose() 38 | { 39 | lock (this) 40 | { 41 | if (!m_isDisposed) 42 | { 43 | foreach (SharpDBTransaction transaction in m_transactions) 44 | { 45 | transaction.Rollback(); 46 | } 47 | 48 | m_transactions.Clear(); 49 | 50 | m_client.ReleaseConnection(this); 51 | 52 | Socket = null; 53 | Serializer = null; 54 | m_client = null; 55 | 56 | m_isDisposed = true; 57 | } 58 | } 59 | } 60 | 61 | private object GetDocumentId(T document) 62 | { 63 | ParameterExpression parameter = Expression.Parameter(typeof(object), "documentId"); 64 | 65 | MethodInfo methodInfo = typeof(T).GetProperty("Id").GetGetMethod(); ; 66 | 67 | Expression> getIdExpression = 68 | Expression.Lambda>( 69 | Expression.Convert(Expression.Call(Expression.Convert(parameter, methodInfo.DeclaringType), methodInfo), typeof(object)), new[] { parameter }); 70 | 71 | return getIdExpression.Compile()(document); 72 | } 73 | 74 | #region Internal binary methods 75 | 76 | internal byte[] GetInternal(byte[] documentIdBytes, SharpDBTransaction transaction = null) 77 | { 78 | if (transaction != null) 79 | { 80 | Socket.SendMoreFrame(Protocol.TransactionGet).SendMoreFrame(transaction.TransactionIdBytes); 81 | } 82 | else 83 | { 84 | Socket.SendMoreFrame(Protocol.Get); 85 | } 86 | 87 | Socket.SendFrame(documentIdBytes); 88 | 89 | bool result = BitConverter.ToBoolean(Socket.ReceiveFrameBytes(), 0); 90 | 91 | if (result) 92 | { 93 | return Socket.ReceiveFrameBytes(); 94 | } 95 | else 96 | { 97 | string error = Socket.ReceiveFrameString(); 98 | 99 | throw new SharpDBException(error); 100 | } 101 | } 102 | 103 | internal void UpdateInternal(byte[] documentIdBytes, byte[] blob, SharpDBTransaction transaction = null) 104 | { 105 | if (transaction != null) 106 | { 107 | Socket.SendMoreFrame(Protocol.TransactionUpdate).SendMoreFrame(transaction.TransactionIdBytes); 108 | } 109 | else 110 | { 111 | Socket.SendMoreFrame(Protocol.Update); 112 | } 113 | 114 | Socket.SendMoreFrame(documentIdBytes).SendFrame(blob); 115 | 116 | bool result = BitConverter.ToBoolean(Socket.ReceiveFrameBytes(), 0); 117 | 118 | if (!result) 119 | { 120 | string error = Socket.ReceiveFrameString(); 121 | 122 | throw new SharpDBException(error); 123 | } 124 | } 125 | 126 | public void DeleteInternal(byte[] documentIdBytes, SharpDBTransaction transaction = null) 127 | { 128 | if (transaction != null) 129 | { 130 | Socket.SendMoreFrame(Protocol.TransactionDelete).SendMoreFrame(transaction.TransactionIdBytes); 131 | } 132 | else 133 | { 134 | Socket.SendMoreFrame(Protocol.Delete); 135 | } 136 | 137 | Socket.SendFrame(documentIdBytes); 138 | 139 | bool result = BitConverter.ToBoolean(Socket.ReceiveFrameBytes(), 0); 140 | 141 | if (!result) 142 | { 143 | string error = Socket.ReceiveFrameString(); 144 | 145 | throw new SharpDBException(error); 146 | } 147 | } 148 | 149 | #endregion Internal binary methods 150 | 151 | #region Transactionless methods 152 | 153 | public T Get(object documentId) 154 | { 155 | byte[] documentIdBytes = Serializer.SerializeDocumentId(documentId); 156 | 157 | byte[] blob = GetInternal(documentIdBytes); 158 | 159 | if (blob.Length > 0) 160 | { 161 | return Serializer.DeserializeDocument(blob); 162 | } 163 | else 164 | { 165 | return default(T); 166 | } 167 | } 168 | 169 | public void Update(T document) 170 | { 171 | object documentId = GetDocumentId(document); 172 | 173 | byte[] documentIdBytes = Serializer.SerializeDocumentId(documentId); 174 | 175 | byte[] blob = Serializer.SerializeDocument(document); 176 | 177 | UpdateInternal(documentIdBytes, blob); 178 | } 179 | 180 | public void DeleteDocument(T document) 181 | { 182 | DeleteDocumentById(GetDocumentId(document)); 183 | } 184 | 185 | public void DeleteDocumentById(object documentId) 186 | { 187 | byte[] documentIdBytes = Serializer.SerializeDocumentId(documentId); 188 | 189 | DeleteInternal(documentIdBytes); 190 | } 191 | 192 | #endregion Transactionless methods 193 | 194 | #region Transaction methods 195 | 196 | internal T TransactionGet(SharpDBTransaction transaction, object documentId) 197 | { 198 | byte[] documentIdBytes = Serializer.SerializeDocumentId(documentId); 199 | 200 | byte[] blob = GetInternal(documentIdBytes, transaction); 201 | 202 | if (blob.Length > 0) 203 | { 204 | return Serializer.DeserializeDocument(blob); 205 | } 206 | else 207 | { 208 | return default(T); 209 | } 210 | } 211 | 212 | internal void TransactionUpdate(SharpDBTransaction transaction, T document) 213 | { 214 | object documentId = GetDocumentId(document); 215 | byte[] documentIdBytes = Serializer.SerializeDocumentId(documentId); 216 | 217 | byte[] blob = Serializer.SerializeDocument(document); 218 | 219 | UpdateInternal(documentIdBytes, blob, transaction); 220 | } 221 | 222 | internal void TransactionDeleteDocument(SharpDBTransaction transaction, T document) 223 | { 224 | TransactionDeleteDocumentById(transaction, GetDocumentId(document)); 225 | } 226 | 227 | internal void TransactionDeleteDocumentById(SharpDBTransaction transaction, object documentId) 228 | { 229 | byte[] documentIdBytes = Serializer.SerializeDocumentId(documentId); 230 | 231 | DeleteInternal(documentIdBytes, transaction); 232 | } 233 | 234 | public SharpDBTransaction StartTransaction() 235 | { 236 | Socket.SendMoreFrame(Protocol.StartTransaction); 237 | 238 | bool result = BitConverter.ToBoolean(Socket.ReceiveFrameBytes(), 0); 239 | 240 | if (result) 241 | { 242 | int transactionId = BitConverter.ToInt32(Socket.ReceiveFrameBytes(), 0); 243 | 244 | Log.DebugFormat("Transaction {0} started", transactionId); 245 | 246 | return new SharpDBTransaction(this, transactionId); 247 | } 248 | else 249 | { 250 | string error = Socket.ReceiveFrameString(); 251 | 252 | Log.ErrorFormat("Failed to start transaction, error: {0}", error); 253 | 254 | throw new SharpDBException(Socket.ReceiveFrameString()); 255 | } 256 | } 257 | 258 | internal void CommitTransaction(SharpDBTransaction transaction) 259 | { 260 | Socket.SendMoreFrame(Protocol.Commit).SendFrame(transaction.TransactionIdBytes); 261 | 262 | bool result = BitConverter.ToBoolean(Socket.ReceiveFrameBytes(), 0); 263 | 264 | m_transactions.Remove(transaction); 265 | 266 | if (!result) 267 | { 268 | string error = Socket.ReceiveFrameString(); 269 | 270 | Log.ErrorFormat("Failed to commit transaction {0}, error: {1}", transaction.TransactionId, error); 271 | 272 | throw new SharpDBException(error); 273 | } 274 | else 275 | { 276 | Log.DebugFormat("Transaction {0} committed", transaction.TransactionId); 277 | } 278 | } 279 | 280 | internal void RollbackTransaction(SharpDBTransaction transaction) 281 | { 282 | Socket.SendMoreFrame(Protocol.Rollback).SendFrame(transaction.TransactionIdBytes); 283 | 284 | bool result = BitConverter.ToBoolean(Socket.ReceiveFrameBytes(), 0); 285 | m_transactions.Remove(transaction); 286 | 287 | if (!result) 288 | { 289 | string error = Socket.ReceiveFrameString(); 290 | 291 | Log.ErrorFormat("Failed to rollback transaction {0}, error: {1}", transaction.TransactionId, error); 292 | 293 | throw new SharpDBException(error); 294 | } 295 | else 296 | { 297 | Log.DebugFormat("Transaction {0} rollbacked", transaction.TransactionId); 298 | } 299 | } 300 | 301 | #endregion Transaction methods 302 | } 303 | } -------------------------------------------------------------------------------- /src/SharpDB.Driver/SharpDBException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SharpDB.Driver 7 | { 8 | public class SharpDBException : Exception 9 | { 10 | public SharpDBException(string message) : base(message) 11 | { 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SharpDB.Driver/SharpDBTransaction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SharpDB.Driver 7 | { 8 | public class SharpDBTransaction : IDisposable 9 | { 10 | private bool m_transactionCompletionHandled = false; 11 | 12 | public SharpDBTransaction(SharpDBConnection connection, int transactionId) 13 | { 14 | Connection = connection; 15 | 16 | TransactionId = transactionId; 17 | TransactionIdBytes = BitConverter.GetBytes(transactionId); 18 | } 19 | 20 | internal byte[] TransactionIdBytes { get; private set; } 21 | internal int TransactionId { get; private set; } 22 | public SharpDBConnection Connection { get; private set; } 23 | 24 | public T Get(object documentId) 25 | { 26 | return Connection.TransactionGet(this, documentId); 27 | } 28 | 29 | public void Update(T document) 30 | { 31 | Connection.TransactionUpdate(this, document); 32 | } 33 | 34 | public void DeleteDocument(T document) 35 | { 36 | Connection.TransactionDeleteDocument(this, document); 37 | } 38 | 39 | 40 | public void DeleteDocumentById(object documentId) 41 | { 42 | Connection.TransactionDeleteDocumentById(this, documentId); 43 | } 44 | 45 | public void Commit() 46 | { 47 | m_transactionCompletionHandled = true; 48 | Connection.CommitTransaction(this); 49 | } 50 | 51 | public void Rollback() 52 | { 53 | m_transactionCompletionHandled = true; 54 | Connection.RollbackTransaction(this); 55 | } 56 | 57 | public void Dispose() 58 | { 59 | if (!m_transactionCompletionHandled) 60 | { 61 | Rollback(); 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/SharpDB.Engine.Specs/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/SharpDB.Engine.Specs/DatabaseContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using SharpDB.Engine.Cache; 7 | using SharpDB.Engine.IO; 8 | 9 | namespace SharpDB.Engine.Specs 10 | { 11 | public class DatabaseContext : IDisposable 12 | { 13 | public const string SpecsDbFile = "specs.dbfile"; 14 | 15 | public DatabaseContext() 16 | { 17 | File.Delete(SpecsDbFile); 18 | 19 | Database = new KeyValueDatabase(filename => new DatabaseFileReader(filename), filename => new DatabaseFileWriter(filename), 20 | filename => new MemoryCacheProvider(filename)); 21 | Database.FileName = SpecsDbFile; 22 | Database.Start(); 23 | } 24 | 25 | public KeyValueDatabase Database { get; private set; } 26 | 27 | public void Restart() 28 | { 29 | Database.Stop(); 30 | Database = new KeyValueDatabase(filename => new DatabaseFileReader(filename), filename => new DatabaseFileWriter(filename), 31 | filename => new MemoryCacheProvider(filename)); 32 | Database.FileName = SpecsDbFile; 33 | Database.Start(); 34 | } 35 | 36 | public void Dispose() 37 | { 38 | Database.Stop(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/SharpDB.Engine.Specs/Features/GetUpdateDelete.feature: -------------------------------------------------------------------------------- 1 | Feature: GetUpdateDelete 2 | In order to manage my documents in the database 3 | As a developer 4 | I want to save, get or delete a document from the database 5 | 6 | Scenario: Saving a new document in the database 7 | When I update a document with Key=1 and Value="Doron" 8 | Then Document is in the database with Key=1 and Value="Doron" 9 | 10 | Scenario: Getting already stored document 11 | Given Document Key=1 and Value="Doron" in the DB 12 | When I get document with key=1 13 | Then Document should exist and Value="Doron" 14 | 15 | Scenario: Update already exist document 16 | Given Document Key=1 and Value="Doron" in the DB 17 | When I update a document with Key=1 and Value="Doron2" 18 | Then Document is in the database with Key=1 and Value="Doron2" 19 | 20 | Scenario: Delete already exist document 21 | Given Document Key=1 and Value="Doron" in the DB 22 | When I delete document with key=1 23 | Then Document with key=1 should not exist 24 | 25 | Scenario: Reupdate an object after got deleted 26 | Given Document Key=1 and Value="Doron" in the DB 27 | When I delete document with key=1 28 | And I update a document with Key=1 and Value="Doron2" 29 | Then Document is in the database with Key=1 and Value="Doron2" 30 | 31 | Scenario: Document is in database after restart 32 | When I update a document with Key=1 and Value="Doron" 33 | And Restrt the database 34 | Then Document is in the database with Key=1 and Value="Doron" 35 | 36 | Scenario: Deleting document already in the database before starting 37 | Given Document Key=1 and Value="Doron" in the DB 38 | And Restrt the database 39 | When I delete document with key=1 40 | And Restrt the database 41 | Then Document with key=1 should not exist 42 | 43 | Scenario: Update document already exist in the database before starting 44 | Given Document Key=1 and Value="Doron" in the DB 45 | And Restrt the database 46 | When I update a document with Key=1 and Value="Doron2" 47 | And Restrt the database 48 | Then Document is in the database with Key=1 and Value="Doron2" 49 | -------------------------------------------------------------------------------- /src/SharpDB.Engine.Specs/Features/GetUpdateDelete.feature.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by SpecFlow (http://www.specflow.org/). 4 | // SpecFlow Version:1.9.0.77 5 | // SpecFlow Generator Version:1.9.0.0 6 | // Runtime Version:4.0.30319.18033 7 | // 8 | // Changes to this file may cause incorrect behavior and will be lost if 9 | // the code is regenerated. 10 | // 11 | // ------------------------------------------------------------------------------ 12 | #region Designer generated code 13 | #pragma warning disable 14 | namespace SharpDB.Engine.Specs.Features 15 | { 16 | using TechTalk.SpecFlow; 17 | 18 | 19 | [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.0.77")] 20 | [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 21 | [NUnit.Framework.TestFixtureAttribute()] 22 | [NUnit.Framework.DescriptionAttribute("GetUpdateDelete")] 23 | public partial class GetUpdateDeleteFeature 24 | { 25 | 26 | private static TechTalk.SpecFlow.ITestRunner testRunner; 27 | 28 | #line 1 "GetUpdateDelete.feature" 29 | #line hidden 30 | 31 | [NUnit.Framework.TestFixtureSetUpAttribute()] 32 | public virtual void FeatureSetup() 33 | { 34 | testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); 35 | TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "GetUpdateDelete", "In order to manage my documents in the database\r\nAs a developer\r\nI want to save, " + 36 | "get or delete a document from the database", ProgrammingLanguage.CSharp, ((string[])(null))); 37 | testRunner.OnFeatureStart(featureInfo); 38 | } 39 | 40 | [NUnit.Framework.TestFixtureTearDownAttribute()] 41 | public virtual void FeatureTearDown() 42 | { 43 | testRunner.OnFeatureEnd(); 44 | testRunner = null; 45 | } 46 | 47 | [NUnit.Framework.SetUpAttribute()] 48 | public virtual void TestInitialize() 49 | { 50 | } 51 | 52 | [NUnit.Framework.TearDownAttribute()] 53 | public virtual void ScenarioTearDown() 54 | { 55 | testRunner.OnScenarioEnd(); 56 | } 57 | 58 | public virtual void ScenarioSetup(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) 59 | { 60 | testRunner.OnScenarioStart(scenarioInfo); 61 | } 62 | 63 | public virtual void ScenarioCleanup() 64 | { 65 | testRunner.CollectScenarioErrors(); 66 | } 67 | 68 | [NUnit.Framework.TestAttribute()] 69 | [NUnit.Framework.DescriptionAttribute("Saving a new document in the database")] 70 | public virtual void SavingANewDocumentInTheDatabase() 71 | { 72 | TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Saving a new document in the database", ((string[])(null))); 73 | #line 6 74 | this.ScenarioSetup(scenarioInfo); 75 | #line 7 76 | testRunner.When("I update a document with Key=1 and Value=\"Doron\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); 77 | #line 8 78 | testRunner.Then("Document is in the database with Key=1 and Value=\"Doron\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); 79 | #line hidden 80 | this.ScenarioCleanup(); 81 | } 82 | 83 | [NUnit.Framework.TestAttribute()] 84 | [NUnit.Framework.DescriptionAttribute("Getting already stored document")] 85 | public virtual void GettingAlreadyStoredDocument() 86 | { 87 | TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Getting already stored document", ((string[])(null))); 88 | #line 10 89 | this.ScenarioSetup(scenarioInfo); 90 | #line 11 91 | testRunner.Given("Document Key=1 and Value=\"Doron\" in the DB", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); 92 | #line 12 93 | testRunner.When("I get document with key=1", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); 94 | #line 13 95 | testRunner.Then("Document should exist and Value=\"Doron\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); 96 | #line hidden 97 | this.ScenarioCleanup(); 98 | } 99 | 100 | [NUnit.Framework.TestAttribute()] 101 | [NUnit.Framework.DescriptionAttribute("Update already exist document")] 102 | public virtual void UpdateAlreadyExistDocument() 103 | { 104 | TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Update already exist document", ((string[])(null))); 105 | #line 15 106 | this.ScenarioSetup(scenarioInfo); 107 | #line 16 108 | testRunner.Given("Document Key=1 and Value=\"Doron\" in the DB", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); 109 | #line 17 110 | testRunner.When("I update a document with Key=1 and Value=\"Doron2\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); 111 | #line 18 112 | testRunner.Then("Document is in the database with Key=1 and Value=\"Doron2\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); 113 | #line hidden 114 | this.ScenarioCleanup(); 115 | } 116 | 117 | [NUnit.Framework.TestAttribute()] 118 | [NUnit.Framework.DescriptionAttribute("Delete already exist document")] 119 | public virtual void DeleteAlreadyExistDocument() 120 | { 121 | TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Delete already exist document", ((string[])(null))); 122 | #line 20 123 | this.ScenarioSetup(scenarioInfo); 124 | #line 21 125 | testRunner.Given("Document Key=1 and Value=\"Doron\" in the DB", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); 126 | #line 22 127 | testRunner.When("I delete document with key=1", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); 128 | #line 23 129 | testRunner.Then("Document with key=1 should not exist", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); 130 | #line hidden 131 | this.ScenarioCleanup(); 132 | } 133 | 134 | [NUnit.Framework.TestAttribute()] 135 | [NUnit.Framework.DescriptionAttribute("Reupdate an object after got deleted")] 136 | public virtual void ReupdateAnObjectAfterGotDeleted() 137 | { 138 | TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Reupdate an object after got deleted", ((string[])(null))); 139 | #line 25 140 | this.ScenarioSetup(scenarioInfo); 141 | #line 26 142 | testRunner.Given("Document Key=1 and Value=\"Doron\" in the DB", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); 143 | #line 27 144 | testRunner.When("I delete document with key=1", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); 145 | #line 28 146 | testRunner.And("I update a document with Key=1 and Value=\"Doron2\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); 147 | #line 29 148 | testRunner.Then("Document is in the database with Key=1 and Value=\"Doron2\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); 149 | #line hidden 150 | this.ScenarioCleanup(); 151 | } 152 | 153 | [NUnit.Framework.TestAttribute()] 154 | [NUnit.Framework.DescriptionAttribute("Document is in database after restart")] 155 | public virtual void DocumentIsInDatabaseAfterRestart() 156 | { 157 | TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Document is in database after restart", ((string[])(null))); 158 | #line 31 159 | this.ScenarioSetup(scenarioInfo); 160 | #line 32 161 | testRunner.When("I update a document with Key=1 and Value=\"Doron\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); 162 | #line 33 163 | testRunner.And("Restrt the database", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); 164 | #line 34 165 | testRunner.Then("Document is in the database with Key=1 and Value=\"Doron\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); 166 | #line hidden 167 | this.ScenarioCleanup(); 168 | } 169 | 170 | [NUnit.Framework.TestAttribute()] 171 | [NUnit.Framework.DescriptionAttribute("Deleting document already in the database before starting")] 172 | public virtual void DeletingDocumentAlreadyInTheDatabaseBeforeStarting() 173 | { 174 | TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Deleting document already in the database before starting", ((string[])(null))); 175 | #line 36 176 | this.ScenarioSetup(scenarioInfo); 177 | #line 37 178 | testRunner.Given("Document Key=1 and Value=\"Doron\" in the DB", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); 179 | #line 38 180 | testRunner.And("Restrt the database", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); 181 | #line 39 182 | testRunner.When("I delete document with key=1", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); 183 | #line 40 184 | testRunner.And("Restrt the database", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); 185 | #line 41 186 | testRunner.Then("Document with key=1 should not exist", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); 187 | #line hidden 188 | this.ScenarioCleanup(); 189 | } 190 | 191 | [NUnit.Framework.TestAttribute()] 192 | [NUnit.Framework.DescriptionAttribute("Update document already exist in the database before starting")] 193 | public virtual void UpdateDocumentAlreadyExistInTheDatabaseBeforeStarting() 194 | { 195 | TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Update document already exist in the database before starting", ((string[])(null))); 196 | #line 43 197 | this.ScenarioSetup(scenarioInfo); 198 | #line 44 199 | testRunner.Given("Document Key=1 and Value=\"Doron\" in the DB", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); 200 | #line 45 201 | testRunner.And("Restrt the database", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); 202 | #line 46 203 | testRunner.When("I update a document with Key=1 and Value=\"Doron2\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); 204 | #line 47 205 | testRunner.And("Restrt the database", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); 206 | #line 48 207 | testRunner.Then("Document is in the database with Key=1 and Value=\"Doron2\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); 208 | #line hidden 209 | this.ScenarioCleanup(); 210 | } 211 | } 212 | } 213 | #pragma warning restore 214 | #endregion 215 | -------------------------------------------------------------------------------- /src/SharpDB.Engine.Specs/Hooks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using SharpDB.Engine.Cache; 7 | using SharpDB.Engine.IO; 8 | using TechTalk.SpecFlow; 9 | using SharpDB.Engine; 10 | 11 | namespace SharpDB.Engine.Specs 12 | { 13 | public class Hooks 14 | { 15 | //[BeforeScenario] 16 | //public static void BeforeScenario() 17 | //{ 18 | 19 | //} 20 | 21 | //[AfterScenario()] 22 | //public static void AfterScenario() 23 | //{ 24 | 25 | //} 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/SharpDB.Engine.Specs/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SharpDB.Engine.Specs")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SharpDB.Engine.Specs")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("c50fd293-3157-4280-961e-cbb6bf5b0f4f")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/SharpDB.Engine.Specs/SharpDB.Engine.Specs.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 8.0.30703 8 | 2.0 9 | {93F474C3-4C19-4E87-8BA8-36EBDB4CBF06} 10 | Library 11 | Properties 12 | SharpDB.Engine.Specs 13 | SharpDB.Engine.Specs 14 | v4.0 15 | 512 16 | 17 | 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | ..\packages\Newtonsoft.Json.11.0.2\lib\net40\Newtonsoft.Json.dll 39 | 40 | 41 | ..\packages\NUnit.3.10.1\lib\net40\nunit.framework.dll 42 | 43 | 44 | 45 | 46 | ..\packages\System.ValueTuple.4.5.0-preview2-26406-04\lib\portable-net40+sl4+win8+wp8\System.ValueTuple.dll 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | True 58 | True 59 | GetUpdateDelete.feature 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | SpecFlowSingleFileGenerator 70 | GetUpdateDelete.feature.cs 71 | 72 | 73 | 74 | 75 | 76 | {A68DF91D-0DA4-453E-8CCE-9312FCA6B9F0} 77 | SharpDB.Engine 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。 87 | 88 | 89 | 90 | 91 | 92 | 99 | -------------------------------------------------------------------------------- /src/SharpDB.Engine.Specs/Steps/DatabaseGeneralSteps.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using TechTalk.SpecFlow; 6 | 7 | namespace SharpDB.Engine.Specs.Steps 8 | { 9 | [Binding] 10 | public class DatabaseGeneralSteps 11 | { 12 | private readonly DatabaseContext m_databaseContext; 13 | 14 | public DatabaseGeneralSteps(DatabaseContext databaseContext) 15 | { 16 | m_databaseContext = databaseContext; 17 | } 18 | 19 | 20 | [StepDefinition(@"Restrt the database")] 21 | public void WhenRestrtTheDatabase() 22 | { 23 | m_databaseContext.Restart(); 24 | } 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/SharpDB.Engine.Specs/Steps/GetUpdateDeleteSteps.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using NUnit.Framework; 6 | using SharpDB.Engine.Cache; 7 | using SharpDB.Engine.Domain; 8 | using SharpDB.Engine.IO; 9 | using TechTalk.SpecFlow; 10 | 11 | namespace SharpDB.Engine.Specs.Steps 12 | { 13 | [Binding] 14 | public class GetUpdateDeleteSteps 15 | { 16 | private readonly DatabaseContext m_databaseContext; 17 | 18 | private byte[] m_documentValue; 19 | 20 | public GetUpdateDeleteSteps(DatabaseContext databaseContext) 21 | { 22 | m_databaseContext = databaseContext; 23 | } 24 | 25 | [Given(@"Document Key=(.*) and Value=""(.*)"" in the DB")] 26 | [When(@"I update a document with Key=(.*) and Value=""(.*)""")] 27 | public void UpdateDocument(int key, string value) 28 | { 29 | byte[] valueBytes = Encoding.ASCII.GetBytes(value); 30 | 31 | m_databaseContext.Database.Update(new DocumentId(key), valueBytes); 32 | } 33 | 34 | [Then(@"Document is in the database with Key=(.*) and Value=""(.*)""")] 35 | public void ThenDocumentIsInTheDatabaseWithKeyAndValue(int key, string value) 36 | { 37 | DocumentId documentId = new DocumentId(key); 38 | 39 | byte[] valueBytes = m_databaseContext.Database.Get(documentId); 40 | 41 | string dbValue = Encoding.ASCII.GetString(valueBytes); 42 | 43 | Assert.AreEqual(dbValue, value); 44 | } 45 | 46 | [When(@"I get document with key=(.*)")] 47 | public void WhenIGetDocumentWithKey(int key) 48 | { 49 | m_documentValue = m_databaseContext.Database.Get(new DocumentId(key)); 50 | } 51 | 52 | [Then(@"Document should exist and Value=""(.*)""")] 53 | public void ThenDocumentShouldExistAndValue(string value) 54 | { 55 | Assert.Greater(value.Length, 0); 56 | 57 | string dbValue = Encoding.ASCII.GetString(m_documentValue); 58 | 59 | Assert.AreEqual(dbValue, value); 60 | } 61 | 62 | [When(@"I delete document with key=(.*)")] 63 | public void WhenIDeleteDocumentWithKey(int key) 64 | { 65 | m_databaseContext.Database.Delete(new DocumentId(Convert.ToInt32(key))); 66 | } 67 | 68 | [Then(@"Document with key=(.*) should not exist")] 69 | public void ThenDocumentWithKeyShouldNotExist(int key) 70 | { 71 | byte[] valueBytes = m_databaseContext.Database.Get(new DocumentId(key)); 72 | 73 | Assert.AreEqual(0, valueBytes.Length); 74 | } 75 | 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/SharpDB.Engine.Specs/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/SharpDB.Engine.Tests/DBTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using NUnit.Framework; 3 | using SharpDB.Engine.Domain; 4 | using SharpDB.Engine.IO; 5 | using SharpDB.Engine.Cache; 6 | 7 | namespace SharpDB.Engine.Tests 8 | { 9 | [TestFixture] 10 | public class DBTests 11 | { 12 | private KeyValueDatabase m_db; 13 | 14 | [SetUp] 15 | public void SetUpTest() 16 | { 17 | m_db = new KeyValueDatabase(filename => new DatabaseFileReader(filename), filename=> new DatabaseFileWriter(filename), 18 | filename => new MemoryCacheProvider(filename)); 19 | 20 | m_db.Start(); 21 | } 22 | 23 | [TearDown] 24 | public void TearDownTest() 25 | { 26 | m_db.Stop(); 27 | m_db = null; 28 | 29 | File.Delete("test.dbfile"); 30 | } 31 | 32 | [Test] 33 | public void TryReadFutureDocument() 34 | { 35 | int transactionId = m_db.StartTransaction(); 36 | 37 | DocumentId id1 = new DocumentId("1"); 38 | 39 | // this object is stored out of transaction therefore should be visible for the transaction 40 | m_db.Update(id1, new byte[1] { 0 }); 41 | 42 | byte[] blob = m_db.TransactionGet(transactionId, id1); 43 | 44 | Assert.IsNull(blob); 45 | 46 | m_db.CommitTransaction(transactionId); 47 | } 48 | 49 | [Test] 50 | public void ReadOldRevisionDuringTransaction() 51 | { 52 | DocumentId id1 = new DocumentId("1"); 53 | 54 | m_db.Update(id1, new byte[1] { 0 }); 55 | 56 | int transactionId = m_db.StartTransaction(); 57 | 58 | m_db.Update(id1, new byte[1] { 1 }); 59 | 60 | byte[] blob = m_db.TransactionGet(transactionId, id1); 61 | 62 | Assert.AreEqual(0, blob[0]); 63 | 64 | m_db.CommitTransaction(transactionId); 65 | 66 | transactionId = m_db.StartTransaction(); 67 | 68 | blob = m_db.TransactionGet(transactionId, id1); 69 | 70 | Assert.AreEqual(1, blob[0]); 71 | 72 | m_db.CommitTransaction(transactionId); 73 | } 74 | 75 | [Test] 76 | public void ReadDocumentUpdatedByTransaction() 77 | { 78 | DocumentId id1 = new DocumentId("1"); 79 | 80 | m_db.Update(id1, new byte[1] { 0 }); 81 | 82 | int transactionId = m_db.StartTransaction(); 83 | 84 | m_db.TransactionUpdate(transactionId, id1, new byte[1] { 1 }); 85 | 86 | byte[] blob = m_db.TransactionGet(transactionId, id1); 87 | Assert.AreEqual(1, blob[0]); 88 | 89 | // reading without transaction should return uncommitted document 90 | blob = m_db.Get(id1); 91 | Assert.AreEqual(0, blob[0]); 92 | 93 | m_db.CommitTransaction(transactionId); 94 | 95 | // reading the object agian after the transaction committed making sure we are reading the new committed document 96 | blob = m_db.Get(id1); 97 | Assert.AreEqual(1, blob[0]); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/SharpDB.Engine.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SharpDB.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SharpDB.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("0cb03035-33c9-478f-a923-1140b9c72e8a")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/SharpDB.Engine.Tests/SharpDB.Engine.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 8.0.30703 8 | 2.0 9 | {4AFC56FA-8F1E-414D-954C-C3A4604800A7} 10 | Library 11 | Properties 12 | SharpDB.Engine.Tests 13 | SharpDB.Engine.Tests 14 | v4.0 15 | 512 16 | 17 | 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | ..\packages\NUnit.3.10.1\lib\net40\nunit.framework.dll 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | {45fd4b03-c653-47bc-8190-46d3ef934add} 58 | SharpDB.Engine 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。 68 | 69 | 70 | 71 | 78 | -------------------------------------------------------------------------------- /src/SharpDB.Engine.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/SharpDB.Engine/Cache/ICacheProvider.cs: -------------------------------------------------------------------------------- 1 | namespace SharpDB.Engine.Cache 2 | { 3 | public interface ICacheProvider 4 | { 5 | string Name { get; set; } 6 | void Set(long fileLocation, byte[] blob); 7 | byte[] Get(long fileLocation); 8 | } 9 | } -------------------------------------------------------------------------------- /src/SharpDB.Engine/Cache/MemoryCacheProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Caching; 5 | using System.Text; 6 | 7 | namespace SharpDB.Engine.Cache 8 | { 9 | public class MemoryCacheProvider : ICacheProvider 10 | { 11 | private MemoryCache m_memoryCache; 12 | private CacheItemPolicy m_policy; 13 | 14 | public MemoryCacheProvider(string name) 15 | { 16 | Name = name; 17 | m_memoryCache = new MemoryCache(Name); 18 | 19 | m_policy = new CacheItemPolicy(); 20 | m_policy.SlidingExpiration = TimeSpan.FromHours(1); 21 | } 22 | 23 | public string Name { get; set; } 24 | 25 | public void Set(long fileLocation, byte[] blob) 26 | { 27 | string key = ConvertLongToString(fileLocation); 28 | 29 | m_memoryCache.Set(key, blob, m_policy); 30 | } 31 | 32 | private string ConvertLongToString(long fileLocation) 33 | { 34 | // converting the long to string 35 | byte[] bytes = BitConverter.GetBytes(fileLocation); 36 | 37 | return Encoding.ASCII.GetString(bytes); 38 | } 39 | 40 | public byte[] Get(long fileLocation) 41 | { 42 | string key = ConvertLongToString(fileLocation); 43 | 44 | return (byte[])m_memoryCache.Get(key); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/SharpDB.Engine/DocumentLockedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SharpDB.Engine 7 | { 8 | public class DocumentLockedException : Exception 9 | { 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/SharpDB.Engine/Domain/Document.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using SharpDB.Engine.Utils; 4 | 5 | namespace SharpDB.Engine.Domain 6 | { 7 | public class Document 8 | { 9 | private SortedList m_revisions = 10 | new SortedList(new ULongDescendingComparer()); 11 | 12 | public Document(DocumentId documentId, ulong documentTimeStamp, long blobFileLocation, int blobSize) 13 | { 14 | DocumentId = documentId; 15 | 16 | CurrentRevision = new DocumentRevision(documentId,documentTimeStamp, blobFileLocation, blobSize); 17 | } 18 | 19 | public DocumentId DocumentId { get; private set; } 20 | 21 | public DocumentRevision CurrentRevision { get; private set; } 22 | 23 | public int TransactionId { get; private set; } 24 | 25 | public bool IsLocked {get { return TransactionId != 0; }} 26 | 27 | public void Update(ulong documentTimeStamp, long blobFileLocation, int blobSize, bool saveRevision) 28 | { 29 | CurrentRevision.Expire(documentTimeStamp); 30 | 31 | if (saveRevision) 32 | { 33 | m_revisions.Add(CurrentRevision.TimeStamp, CurrentRevision); 34 | } 35 | 36 | CurrentRevision = new DocumentRevision(DocumentId, documentTimeStamp, blobFileLocation, blobSize); 37 | } 38 | 39 | public void TransactionLock(int transactionId) 40 | { 41 | TransactionId = transactionId; 42 | } 43 | 44 | public void RollbackTransaction() 45 | { 46 | TransactionId = 0; 47 | } 48 | 49 | public void CommitTransaction(ulong documentTimeStamp, long blobFileLocation, int blobSize) 50 | { 51 | TransactionId = 0; 52 | 53 | Update(documentTimeStamp, blobFileLocation, blobSize, true); 54 | } 55 | 56 | public DocumentRevision GetDocumentRevisionByTimestamp(ulong timestamp) 57 | { 58 | if (timestamp >= CurrentRevision.TimeStamp) 59 | { 60 | return CurrentRevision; 61 | } 62 | else 63 | { 64 | foreach (KeyValuePair documentRevision in m_revisions) 65 | { 66 | if (timestamp >= documentRevision.Value.TimeStamp && timestamp < documentRevision.Value.ExpireTimeStamp) 67 | { 68 | return documentRevision.Value; 69 | } 70 | } 71 | 72 | return null; 73 | } 74 | } 75 | 76 | public List GetRevisionsAboveTimestamp(ulong timestamp) 77 | { 78 | var list = m_revisions.Where(r => r.Key > timestamp).Select(r=> r.Value).ToList(); 79 | 80 | if (CurrentRevision.TimeStamp > timestamp) 81 | { 82 | list.Add(CurrentRevision); 83 | } 84 | 85 | return list; 86 | } 87 | 88 | public void Cleanup(ulong timestamp) 89 | { 90 | // find all the revisions with timestamp lower than the minimum timestamp 91 | ulong[] keys = m_revisions.Keys.Where(k => k < timestamp).ToArray(); 92 | 93 | foreach (ulong key in keys) 94 | { 95 | m_revisions.Remove(key); 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/SharpDB.Engine/Domain/DocumentKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SharpDB.Engine.Domain 7 | { 8 | public class DocumentId 9 | { 10 | private int m_hash; 11 | 12 | public DocumentId(string id) 13 | : this(Encoding.Unicode.GetBytes(id)) 14 | { 15 | 16 | } 17 | 18 | public DocumentId(int id) 19 | : this(BitConverter.GetBytes(id)) 20 | { 21 | 22 | } 23 | 24 | public DocumentId(byte[] value) 25 | { 26 | Bytes = value; 27 | } 28 | 29 | public byte[] Bytes { get; private set; } 30 | 31 | public int Length 32 | { 33 | get { return Bytes.Length; } 34 | } 35 | 36 | public string GetBytesReprestnation() 37 | { 38 | StringBuilder bytesRepresentation = new StringBuilder(); 39 | 40 | for (int i = 0; i < Bytes.Length - 1; i++) 41 | { 42 | bytesRepresentation.AppendFormat("{0},", (int)Bytes[i]); 43 | } 44 | 45 | bytesRepresentation.AppendFormat("{0}", (int) Bytes.Last()); 46 | 47 | return bytesRepresentation.ToString(); 48 | } 49 | 50 | protected bool Equals(DocumentId other) 51 | { 52 | if (other.Bytes.Length != Bytes.Length) 53 | { 54 | return false; 55 | } 56 | 57 | return Bytes.SequenceEqual(other.Bytes); 58 | } 59 | 60 | public override bool Equals(object obj) 61 | { 62 | if (ReferenceEquals(null, obj)) return false; 63 | if (ReferenceEquals(this, obj)) return true; 64 | 65 | if (obj.GetType() != this.GetType()) return false; 66 | return Equals((DocumentId)obj); 67 | } 68 | 69 | public override int GetHashCode() 70 | { 71 | if (m_hash == 0) 72 | { 73 | foreach (byte b in Bytes) 74 | { 75 | m_hash = 31 * m_hash + b; 76 | } 77 | } 78 | return m_hash; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/SharpDB.Engine/Domain/DocumentRevision.cs: -------------------------------------------------------------------------------- 1 | namespace SharpDB.Engine.Domain 2 | { 3 | public class DocumentRevision 4 | { 5 | public DocumentRevision(DocumentId documentId, ulong timeStamp, long blobFileLocation, int blobSize) 6 | { 7 | DocumentId = documentId; 8 | TimeStamp = timeStamp; 9 | BlobFileLocation = blobFileLocation; 10 | BlobSize = blobSize; 11 | 12 | ExpireTimeStamp = 0; 13 | } 14 | 15 | public DocumentId DocumentId { get; private set; } 16 | 17 | public ulong TimeStamp { get; private set; } 18 | 19 | public ulong ExpireTimeStamp { get; private set; } 20 | 21 | public long BlobFileLocation { get; private set; } 22 | 23 | public int BlobSize { get; private set; } 24 | 25 | public bool IsDeleted {get { return BlobSize == 0; }} 26 | 27 | public void Expire(ulong timestamp) 28 | { 29 | ExpireTimeStamp = timestamp; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/SharpDB.Engine/Domain/DocumentStore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SharpDB.Engine.Domain 7 | { 8 | public class DocumentStore 9 | { 10 | Dictionary m_documents; 11 | 12 | public DocumentStore(Dictionary documents) 13 | { 14 | m_documents = documents; 15 | } 16 | 17 | public Document GetDocumentForUpdate(DocumentId documentId, int transactionId) 18 | { 19 | Document document; 20 | 21 | if (m_documents.TryGetValue(documentId, out document) && document.IsLocked && transactionId != document.TransactionId) 22 | { 23 | throw new DocumentLockedException(); 24 | } 25 | else if (document != null && document.TransactionId == 0 && transactionId != -1) 26 | { 27 | document.TransactionLock(transactionId); 28 | } 29 | 30 | return document; 31 | } 32 | 33 | public Document GetDocument(DocumentId documentId) 34 | { 35 | Document document; 36 | 37 | m_documents.TryGetValue(documentId, out document); 38 | 39 | return document; 40 | } 41 | 42 | public void AddNewDocument(DocumentId documentId, ulong documentTimeStamp, long blobFileLocation, int blobSize) 43 | { 44 | m_documents.Add(documentId, new Document(documentId, documentTimeStamp, blobFileLocation, blobSize)); 45 | } 46 | 47 | public void Cleanup(ulong timestamp) 48 | { 49 | foreach (KeyValuePair keyValuePair in m_documents) 50 | { 51 | keyValuePair.Value.Cleanup(timestamp); 52 | } 53 | } 54 | 55 | public IList GetAllDocumentsLatestRevision() 56 | { 57 | return m_documents.Values.Select(d => d.CurrentRevision).ToList(); 58 | } 59 | 60 | public IList GetAllRevisionAboveTimestamp(ulong timestamp) 61 | { 62 | return m_documents.Values.SelectMany(d => d.GetRevisionsAboveTimestamp(timestamp)).ToList(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/SharpDB.Engine/Domain/Transaction.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SharpDB.Engine.Domain 4 | { 5 | public class Transaction 6 | { 7 | Dictionary m_updates = new Dictionary(); 8 | 9 | public Transaction(int transactionId, ulong dbTimestamp) 10 | { 11 | TransactionId = transactionId; 12 | DBTimestamp = dbTimestamp; 13 | } 14 | 15 | public int TransactionId { get; private set; } 16 | public ulong DBTimestamp { get; private set; } 17 | 18 | public int Count 19 | { 20 | get { return m_updates.Count; } 21 | } 22 | 23 | public void AddUpdate(DocumentId documentId, byte[] blob) 24 | { 25 | m_updates.Add(documentId, blob); 26 | } 27 | 28 | public IEnumerable GetDocumentIds() 29 | { 30 | return m_updates.Keys; 31 | } 32 | 33 | public byte[] GetBlob(DocumentId documentId) 34 | { 35 | return m_updates[documentId]; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/SharpDB.Engine/IO/DatabaseFileReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using SharpDB.Engine.Domain; 6 | 7 | namespace SharpDB.Engine.IO 8 | { 9 | public class DatabaseFileReader : IDatabaseReader 10 | { 11 | private FileStream m_readStream; 12 | 13 | public DatabaseFileReader(string fileName) 14 | { 15 | FileName = fileName; 16 | 17 | m_readStream = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); 18 | } 19 | 20 | public string FileName { get; private set; } 21 | 22 | public Dictionary GetDocuments(out ulong dbTimestamp) 23 | { 24 | dbTimestamp = 0; 25 | 26 | Dictionary documents = new Dictionary(); 27 | 28 | // initialize the buffers 29 | byte[] timestampBuffer = new byte[12]; 30 | byte[] documentIdLengthBuffer = new byte[2]; 31 | byte[] documentIdBuffer = new byte[UInt16.MaxValue]; 32 | 33 | byte[] blobLengthBuffer = new byte[4]; 34 | 35 | int numberOfDocuments = 0; 36 | int documentsCounter = 0; 37 | 38 | // now we read all the objects meta data (not loading any data yet) 39 | while (m_readStream.Position < m_readStream.Length) 40 | { 41 | if (numberOfDocuments == documentsCounter) 42 | { 43 | // now we are reading the object timestamp 44 | m_readStream.Read(timestampBuffer, 0, 12); 45 | dbTimestamp = BitConverter.ToUInt64(timestampBuffer, 0); 46 | 47 | numberOfDocuments = BitConverter.ToInt32(timestampBuffer, 8); 48 | documentsCounter = 0; 49 | } 50 | 51 | // first is the object id lenth 52 | m_readStream.Read(documentIdLengthBuffer, 0, 2); 53 | UInt16 objectIdLength = BitConverter.ToUInt16(documentIdLengthBuffer, 0); 54 | 55 | // read the objectId 56 | m_readStream.Read(documentIdBuffer, 0, objectIdLength); 57 | 58 | byte[] documentIdBytes = new byte[objectIdLength]; 59 | Buffer.BlockCopy(documentIdBuffer, 0, documentIdBytes, 0, objectIdLength); 60 | 61 | DocumentId documentId = new DocumentId(documentIdBytes); 62 | 63 | // read the blob length 64 | m_readStream.Read(blobLengthBuffer, 0, 4); 65 | int blobLength = BitConverter.ToInt32(blobLengthBuffer, 0); 66 | long blobLocation = m_readStream.Position; 67 | 68 | // take the position of the file to the next document 69 | m_readStream.Position += blobLength; 70 | 71 | // check if the document not exist and not deleted (zero length is deleted 72 | if (!documents.ContainsKey(documentId) && blobLength > 0) 73 | { 74 | documents.Add(documentId, new Document(documentId, dbTimestamp, blobLocation, blobLength)); 75 | } 76 | else 77 | { 78 | // if the document is deleted we just remove the document from the store 79 | if (blobLength == 0) 80 | { 81 | documents.Remove(documentId); 82 | } 83 | else 84 | { 85 | Document document = documents[documentId]; 86 | 87 | document.Update(dbTimestamp, blobLocation, blobLength, false); 88 | } 89 | } 90 | 91 | documentsCounter++; 92 | } 93 | 94 | if (documentsCounter != numberOfDocuments) 95 | { 96 | // database is corrupted, need to recover 97 | throw new Exception("Database is corrupted"); 98 | } 99 | 100 | return documents; 101 | } 102 | 103 | public byte[] ReadDocument(long fileLocation, int size) 104 | { 105 | m_readStream.Position = fileLocation; 106 | 107 | byte[] blob = new byte[size]; 108 | m_readStream.Read(blob, 0, size); 109 | 110 | return blob; 111 | } 112 | 113 | 114 | public void Dispose() 115 | { 116 | m_readStream.Dispose(); 117 | 118 | m_readStream = null; 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/SharpDB.Engine/IO/DatabaseFileWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using SharpDB.Engine.Domain; 5 | 6 | namespace SharpDB.Engine.IO 7 | { 8 | public class DatabaseFileWriter : IDatabaseWriter 9 | { 10 | private FileStream m_writerStream; 11 | 12 | private byte[] m_writeDocumentBuffer; 13 | 14 | private byte[] m_writeStartTimestampBuffer; 15 | 16 | private const uint BlobMaxSize = 1024 * 1024; // One mega 17 | 18 | public DatabaseFileWriter(string fileName) 19 | { 20 | FileName = fileName; 21 | 22 | m_writerStream = new FileStream(FileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read); 23 | m_writerStream.Position = m_writerStream.Length; 24 | 25 | // calculating the max size of an object 26 | uint size = 2 + UInt16.MaxValue + 4 + BlobMaxSize; 27 | 28 | m_writeDocumentBuffer = new byte[size]; 29 | 30 | // timestamp size + int16 size 31 | m_writeStartTimestampBuffer = new byte[12]; 32 | } 33 | 34 | public string FileName { get; private set; } 35 | 36 | public long WriteDocument(DocumentId documentId, byte[] blob) 37 | { 38 | // calculate the size of the document including meta data 39 | int size = 2 + documentId.Length + 4 + blob.Length; 40 | 41 | int position = 0; 42 | 43 | Buffer.BlockCopy(BitConverter.GetBytes((UInt16)documentId.Length), 0, m_writeDocumentBuffer, position, 2); 44 | position += 2; 45 | 46 | Buffer.BlockCopy(documentId.Bytes, 0, m_writeDocumentBuffer, position, documentId.Length); 47 | position += documentId.Length; 48 | 49 | Buffer.BlockCopy(BitConverter.GetBytes(blob.Length), 0, m_writeDocumentBuffer, position, 4); 50 | position += 4; 51 | 52 | if (blob.Length > 0) 53 | { 54 | Buffer.BlockCopy(blob, 0, m_writeDocumentBuffer, position, blob.Length); 55 | } 56 | 57 | m_writerStream.Write(m_writeDocumentBuffer, 0, size); 58 | 59 | return m_writerStream.Position - blob.Length; 60 | } 61 | 62 | public void BeginTimestamp(ulong timestamp, int numberOfDocuments) 63 | { 64 | Buffer.BlockCopy(BitConverter.GetBytes(timestamp), 0, m_writeStartTimestampBuffer, 0, 8); 65 | Buffer.BlockCopy(BitConverter.GetBytes(numberOfDocuments), 0, m_writeStartTimestampBuffer, 8, 4); 66 | 67 | m_writerStream.Write(m_writeStartTimestampBuffer, 0, 12); 68 | } 69 | 70 | public void Flush() 71 | { 72 | m_writerStream.Flush(); 73 | } 74 | 75 | 76 | public void Dispose() 77 | { 78 | m_writerStream.Dispose(); 79 | 80 | m_writerStream = null; 81 | m_writeDocumentBuffer = null; 82 | m_writeStartTimestampBuffer = null; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/SharpDB.Engine/IO/IDatabaseReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using SharpDB.Engine.Domain; 4 | 5 | namespace SharpDB.Engine.IO 6 | { 7 | public interface IDatabaseReader : IDisposable 8 | { 9 | string FileName { get; } 10 | Dictionary GetDocuments(out ulong dbTimestamp); 11 | byte[] ReadDocument(long fileLocation, int size); 12 | } 13 | } -------------------------------------------------------------------------------- /src/SharpDB.Engine/IO/IDatabaseWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using SharpDB.Engine.Domain; 3 | 4 | namespace SharpDB.Engine.IO 5 | { 6 | public interface IDatabaseWriter : IDisposable 7 | { 8 | string FileName { get; } 9 | long WriteDocument(DocumentId documentId, byte[] blob); 10 | void BeginTimestamp(ulong timestamp, int numberOfDocuments); 11 | void Flush(); 12 | } 13 | } -------------------------------------------------------------------------------- /src/SharpDB.Engine/KeyValueDatabase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Common.Logging; 9 | using SharpDB.Engine.Domain; 10 | using SharpDB.Engine.IO; 11 | using SharpDB.Engine.Cache; 12 | 13 | namespace SharpDB.Engine 14 | { 15 | public class KeyValueDatabase 16 | { 17 | private readonly byte[] ZeroBlob = new byte[0]; 18 | 19 | private readonly ILog m_log; 20 | private readonly Func m_readerFactory; 21 | private readonly Func m_writerFactory; 22 | private readonly Func m_cacheProviderFactory; 23 | 24 | private DocumentStore m_documentStore; 25 | 26 | private IDatabaseWriter m_databaseFileWriter; 27 | private IDatabaseReader m_databaseFileReader; 28 | private ICacheProvider m_cacheProvider; 29 | 30 | private int m_currentTransactionId = 1; 31 | 32 | private Dictionary m_pendingTransaction = new Dictionary(); 33 | 34 | public KeyValueDatabase(Func readerFactory, 35 | Func writerFactory, Func cacheProviderFactory) 36 | { 37 | m_log = LogManager.GetLogger(this.GetType()); 38 | 39 | FileName = "default.dbfile"; 40 | m_readerFactory = readerFactory; 41 | m_writerFactory = writerFactory; 42 | m_cacheProviderFactory = cacheProviderFactory; 43 | } 44 | 45 | public ulong DBTimeStamp { get; private set; } 46 | 47 | public string FileName { get; set; } 48 | 49 | public void Start() 50 | { 51 | m_databaseFileWriter = m_writerFactory(FileName); 52 | m_databaseFileReader = m_readerFactory(FileName); 53 | 54 | m_cacheProvider = m_cacheProviderFactory(FileName); 55 | 56 | ulong timestamp; 57 | 58 | m_documentStore = new DocumentStore(m_databaseFileReader.GetDocuments(out timestamp)); 59 | 60 | DBTimeStamp = timestamp; 61 | } 62 | 63 | public void Update(DocumentId documentId, byte[] blob) 64 | { 65 | Document document; 66 | try 67 | { 68 | document = m_documentStore.GetDocumentForUpdate(documentId, -1); 69 | } 70 | catch (DocumentLockedException) 71 | { 72 | m_log.InfoFormat("Update failed because document is locked by another transaction, documentId(bytes):{0}", 73 | documentId.GetBytesReprestnation()); 74 | 75 | throw; 76 | } 77 | 78 | DBTimeStamp++; 79 | 80 | m_databaseFileWriter.BeginTimestamp(DBTimeStamp, 1); 81 | 82 | long documentLocation = m_databaseFileWriter.WriteDocument(documentId, blob); 83 | 84 | if (blob.Length > 0) 85 | { 86 | m_cacheProvider.Set(documentLocation, blob); 87 | } 88 | 89 | m_databaseFileWriter.Flush(); 90 | 91 | if (document != null) 92 | { 93 | document.Update(DBTimeStamp, documentLocation, blob.Length, true); 94 | } 95 | else 96 | { 97 | m_documentStore.AddNewDocument(documentId, DBTimeStamp, documentLocation, blob.Length); 98 | } 99 | } 100 | 101 | public byte[] Get(DocumentId key) 102 | { 103 | Document document = m_documentStore.GetDocument(key); 104 | 105 | if (document != null) 106 | { 107 | return ReadInternal(document.CurrentRevision.BlobFileLocation, document.CurrentRevision.BlobSize); 108 | } 109 | else 110 | { 111 | return ZeroBlob; 112 | } 113 | } 114 | 115 | public void Delete(DocumentId documentId) 116 | { 117 | Update(documentId, ZeroBlob); 118 | } 119 | 120 | private byte[] ReadInternal(long fileLocation, int size) 121 | { 122 | if (size > 0) 123 | { 124 | byte[] blob = m_cacheProvider.Get(fileLocation); 125 | 126 | if (blob == null) 127 | { 128 | blob = m_databaseFileReader.ReadDocument(fileLocation, size); 129 | m_cacheProvider.Set(fileLocation, blob); 130 | } 131 | 132 | return blob; 133 | } 134 | else 135 | { 136 | return ZeroBlob; 137 | } 138 | } 139 | 140 | public int StartTransaction() 141 | { 142 | Transaction transaction = new Transaction(m_currentTransactionId, DBTimeStamp); 143 | m_currentTransactionId++; 144 | 145 | m_pendingTransaction.Add(transaction.TransactionId, transaction); 146 | 147 | m_log.DebugFormat("Start transaction {0}", transaction.TransactionId); 148 | 149 | return transaction.TransactionId; 150 | } 151 | 152 | public void TransactionUpdate(int transactionId, DocumentId documentId, byte[] blob) 153 | { 154 | Transaction transaction; 155 | 156 | if (!m_pendingTransaction.TryGetValue(transactionId, out transaction)) 157 | { 158 | throw new TransactionNotExistException(); 159 | } 160 | 161 | try 162 | { 163 | // mark the document is updated by transaction 164 | m_documentStore.GetDocumentForUpdate(documentId, transactionId); 165 | } 166 | catch (DocumentLockedException) 167 | { 168 | m_log.InfoFormat("Tranasction {1} update failed because document is locked by another transaction, documentId(bytes):{0}", 169 | documentId.GetBytesReprestnation(), transactionId); 170 | 171 | throw; 172 | } 173 | 174 | transaction.AddUpdate(documentId, blob); 175 | } 176 | 177 | public void TransactionDelete(int transactionId, DocumentId documentId) 178 | { 179 | TransactionUpdate(transactionId, documentId, ZeroBlob); 180 | } 181 | 182 | public byte[] TransactionGet(int transactionId, DocumentId documentId) 183 | { 184 | Transaction transaction; 185 | 186 | if (!m_pendingTransaction.TryGetValue(transactionId, out transaction)) 187 | { 188 | throw new TransactionNotExistException(); 189 | } 190 | 191 | Document document = m_documentStore.GetDocument(documentId); 192 | if (document == null) 193 | { 194 | return ZeroBlob; 195 | } 196 | 197 | // if updated by current transaction we just return the current blob 198 | if (document.TransactionId == transactionId) 199 | { 200 | return transaction.GetBlob(documentId); 201 | } 202 | 203 | // we need to find the current revision 204 | DocumentRevision revision = document.GetDocumentRevisionByTimestamp(transaction.DBTimestamp); 205 | 206 | // if there is no revision the object is not exist for this timestamp 207 | if (revision == null) 208 | { 209 | return ZeroBlob; 210 | } 211 | 212 | return ReadInternal(revision.BlobFileLocation, revision.BlobSize); 213 | } 214 | 215 | public void RollbackTransaction(int transactionId) 216 | { 217 | Transaction transaction; 218 | 219 | if (!m_pendingTransaction.TryGetValue(transactionId, out transaction)) 220 | { 221 | throw new TransactionNotExistException(); 222 | } 223 | 224 | foreach (DocumentId documentId in transaction.GetDocumentIds()) 225 | { 226 | Document document = m_documentStore.GetDocument(documentId); 227 | document.RollbackTransaction(); 228 | } 229 | 230 | m_pendingTransaction.Remove(transactionId); 231 | 232 | m_log.DebugFormat("Transaction {0} rollbacked", transaction.TransactionId); 233 | } 234 | 235 | public void CommitTransaction(int transactionId) 236 | { 237 | Transaction transaction; 238 | 239 | if (!m_pendingTransaction.TryGetValue(transactionId, out transaction)) 240 | { 241 | throw new TransactionNotExistException(); 242 | } 243 | 244 | if (transaction.Count > 0) 245 | { 246 | DBTimeStamp++; 247 | 248 | m_databaseFileWriter.BeginTimestamp(DBTimeStamp, transaction.Count); 249 | 250 | foreach (DocumentId documentId in transaction.GetDocumentIds()) 251 | { 252 | byte[] blob = transaction.GetBlob(documentId); 253 | 254 | long documentLocation = m_databaseFileWriter.WriteDocument(documentId, blob); 255 | 256 | // we don't store deleted objects in the cache 257 | if (blob.Length > 0) 258 | { 259 | m_cacheProvider.Set(documentLocation, blob); 260 | } 261 | 262 | Document document = m_documentStore.GetDocument(documentId); 263 | 264 | if (document != null) 265 | { 266 | document.CommitTransaction(DBTimeStamp, documentLocation, blob.Length); 267 | } 268 | else 269 | { 270 | // only add the new document if the document is not deleted 271 | if (blob.Length > 0) 272 | { 273 | m_documentStore.AddNewDocument(documentId, DBTimeStamp, documentLocation, blob.Length); 274 | } 275 | } 276 | } 277 | 278 | m_databaseFileWriter.Flush(); 279 | } 280 | 281 | m_pendingTransaction.Remove(transactionId); 282 | 283 | m_log.DebugFormat("Transaction {0} committed", transaction.TransactionId); 284 | } 285 | 286 | public void Cleanup() 287 | { 288 | ulong minTimestamp = DBTimeStamp; 289 | 290 | if (m_pendingTransaction.Any()) 291 | { 292 | minTimestamp = m_pendingTransaction.Min(t => t.Value.DBTimestamp); 293 | } 294 | 295 | m_documentStore.Cleanup(minTimestamp); 296 | } 297 | 298 | public void Stop() 299 | { 300 | m_databaseFileWriter.Dispose(); 301 | m_databaseFileWriter = null; 302 | 303 | m_databaseFileReader.Dispose(); 304 | m_databaseFileReader = null; 305 | 306 | m_documentStore = null; 307 | 308 | m_pendingTransaction.Clear(); 309 | m_pendingTransaction = null; 310 | 311 | m_currentTransactionId = 0; 312 | DBTimeStamp = 0; 313 | } 314 | } 315 | } -------------------------------------------------------------------------------- /src/SharpDB.Engine/SharpDB.Engine.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0;net40 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 4.5.0-preview2-26406-04 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/SharpDB.Engine/TransactionNotExistException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SharpDB.Engine 7 | { 8 | public class TransactionNotExistException : Exception 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/SharpDB.Engine/Utils/ULongDescendingComparer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SharpDB.Engine.Utils 7 | { 8 | public class ULongDescendingComparer : IComparer 9 | { 10 | public int Compare(ulong x, ulong y) 11 | { 12 | if (x > y) 13 | { 14 | return -1; 15 | } 16 | else if (y > x) 17 | { 18 | return 1; 19 | } 20 | else 21 | { 22 | return 0; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/SharpDB.Example/Program.cs: -------------------------------------------------------------------------------- 1 | using SharpDB.Driver; 2 | using System; 3 | 4 | namespace SharpDB.Example 5 | { 6 | internal class Program 7 | { 8 | private static void Main(string[] args) 9 | { 10 | using (SharpDBClient client = new SharpDBClient("tcp://127.0.0.1:5999")) 11 | { 12 | using (SharpDBConnection connection = client.GetConnection()) 13 | { 14 | Account newAccount = new Account(); 15 | newAccount.Name = "Hello"; 16 | newAccount.Id = 1; 17 | connection.Update(newAccount); 18 | Account storedAccount = connection.Get(1); 19 | connection.DeleteDocument(newAccount); 20 | Console.WriteLine("Hello" == storedAccount.Name); 21 | } 22 | } 23 | } 24 | 25 | public class Account 26 | { 27 | public int Id { get; set; } 28 | 29 | public string Name { get; set; } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/SharpDB.Example/SharpDB.Example.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/SharpDB.Server.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27428.2037 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpDB.Server", "SharpDB.Server\SharpDB.Server.csproj", "{5A25A3D1-5B63-4B5F-A606-581F8A858BF6}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpDB.Shared", "SharpDB.Shared\SharpDB.Shared.csproj", "{C88C0D35-FF8C-46B0-87EE-A14AF9A2895D}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpDB.Engine", "SharpDB.Engine\SharpDB.Engine.csproj", "{45FD4B03-C653-47BC-8190-46D3EF934ADD}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpDB.Driver", "SharpDB.Driver\SharpDB.Driver.csproj", "{5C55E790-79A4-491F-801E-502ED19D0FBE}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpDB.Engine.Tests", "SharpDB.Engine.Tests\SharpDB.Engine.Tests.csproj", "{4AFC56FA-8F1E-414D-954C-C3A4604800A7}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpDB.Example", "SharpDB.Example\SharpDB.Example.csproj", "{473F1737-D746-4347-92F6-D0C76A7464CC}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {5A25A3D1-5B63-4B5F-A606-581F8A858BF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {5A25A3D1-5B63-4B5F-A606-581F8A858BF6}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {5A25A3D1-5B63-4B5F-A606-581F8A858BF6}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {5A25A3D1-5B63-4B5F-A606-581F8A858BF6}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {C88C0D35-FF8C-46B0-87EE-A14AF9A2895D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {C88C0D35-FF8C-46B0-87EE-A14AF9A2895D}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {C88C0D35-FF8C-46B0-87EE-A14AF9A2895D}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {C88C0D35-FF8C-46B0-87EE-A14AF9A2895D}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {45FD4B03-C653-47BC-8190-46D3EF934ADD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {45FD4B03-C653-47BC-8190-46D3EF934ADD}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {45FD4B03-C653-47BC-8190-46D3EF934ADD}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {45FD4B03-C653-47BC-8190-46D3EF934ADD}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {5C55E790-79A4-491F-801E-502ED19D0FBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {5C55E790-79A4-491F-801E-502ED19D0FBE}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {5C55E790-79A4-491F-801E-502ED19D0FBE}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {5C55E790-79A4-491F-801E-502ED19D0FBE}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {4AFC56FA-8F1E-414D-954C-C3A4604800A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {4AFC56FA-8F1E-414D-954C-C3A4604800A7}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {4AFC56FA-8F1E-414D-954C-C3A4604800A7}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {4AFC56FA-8F1E-414D-954C-C3A4604800A7}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {473F1737-D746-4347-92F6-D0C76A7464CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {473F1737-D746-4347-92F6-D0C76A7464CC}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {473F1737-D746-4347-92F6-D0C76A7464CC}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {473F1737-D746-4347-92F6-D0C76A7464CC}.Release|Any CPU.Build.0 = Release|Any CPU 48 | EndGlobalSection 49 | GlobalSection(SolutionProperties) = preSolution 50 | HideSolutionNode = FALSE 51 | EndGlobalSection 52 | GlobalSection(ExtensibilityGlobals) = postSolution 53 | SolutionGuid = {1AC48B4D-D8D3-4EAF-88F5-762197106064} 54 | EndGlobalSection 55 | EndGlobal 56 | -------------------------------------------------------------------------------- /src/SharpDB.Server/Network/Server.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Common.Logging; 6 | using NetMQ; 7 | using SharpDB.Engine; 8 | using SharpDB.Engine.Domain; 9 | using SharpDB.Shared; 10 | 11 | namespace SharpDB.Server.Network 12 | { 13 | public class Server 14 | { 15 | private readonly KeyValueDatabase m_db; 16 | private readonly string[] m_addresses; 17 | private NetMQSocket m_serverSocket; 18 | private NetMQPoller m_poller; 19 | private ILog m_log; 20 | 21 | public Server(KeyValueDatabase db, params string[] addresses) 22 | { 23 | m_db = db; 24 | m_addresses = addresses; 25 | m_log = LogManager.GetLogger(this.GetType()); 26 | 27 | if (!m_addresses.Any()) 28 | { 29 | throw new ArgumentException("You must provide at least one address to listen too"); 30 | } 31 | } 32 | 33 | public void Start() 34 | { 35 | using (m_serverSocket = new NetMQ.Sockets.ResponseSocket()) 36 | { 37 | foreach (var address in m_addresses) 38 | { 39 | m_log.InfoFormat("Listening on {0}", address); 40 | m_serverSocket.Bind(address); 41 | } 42 | m_serverSocket.ReceiveReady += OnMessage; 43 | 44 | m_poller = new NetMQPoller(); 45 | m_poller.Add(m_serverSocket); 46 | 47 | m_poller.Run(); 48 | } 49 | } 50 | 51 | public void Stop() 52 | { 53 | m_poller.Stop(); 54 | } 55 | 56 | private void OnMessage(object sender, NetMQSocketEventArgs e) 57 | { 58 | byte[] messageTypeBytes = m_serverSocket.ReceiveFrameBytes(); 59 | 60 | MessageType messageType = (MessageType)messageTypeBytes[0]; 61 | 62 | switch (messageType) 63 | { 64 | case MessageType.Get: 65 | Get(); 66 | break; 67 | 68 | case MessageType.Update: 69 | Update(); 70 | break; 71 | 72 | case MessageType.Delete: 73 | Delete(); 74 | break; 75 | 76 | case MessageType.StartTransaction: 77 | StartTransaction(); 78 | break; 79 | 80 | case MessageType.Commit: 81 | Commit(); 82 | break; 83 | 84 | case MessageType.Rollback: 85 | Rollback(); 86 | break; 87 | 88 | case MessageType.TransactionGet: 89 | TransactionGet(); 90 | break; 91 | 92 | case MessageType.TransactionUpdate: 93 | TransactionUpdate(); 94 | break; 95 | 96 | case MessageType.TransactionDelete: 97 | TransactionDelete(); 98 | break; 99 | 100 | default: 101 | throw new ArgumentOutOfRangeException(); 102 | } 103 | } 104 | 105 | private void TransactionDelete() 106 | { 107 | byte[] transactionIdBytes = m_serverSocket.ReceiveFrameBytes(); 108 | 109 | int transactionId = BitConverter.ToInt32(transactionIdBytes, 0); 110 | 111 | byte[] documentIdBytes = m_serverSocket.ReceiveFrameBytes(); 112 | 113 | DocumentId documentId = new DocumentId(documentIdBytes); 114 | 115 | try 116 | { 117 | m_db.TransactionDelete(transactionId, documentId); 118 | 119 | // sending success 120 | m_serverSocket.SendFrame(Protocol.Success); 121 | } 122 | catch (TransactionNotExistException ex) 123 | { 124 | m_serverSocket.SendMoreFrame(Protocol.Failed).SendFrame("Transaction doesn't exist"); 125 | } 126 | catch (DocumentLockedException) 127 | { 128 | m_serverSocket.SendMoreFrame(Protocol.Failed).SendFrame("Document locked by another transaction"); 129 | } 130 | } 131 | 132 | private void TransactionUpdate() 133 | { 134 | byte[] transactionIdBytes = m_serverSocket.ReceiveFrameBytes(); 135 | 136 | int transactionId = BitConverter.ToInt32(transactionIdBytes, 0); 137 | 138 | byte[] documentIdBytes = m_serverSocket.ReceiveFrameBytes(); 139 | 140 | DocumentId documentId = new DocumentId(documentIdBytes); 141 | 142 | byte[] blob = m_serverSocket.ReceiveFrameBytes(); 143 | 144 | try 145 | { 146 | m_db.TransactionUpdate(transactionId, documentId, blob); 147 | 148 | // sending success 149 | m_serverSocket.SendFrame(Protocol.Success); 150 | } 151 | catch (TransactionNotExistException ex) 152 | { 153 | m_serverSocket.SendMoreFrame(Protocol.Failed).SendFrame("Transaction doesn't exist"); 154 | } 155 | catch (DocumentLockedException) 156 | { 157 | m_serverSocket.SendMoreFrame(Protocol.Failed).SendFrame("Document locked by another transaction"); 158 | } 159 | } 160 | 161 | private void TransactionGet() 162 | { 163 | byte[] transactionIdBytes = m_serverSocket.ReceiveFrameBytes(); 164 | 165 | int transactionId = BitConverter.ToInt32(transactionIdBytes, 0); 166 | 167 | byte[] documentIdBytes = m_serverSocket.ReceiveFrameBytes(); 168 | 169 | DocumentId documentId = new DocumentId(documentIdBytes); 170 | 171 | try 172 | { 173 | byte[] blob = m_db.TransactionGet(transactionId, documentId); 174 | 175 | if (blob == null) 176 | { 177 | blob = new byte[0]; 178 | } 179 | 180 | m_serverSocket.SendMoreFrame(Protocol.Success).SendFrame(blob); 181 | } 182 | catch (TransactionNotExistException ex) 183 | { 184 | m_serverSocket.SendMoreFrame(Protocol.Failed).SendFrame("Transaction doesn't exist"); 185 | } 186 | } 187 | 188 | private void Rollback() 189 | { 190 | byte[] transactionIdBytes = m_serverSocket.ReceiveFrameBytes(); 191 | 192 | int transactionId = BitConverter.ToInt32(transactionIdBytes, 0); 193 | 194 | try 195 | { 196 | m_db.RollbackTransaction(transactionId); 197 | m_serverSocket.SendFrame(Protocol.Success); 198 | } 199 | catch (TransactionNotExistException ex) 200 | { 201 | m_serverSocket.SendMoreFrame(Protocol.Failed).SendFrame("Transaction doesn't exist"); 202 | } 203 | } 204 | 205 | private void Commit() 206 | { 207 | byte[] transactionIdBytes = m_serverSocket.ReceiveFrameBytes(); 208 | 209 | int transactionId = BitConverter.ToInt32(transactionIdBytes, 0); 210 | 211 | try 212 | { 213 | m_db.CommitTransaction(transactionId); 214 | m_serverSocket.SendFrame(Protocol.Success); 215 | } 216 | catch (TransactionNotExistException ex) 217 | { 218 | m_serverSocket.SendMoreFrame(Protocol.Failed).SendFrame("Transaction doesn't exist"); 219 | } 220 | } 221 | 222 | private void StartTransaction() 223 | { 224 | int transactionId = m_db.StartTransaction(); 225 | 226 | m_serverSocket.SendMoreFrame(Protocol.Success).SendFrame(BitConverter.GetBytes(transactionId)); 227 | } 228 | 229 | private void Update() 230 | { 231 | byte[] documentIdBytes = m_serverSocket.ReceiveFrameBytes(); 232 | 233 | DocumentId documentId = new DocumentId(documentIdBytes); 234 | 235 | byte[] blob = m_serverSocket.ReceiveFrameBytes(); 236 | 237 | try 238 | { 239 | m_db.Update(documentId, blob); 240 | 241 | // sending success 242 | m_serverSocket.SendFrame(Protocol.Success); 243 | } 244 | catch (DocumentLockedException) 245 | { 246 | m_serverSocket.SendMoreFrame(Protocol.Failed).SendFrame("Document locked by another transaction"); 247 | } 248 | } 249 | 250 | private void Delete() 251 | { 252 | byte[] documentIdBytes = m_serverSocket.ReceiveFrameBytes(); 253 | 254 | DocumentId documentId = new DocumentId(documentIdBytes); 255 | 256 | try 257 | { 258 | m_db.Delete(documentId); 259 | 260 | // sending success 261 | m_serverSocket.SendFrame(Protocol.Success); 262 | } 263 | catch (DocumentLockedException) 264 | { 265 | m_serverSocket.SendMoreFrame(Protocol.Failed).SendFrame("Document locked by another transaction"); 266 | } 267 | } 268 | 269 | private void Get() 270 | { 271 | byte[] documentIdBytes = m_serverSocket.ReceiveFrameBytes(); 272 | 273 | DocumentId documentId = new DocumentId(documentIdBytes); 274 | 275 | byte[] blob = m_db.Get(documentId); 276 | 277 | if (blob == null) 278 | { 279 | blob = new byte[0]; 280 | } 281 | 282 | m_serverSocket.SendMoreFrame(Protocol.Success).SendFrame(blob); 283 | } 284 | } 285 | } -------------------------------------------------------------------------------- /src/SharpDB.Server/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Topshelf; 3 | 4 | namespace SharpDB.Server 5 | { 6 | internal class Program 7 | { 8 | private static int Main(string[] args) 9 | { 10 | string databaseFile = "default"; 11 | int port = 5999; 12 | return (int)HostFactory.Run(x => 13 | { 14 | x.UseAssemblyInfoForServiceInfo(); 15 | 16 | x.Service(settings => new ServerService(databaseFile, port), s => 17 | { 18 | s.BeforeStartingService(_ => Console.WriteLine("BeforeStart")); 19 | s.BeforeStoppingService(_ => Console.WriteLine("BeforeStop")); 20 | }); 21 | 22 | x.SetStartTimeout(TimeSpan.FromSeconds(10)); 23 | x.SetStopTimeout(TimeSpan.FromSeconds(10)); 24 | 25 | x.AddCommandLineDefinition("name", f => databaseFile = f); 26 | x.AddCommandLineDefinition("port", p => port = Convert.ToInt32(p)); 27 | 28 | x.OnException((exception) => 29 | { 30 | Console.WriteLine("Exception thrown - " + exception.Message); 31 | }); 32 | }); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/SharpDB.Server/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | FileSystem 8 | Release 9 | netstandard2.0 10 | bin\Release\PublishOutput 11 | 12 | -------------------------------------------------------------------------------- /src/SharpDB.Server/ServerService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Common.Logging; 7 | using Common.Logging.Simple; 8 | using NetMQ; 9 | using SharpDB.Engine; 10 | using SharpDB.Engine.Cache; 11 | using SharpDB.Engine.IO; 12 | using Topshelf; 13 | 14 | namespace SharpDB.Server 15 | { 16 | #if NET40 17 | 18 | public class ServerService 19 | #else 20 | 21 | public class ServerService : ServiceControl 22 | #endif 23 | { 24 | private readonly string m_name; 25 | private readonly int m_port; 26 | private KeyValueDatabase m_db; 27 | private Network.Server m_server; 28 | private ILog m_log; 29 | private Task m_task; 30 | 31 | public ServerService(string name, int port) 32 | { 33 | m_name = name; 34 | m_port = port; 35 | } 36 | 37 | #if NET40 38 | 39 | public bool Start() 40 | #else 41 | 42 | public bool Start(HostControl hostControl) 43 | #endif 44 | { 45 | m_log = LogManager.GetLogger(this.GetType()); 46 | 47 | m_log.InfoFormat("Starting SharpDB..."); 48 | m_log.InfoFormat("Database Name: {0}", m_name); 49 | 50 | m_db = new KeyValueDatabase(filename => new DatabaseFileReader(filename), filename => new DatabaseFileWriter(filename), 51 | filename => new MemoryCacheProvider(filename)); 52 | m_db.FileName = m_name + ".sdb"; 53 | m_db.Start(); 54 | 55 | m_server = new Network.Server(m_db, string.Format("tcp://*:{0}", m_port)); 56 | 57 | m_task = Task.Factory.StartNew(m_server.Start); 58 | return true; 59 | } 60 | 61 | #if NET40 62 | 63 | public bool Stop() 64 | #else 65 | 66 | public bool Stop(HostControl hostControl) 67 | #endif 68 | { 69 | m_log.InfoFormat("Stopping SharpDB..."); 70 | 71 | m_server.Stop(); 72 | m_task.Wait(); 73 | 74 | m_db.Stop(); 75 | 76 | m_server = null; 77 | m_db = null; 78 | return true; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /src/SharpDB.Server/SharpDB.Server.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | netcoreapp2.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 4.1.0.117-develop 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/SharpDB.Shared/MessageType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SharpDB.Shared 7 | { 8 | public enum MessageType : byte 9 | { 10 | Get, Update,Delete, StartTransaction, Commit, Rollback, TransactionGet, TransactionUpdate, TransactionDelete 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/SharpDB.Shared/Protocol.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SharpDB.Shared 7 | { 8 | public static class Protocol 9 | { 10 | public static readonly byte[] Success = BitConverter.GetBytes(true); 11 | public static readonly byte[] Failed = BitConverter.GetBytes(false); 12 | 13 | 14 | public static readonly byte[] Get = new byte[] { (byte)MessageType.Get}; 15 | public static readonly byte[] Update = new byte[] { (byte)MessageType.Update }; 16 | public static readonly byte[] Delete = new byte[] { (byte)MessageType.Delete }; 17 | public static readonly byte[] StartTransaction = new byte[] { (byte)MessageType.StartTransaction }; 18 | public static readonly byte[] Commit = new byte[] { (byte)MessageType.Commit }; 19 | public static readonly byte[] Rollback = new byte[] { (byte)MessageType.Rollback }; 20 | public static readonly byte[] TransactionGet = new byte[] { (byte)MessageType.TransactionGet }; 21 | public static readonly byte[] TransactionUpdate = new byte[] { (byte)MessageType.TransactionUpdate }; 22 | public static readonly byte[] TransactionDelete = new byte[] { (byte)MessageType.TransactionDelete }; 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/SharpDB.Shared/SharpDB.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net40;netstandard2.0 4 | 5 | --------------------------------------------------------------------------------