diff --git a/src/LightningDB.Tests/CursorTests.cs b/src/LightningDB.Tests/CursorTests.cs index 3e95de3..1018185 100644 --- a/src/LightningDB.Tests/CursorTests.cs +++ b/src/LightningDB.Tests/CursorTests.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using LightningDB.Native; using Xunit; using static System.Text.Encoding; @@ -11,242 +8,359 @@ namespace LightningDB.Tests [Collection("SharedFileSystem")] public class CursorTests : IDisposable { - private LightningEnvironment _env; - private LightningTransaction _txn; - private LightningDatabase _db; + private readonly LightningEnvironment _env; public CursorTests(SharedFileSystem fileSystem) { var path = fileSystem.CreateNewDirectoryForTest(); - - _env = new LightningEnvironment(path); _env.Open(); - - _txn = _env.BeginTransaction(); } public void Dispose() { - _txn.Dispose(); _env.Dispose(); } - private void PopulateCursorValues() + private static byte[][] PopulateCursorValues(LightningCursor cursor, int count = 5, string keyPrefix = "key") { - using (var cur = _txn.CreateCursor(_db)) - { - var keys = Enumerable.Range(1, 5) - .Select(i => UTF8.GetBytes("key" + i)) - .ToArray(); + var keys = Enumerable.Range(1, count) + .Select(i => UTF8.GetBytes(keyPrefix + i)) + .ToArray(); - foreach (var k in keys) - { - cur.Put(k, k, CursorPutOptions.None); - } + foreach (var k in keys) + { + var result = cursor.Put(k, k, CursorPutOptions.None); + Assert.Equal(MDBResultCode.Success, result); } + + return keys; + } + + private static byte[][] PopulateMultipleCursorValues(LightningCursor cursor, string key = "TestKey") + { + var values = Enumerable.Range(1, 5).Select(BitConverter.GetBytes).ToArray(); + var result = cursor.Put(UTF8.GetBytes(key), values); + Assert.Equal(MDBResultCode.Success, result); + var notDuplicate = values[0]; + result = cursor.Put(notDuplicate, notDuplicate, CursorPutOptions.NoDuplicateData); + Assert.Equal(MDBResultCode.Success, result); + return values; } [Fact] public void CursorShouldBeCreated() { - _db = _txn.OpenDatabase(configuration: new DatabaseConfiguration { Flags = DatabaseOpenFlags.Create }); - _txn.CreateCursor(_db).Dispose(); + _env.RunCursorScenario((tx, db, c) => Assert.NotNull(c)); } [Fact] public void CursorShouldPutValues() { - _db = _txn.OpenDatabase(configuration: new DatabaseConfiguration { Flags = DatabaseOpenFlags.Create }); - PopulateCursorValues(); + _env.RunCursorScenario((tx, db, c) => + { + PopulateCursorValues(c); + c.Dispose(); + //TODO evaluate how not to require this Dispose on Linux (test only fails there) + var result = tx.Commit(); + Assert.Equal(MDBResultCode.Success, result); + }); } [Fact] - public void CursorShouldMoveToLast() + public void CursorShouldSetSpanKey() { - _db = _txn.OpenDatabase(configuration: new DatabaseConfiguration { Flags = DatabaseOpenFlags.Create }); - PopulateCursorValues(); - - using (var cur = _txn.CreateCursor(_db)) + _env.RunCursorScenario((tx, db, c) => { - Assert.True(cur.MoveToLast()); - - var lastKey = UTF8.GetString(cur.Current.Key.CopyToNewArray()); - var lastValue = UTF8.GetString(cur.Current.Value.CopyToNewArray()); + var keys = PopulateCursorValues(c); + var firstKey = keys.First(); + var result = c.Set(firstKey.AsSpan()); + Assert.Equal(MDBResultCode.Success, result); + var current = c.GetCurrent(); + Assert.Equal(firstKey, current.key.CopyToNewArray()); + }); + } - Assert.Equal("key5", lastKey); - Assert.Equal("key5", lastValue); - } + [Fact] + public void CursorShouldMoveToLast() + { + _env.RunCursorScenario((tx, db, c) => + { + var keys = PopulateCursorValues(c); + var lastKey = keys.Last(); + var result = c.Last(); + Assert.Equal(MDBResultCode.Success, result); + var current = c.GetCurrent(); + Assert.Equal(lastKey, current.key.CopyToNewArray()); + }); } [Fact] public void CursorShouldMoveToFirst() { - _db = _txn.OpenDatabase(configuration: new DatabaseConfiguration { Flags = DatabaseOpenFlags.Create }); - PopulateCursorValues(); - - using (var cur = _txn.CreateCursor(_db)) + _env.RunCursorScenario((tx, db, c) => { - cur.MoveToFirst(); - - var lastKey = UTF8.GetString(cur.Current.Key.CopyToNewArray()); - var lastValue = UTF8.GetString(cur.Current.Value.CopyToNewArray()); - - Assert.Equal("key1", lastKey); - Assert.Equal("key1", lastValue); - } + var keys = PopulateCursorValues(c); + var firstKey = keys.First(); + var result = c.First(); + Assert.Equal(MDBResultCode.Success, result); + var current = c.GetCurrent(); + Assert.Equal(firstKey, current.key.CopyToNewArray()); + }); } [Fact] public void ShouldIterateThroughCursor() { - _db = _txn.OpenDatabase(configuration: new DatabaseConfiguration { Flags = DatabaseOpenFlags.Create }); - PopulateCursorValues(); - - using (var cur = _txn.CreateCursor(_db)) + _env.RunCursorScenario((tx, db, c) => { - var i = 0; - - while (cur.MoveNext()) + var keys = PopulateCursorValues(c); + using var c2 = tx.CreateCursor(db); + var items = c2.AsEnumerable().Select((x, i) => (x, i)).ToList(); + foreach (var (x, i) in items) { - var key = UTF8.GetString(cur.Current.Key.CopyToNewArray()); - var value = UTF8.GetString(cur.Current.Value.CopyToNewArray()); - - var name = "key" + ++i; + Assert.Equal(keys[i], x.Item1.CopyToNewArray()); + } - Assert.Equal(name, key); - Assert.Equal(name, value); - } - Assert.NotEqual(0, i); - } + Assert.Equal(keys.Length, items.Count); + }); } [Fact] public void CursorShouldDeleteElements() { - _db = _txn.OpenDatabase(configuration: new DatabaseConfiguration { Flags = DatabaseOpenFlags.Create }); - PopulateCursorValues(); - - using (var cursor = _txn.CreateCursor(_db)) + _env.RunCursorScenario((tx, db, c) => { - for (int i = 0; i < 2; ++i) + var keys = PopulateCursorValues(c).Take(2).ToArray(); + for (var i = 0; i < 2; ++i) { - cursor.MoveNext(); - cursor.Delete(); + c.Next(); + c.Delete(); } - } - using(var cursor = _txn.CreateCursor(_db)) - { - var foundDeleted = cursor.Select(x => UTF8.GetString(x.Key.CopyToNewArray())) - .Any(x => x == "key1" || x == "key2"); - Assert.False(foundDeleted); - } + + using var c2 = tx.CreateCursor(db); + Assert.DoesNotContain(c2.AsEnumerable(), x => + keys.Any(k => x.Item1.CopyToNewArray() == k)); + }); } [Fact] - public void ShouldIterateThroughCursorByEnumerator() + public void ShouldPutMultiple() { - _db = _txn.OpenDatabase(configuration: new DatabaseConfiguration { Flags = DatabaseOpenFlags.Create }); - PopulateCursorValues(); + _env.RunCursorScenario((tx, db, c) => { PopulateMultipleCursorValues(c); }, + DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); + } - var i = 0; - using(var cursor = _txn.CreateCursor(_db)) + [Fact] + public void ShouldGetMultiple() + { + _env.RunCursorScenario((tx, db, c) => { - foreach (var pair in cursor) - { - var name = "key" + ++i; - Assert.Equal(name, UTF8.GetString(pair.Key.CopyToNewArray())); - Assert.Equal(name, UTF8.GetString(pair.Value.CopyToNewArray())); - } - } + var key = UTF8.GetBytes("TestKey"); + var keys = PopulateMultipleCursorValues(c); + c.Set(key); + c.NextDuplicate(); + var (resultCode, _, value) = c.GetMultiple(); + Assert.Equal(MDBResultCode.Success, resultCode); + Assert.Equal(keys, value.CopyToNewArray().Split(sizeof(int)).ToArray()); + }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); } [Fact] - public void ShouldPutMultiple() + public void ShouldGetNextMultiple() { - _db = _txn.OpenDatabase(configuration: new DatabaseConfiguration { Flags = DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create }); - - var values = new[] { 1, 2, 3, 4, 5 }.Select(BitConverter.GetBytes).ToArray(); - using (var cur = _txn.CreateCursor(_db)) - cur.PutMultiple(UTF8.GetBytes("TestKey"), values); - - var pairs = new KeyValuePair[values.Length]; - - using (var cur = _txn.CreateCursor(_db)) + _env.RunCursorScenario((tx, db, c) => { - for (var i = 0; i < values.Length; i++) - { - cur.MoveNextDuplicate(); - pairs[i] = cur.Current; - } - } - - Assert.Equal(values, pairs.Select(x => x.Value.CopyToNewArray()).ToArray()); + var key = UTF8.GetBytes("TestKey"); + var keys = PopulateMultipleCursorValues(c); + c.Set(key); + var (resultCode, _, value) = c.NextMultiple(); + Assert.Equal(MDBResultCode.Success, resultCode); + Assert.Equal(keys, value.CopyToNewArray().Split(sizeof(int)).ToArray()); + }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); } [Fact] - public void ShouldGetMultiple() + public void ShouldAdvanceKeyToClosestWhenKeyNotFound() { - _db = _txn.OpenDatabase(configuration: new DatabaseConfiguration { Flags = DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create}); - - var original = new[] { 1, 2, 3, 4, 5 }; - var originalBytes = original.Select(BitConverter.GetBytes).ToArray(); - using (var cur = _txn.CreateCursor(_db)) - cur.PutMultiple(UTF8.GetBytes("TestKey"), originalBytes); - - using (var cur = _txn.CreateCursor(_db)) + _env.RunCursorScenario((tx, db, c) => { - cur.MoveNext(); - cur.GetMultiple(); - var resultPair = cur.Current; - Assert.Equal("TestKey", UTF8.GetString(resultPair.Key.CopyToNewArray())); - var result = resultPair.Value.CopyToNewArray().Split(sizeof(int)) - .Select(x => BitConverter.ToInt32(x.ToArray(), 0)).ToArray(); - Assert.Equal(original, result); - } + var expected = PopulateCursorValues(c).First(); + var result = c.Set(UTF8.GetBytes("key")); + Assert.Equal(MDBResultCode.NotFound, result); + var (_, key, _) = c.GetCurrent(); + Assert.Equal(expected, key.CopyToNewArray()); + }); } [Fact] - public void ShouldMoveNextMultiple() + public void ShouldSetKeyAndGet() { - _db = _txn.OpenDatabase(configuration: new DatabaseConfiguration { Flags = DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create }); - - var original = new[] { 1, 2, 3, 4, 5 }; - var originalBytes = original.Select(BitConverter.GetBytes).ToArray(); - using (var cur = _txn.CreateCursor(_db)) - cur.PutMultiple(UTF8.GetBytes("TestKey"), originalBytes); - - using (var cur = _txn.CreateCursor(_db)) + _env.RunCursorScenario((tx, db, c) => { - cur.MoveNextMultiple(); - var resultPair = cur.Current; - Assert.Equal("TestKey", UTF8.GetString(resultPair.Key.CopyToNewArray())); - var result = resultPair.Value.CopyToNewArray().Split(sizeof(int)) - .Select(x => BitConverter.ToInt32(x.ToArray(), 0)).ToArray(); - Assert.Equal(original, result); - } + var expected = PopulateCursorValues(c).ElementAt(2); + var result = c.SetKey(expected); + Assert.Equal(MDBResultCode.Success, result.resultCode); + Assert.Equal(expected, result.key.CopyToNewArray()); + }); } - + [Fact] - public void ShouldAdvanceKeyToClosestWhenKeyNotFound() + public void ShouldSetKeyAndGetWithSpan() { - var keys = new List + _env.RunCursorScenario((tx, db, c) => { - "one", - "two/1", - "two/2", - "two/3" - }; - _db = _txn.OpenDatabase(configuration: new DatabaseConfiguration { Flags = DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create }); - foreach (var key in keys) + var expected = PopulateCursorValues(c).ElementAt(2); + var result = c.SetKey(expected.AsSpan()); + Assert.Equal(MDBResultCode.Success, result.resultCode); + Assert.Equal(expected, result.key.CopyToNewArray()); + }); + } + + [Fact] + public void ShouldGetBoth() + { + _env.RunCursorScenario((tx, db, c) => { - _txn.Put(_db, Encoding.UTF8.GetBytes(key), null); - } + var expected = PopulateCursorValues(c).ElementAt(2); + var result = c.GetBoth(expected, expected); + Assert.Equal(MDBResultCode.Success, result); + }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); + } + + [Fact] + public void ShouldGetBothWithSpan() + { + _env.RunCursorScenario((tx, db, c) => + { + var expected = PopulateCursorValues(c).ElementAt(2); + var expectedSpan = expected.AsSpan(); + var result = c.GetBoth(expectedSpan, expectedSpan); + Assert.Equal(MDBResultCode.Success, result); + }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); + } + + [Fact] + public void ShouldMoveToPrevious() + { + _env.RunCursorScenario((tx, db, c) => + { + var expected = PopulateCursorValues(c).ElementAt(2); + var expectedSpan = expected.AsSpan(); + c.GetBoth(expectedSpan, expectedSpan); + var result = c.Previous(); + Assert.Equal(MDBResultCode.Success, result); + }); + } + + [Fact] + public void ShouldSetRangeWithSpan() + { + _env.RunCursorScenario((tx, db, c) => + { + var values = PopulateCursorValues(c); + var firstAfter = values[0].AsSpan(); + var result = c.SetRange(firstAfter); + Assert.Equal(MDBResultCode.Success, result); + var current = c.GetCurrent(); + Assert.Equal(values[0], current.value.CopyToNewArray()); + }); + } + + [Fact] + public void ShouldGetBothRange() + { + _env.RunCursorScenario((tx, db, c) => + { + var key = UTF8.GetBytes("TestKey"); + var values = PopulateMultipleCursorValues(c); + var result = c.GetBothRange(key, values[1]); + Assert.Equal(MDBResultCode.Success, result); + var current = c.GetCurrent(); + Assert.Equal(values[1], current.value.CopyToNewArray()); + }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); + } + + [Fact] + public void ShouldGetBothRangeWithSpan() + { + _env.RunCursorScenario((tx, db, c) => + { + var key = UTF8.GetBytes("TestKey").AsSpan(); + var values = PopulateMultipleCursorValues(c); + var result = c.GetBothRange(key, values[1].AsSpan()); + Assert.Equal(MDBResultCode.Success, result); + var current = c.GetCurrent(); + Assert.Equal(values[1], current.value.CopyToNewArray()); + }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); + } + + [Fact] + public void ShouldMoveToFirstDuplicate() + { + _env.RunCursorScenario((tx, db, c) => + { + var key = UTF8.GetBytes("TestKey"); + var values = PopulateMultipleCursorValues(c); + var result = c.GetBothRange(key, values[1]); + Assert.Equal(MDBResultCode.Success, result); + result = c.FirstDuplicate(); + Assert.Equal(MDBResultCode.Success, result); + var current = c.GetCurrent(); + Assert.Equal(values[0], current.value.CopyToNewArray()); + }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); + } + + [Fact] + public void ShouldMoveToLastDuplicate() + { + _env.RunCursorScenario((tx, db, c) => + { + var key = UTF8.GetBytes("TestKey"); + var values = PopulateMultipleCursorValues(c); + c.Set(key); + var result = c.LastDuplicate(); + Assert.Equal(MDBResultCode.Success, result); + var current = c.GetCurrent(); + Assert.Equal(values[4], current.value.CopyToNewArray()); + }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); + } + + [Fact] + public void ShouldMoveToNextNoDuplicate() + { + _env.RunCursorScenario((tx, db, c) => + { + var values = PopulateMultipleCursorValues(c); + var result = c.NextNoDuplicate(); + Assert.Equal(MDBResultCode.Success, result); + var current = c.GetCurrent(); + Assert.Equal(values[0], current.value.CopyToNewArray()); + }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); + } + + [Fact] + public void ShouldRenewSameTransaction() + { + _env.RunCursorScenario((tx, db, c) => + { + var result = c.Renew(); + Assert.Equal(MDBResultCode.Success, result); + }, transactionFlags: TransactionBeginFlags.ReadOnly); + } - using var cursor = _txn.CreateCursor(_db); - cursor.MoveTo(UTF8.GetBytes("two")); - var result = cursor.GetCurrent().Key.CopyToNewArray(); - Assert.Equal("two/1", UTF8.GetString(result)); + [Fact] + public void ShouldDeleteDuplicates() + { + _env.RunCursorScenario((tx, db, c) => + { + var key = UTF8.GetBytes("TestKey"); + PopulateMultipleCursorValues(c); + c.Set(key); + c.DeleteDuplicateData(); + var result = c.Set(key); + Assert.Equal(MDBResultCode.NotFound, result); + }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); } } -} +} \ No newline at end of file diff --git a/src/LightningDB.Tests/DatabaseIOTests.cs b/src/LightningDB.Tests/DatabaseIOTests.cs index a1cc604..7c1563b 100644 --- a/src/LightningDB.Tests/DatabaseIOTests.cs +++ b/src/LightningDB.Tests/DatabaseIOTests.cs @@ -44,7 +44,7 @@ public void DatabaseGetShouldNotThrowExceptions() } [Fact] - public void DatabaseInsertedValueShouldBeRetrivedThen() + public void DatabaseInsertedValueShouldBeRetrievedThen() { var key = "key"; var value = "value"; diff --git a/src/LightningDB.Tests/DatabaseTests.cs b/src/LightningDB.Tests/DatabaseTests.cs index 1c0b540..8ddd360 100644 --- a/src/LightningDB.Tests/DatabaseTests.cs +++ b/src/LightningDB.Tests/DatabaseTests.cs @@ -88,8 +88,8 @@ public void NamedDatabaseNameExistsInMaster() var db = tx.OpenDatabase(); using (var cursor = tx.CreateCursor(db)) { - cursor.MoveNext(); - Assert.Equal("customdb", UTF8.GetString(cursor.Current.Key.CopyToNewArray())); + cursor.Next(); + Assert.Equal("customdb", UTF8.GetString(cursor.GetCurrent().key.CopyToNewArray())); } } } @@ -166,7 +166,7 @@ public void TruncatingTheDatabase() db = _txn.OpenDatabase(); var result = _txn.Get(db, UTF8.GetBytes("hello")); - Assert.Null(result); + Assert.Equal(MDBResultCode.NotFound, result.resultCode); } } } diff --git a/src/LightningDB.Tests/HelperTests.cs b/src/LightningDB.Tests/HelperTests.cs deleted file mode 100644 index 467b54a..0000000 --- a/src/LightningDB.Tests/HelperTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Xunit; -using System.Text; - -namespace LightningDB.Tests -{ - public class HelperTests - { - [Fact] - public void ByteArrayStartsWith() - { - var message = "Hello World!"; - var hello = "Hello"; - var messageBytes = Encoding.UTF8.GetBytes(message); - var helloBytes = Encoding.UTF8.GetBytes(hello); - Assert.True(messageBytes.StartsWith(helloBytes)); - } - - [Fact] - public void ByteArrayDoesntStartWith() - { - var message = "Hello World!"; - var hello = "hello"; - var messageBytes = Encoding.UTF8.GetBytes(message); - var heloBytes = Encoding.UTF8.GetBytes(hello); - Assert.False(messageBytes.StartsWith(heloBytes)); - } - - [Fact] - public void ByteArrayContains() - { - var message = "Hello World!"; - var world = "World!"; - var messageBytes = Encoding.UTF8.GetBytes(message); - var worldBytes = Encoding.UTF8.GetBytes(world); - Assert.False(messageBytes.StartsWith(worldBytes)); - } - } -} \ No newline at end of file diff --git a/src/LightningDB.Tests/ProfilingTests.cs b/src/LightningDB.Tests/ProfilingTests.cs deleted file mode 100644 index 7f8d013..0000000 --- a/src/LightningDB.Tests/ProfilingTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using Xunit; - -namespace LightningDB.Tests -{ - [Collection("SharedFileSystem")] - public class ProfilingTests : IDisposable - { - private readonly LightningEnvironment _env; - - public ProfilingTests(SharedFileSystem fileSystem) - { - _env = new LightningEnvironment(fileSystem.CreateNewDirectoryForTest()); - _env.MaxDatabases = 2; - _env.Open(); - } - - [Fact(Skip = "Can come back to profiling tests later"), Trait("prof", "explicit")] - public void DoStuff() - { - Console.WriteLine("Take a baseline snapshot then press enter."); - Console.ReadLine(); - DoStuffHelper(); - Console.WriteLine("Take another snapshot for comparison and press enter."); - Console.ReadLine(); - } - - private void DoStuffHelper() - { - using (var tx = _env.BeginTransaction()) - using (var db = tx.OpenDatabase("test", new DatabaseConfiguration { Flags = DatabaseOpenFlags.Create })) - { - for (var i = 0; i < 100; ++i) - { - tx.Put(db, BitConverter.GetBytes(i), BitConverter.GetBytes(i)); - } - tx.Commit(); - } - using (var tx = _env.BeginTransaction()) - using (var db = tx.OpenDatabase("test")) - { - tx.Get(db, BitConverter.GetBytes(1)); - using (var cursor = tx.CreateCursor(db)) - { - while (cursor.MoveNext()) - { - var current = cursor.Current; - } - } - } - } - - public void Dispose() - { - _env.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/LightningDB.Tests/TestHelperExtensions.cs b/src/LightningDB.Tests/TestHelperExtensions.cs index 9e394be..d5189e5 100644 --- a/src/LightningDB.Tests/TestHelperExtensions.cs +++ b/src/LightningDB.Tests/TestHelperExtensions.cs @@ -1,21 +1,22 @@ -using System.Linq; +using System; +using System.Linq; using System.Collections.Generic; namespace LightningDB.Tests { public static class TestHelperExtensions { - public static void Put(this LightningTransaction tx, LightningDatabase db, string key, string value) + public static MDBResultCode Put(this LightningTransaction tx, LightningDatabase db, string key, string value) { var enc = System.Text.Encoding.UTF8; - tx.Put(db, enc.GetBytes(key), enc.GetBytes(value)); + return tx.Put(db, enc.GetBytes(key), enc.GetBytes(value)); } public static string Get(this LightningTransaction tx, LightningDatabase db, string key) { var enc = System.Text.Encoding.UTF8; var result = tx.Get(db, enc.GetBytes(key)); - return enc.GetString(result); + return enc.GetString(result.value.CopyToNewArray()); } public static void Delete(this LightningTransaction tx, LightningDatabase db, string key) @@ -46,5 +47,24 @@ public static IEnumerable> Split(this IEnumerable list, int .GroupBy(x => x.Index / parts) .Select(x => x.Select(v => v.Value)); } + + public static void RunCursorScenario(this LightningEnvironment env, + Action scenario, + DatabaseOpenFlags flags = DatabaseOpenFlags.Create, TransactionBeginFlags transactionFlags = TransactionBeginFlags.None) + { + using var tx = env.BeginTransaction(transactionFlags); + using var db = tx.OpenDatabase(configuration: new DatabaseConfiguration { Flags = flags }); + using var cursor = tx.CreateCursor(db); + scenario(tx, db, cursor); + } + + public static void RunTransactionScenario(this LightningEnvironment env, + Action scenario, + DatabaseOpenFlags flags = DatabaseOpenFlags.Create, TransactionBeginFlags transactionFlags = TransactionBeginFlags.None) + { + using var tx = env.BeginTransaction(transactionFlags); + using var db = tx.OpenDatabase(configuration: new DatabaseConfiguration { Flags = flags }); + scenario(tx, db); + } } } \ No newline at end of file diff --git a/src/LightningDB.Tests/TransactionTests.cs b/src/LightningDB.Tests/TransactionTests.cs index b402d68..9f896a4 100644 --- a/src/LightningDB.Tests/TransactionTests.cs +++ b/src/LightningDB.Tests/TransactionTests.cs @@ -1,18 +1,17 @@ using System; using System.Linq; using System.Collections.Generic; -using LightningDB.Native; using Xunit; using System.Runtime.InteropServices; namespace LightningDB.Tests { [Collection("SharedFileSystem")] - public class TransactionDupFixedTests : IDisposable + public class TransactionTests : IDisposable { - private LightningEnvironment _env; + private readonly LightningEnvironment _env; - public TransactionDupFixedTests(SharedFileSystem fileSystem) + public TransactionTests(SharedFileSystem fileSystem) { var path = fileSystem.CreateNewDirectoryForTest(); _env = new LightningEnvironment(path); @@ -23,167 +22,143 @@ public void Dispose() { _env.Dispose(); } - - + [Fact] - public void CanCountTransactionEntries() + public void CanDeletePreviouslyCommittedWithMultipleValuesByPassingNullForValue() { - LightningDatabase db; - - using (var openDbTxn = _env.BeginTransaction()) - { - db = openDbTxn.OpenDatabase(); - } - - var key = MemoryMarshal.Cast("abcd"); - - using(var writeTxn = _env.BeginTransaction()) + _env.RunTransactionScenario((tx, db) => { - writeTxn.Put(db, key, MemoryMarshal.Cast("Value1")); - writeTxn.Put(db, key, MemoryMarshal.Cast("Value2")); - writeTxn.Commit(); - } + var key = MemoryMarshal.Cast("abcd"); - using (var delTxn = _env.BeginTransaction()) - { - delTxn.Delete(db, key, null);//should not throw - delTxn.Commit(); - } - } - } - - - [Collection("SharedFileSystem")] - public class TransactionTests : IDisposable - { - private LightningEnvironment _env; - - public TransactionTests(SharedFileSystem fileSystem) - { - var path = fileSystem.CreateNewDirectoryForTest(); - _env = new LightningEnvironment(path); - _env.Open(); - } - - public void Dispose() - { - _env.Dispose(); + tx.Put(db, key, MemoryMarshal.Cast("Value1")); + tx.Put(db, key, MemoryMarshal.Cast("Value2"), PutOptions.AppendData); + tx.Commit(); + tx.Dispose(); + + using var delTxn = _env.BeginTransaction(); + var result = delTxn.Delete(db, key, null); + Assert.Equal(MDBResultCode.Success, result); + result = delTxn.Commit(); + Assert.Equal(MDBResultCode.Success, result); + }, DatabaseOpenFlags.Create | DatabaseOpenFlags.DuplicatesFixed); } [Fact] public void TransactionShouldBeCreated() { - var txn = _env.BeginTransaction(); - - Assert.Equal(LightningTransactionState.Active, txn.State); + _env.RunTransactionScenario((tx, db) => + { + Assert.Equal(LightningTransactionState.Active, tx.State); + }); } [Fact] public void TransactionShouldBeAbortedIfEnvironmentCloses() { - var txn = _env.BeginTransaction(); - - _env.Dispose(); - - Assert.Equal(LightningTransactionState.Aborted, txn.State); + _env.RunTransactionScenario((tx, db) => + { + _env.Dispose(); + Assert.Equal(LightningTransactionState.Aborted, tx.State); + }); } [Fact] public void TransactionShouldChangeStateOnCommit() { - var txn = _env.BeginTransaction(); - - txn.Commit(); - - Assert.Equal(LightningTransactionState.Commited, txn.State); + _env.RunTransactionScenario((tx, db) => + { + tx.Commit(); + Assert.Equal(LightningTransactionState.Commited, tx.State); + }); } [Fact] public void ChildTransactionShouldBeCreated() { - var txn = _env.BeginTransaction(); - - var subTxn = txn.BeginTransaction(); - - Assert.Equal(LightningTransactionState.Active, subTxn.State); + _env.RunTransactionScenario((tx, db) => + { + var subTxn = tx.BeginTransaction(); + Assert.Equal(LightningTransactionState.Active, subTxn.State); + }); } [Fact] public void ResetTransactionAbortedOnDispose() { - var txn = _env.BeginTransaction(TransactionBeginFlags.ReadOnly); - txn.Reset(); - txn.Dispose(); - Assert.Equal(LightningTransactionState.Aborted, txn.State); + _env.RunTransactionScenario((tx, db) => + { + tx.Reset(); + tx.Dispose(); + Assert.Equal(LightningTransactionState.Aborted, tx.State); + }, transactionFlags: TransactionBeginFlags.ReadOnly); } [Fact] public void ChildTransactionShouldBeAbortedIfParentIsAborted() { - var txn = _env.BeginTransaction(); - var child = txn.BeginTransaction(); - - txn.Abort(); - - Assert.Equal(LightningTransactionState.Aborted, child.State); + _env.RunTransactionScenario((tx, db) => + { + var child = tx.BeginTransaction(); + tx.Abort(); + Assert.Equal(LightningTransactionState.Aborted, child.State); + }); } [Fact] public void ChildTransactionShouldBeAbortedIfParentIsCommited() { - var txn = _env.BeginTransaction(); - var child = txn.BeginTransaction(); - - txn.Commit(); - - Assert.Equal(LightningTransactionState.Aborted, child.State); + _env.RunTransactionScenario((tx, db) => + { + var child = tx.BeginTransaction(); + tx.Commit(); + Assert.Equal(LightningTransactionState.Aborted, child.State); + }); } [Fact] public void ChildTransactionShouldBeAbortedIfEnvironmentIsClosed() { - var txn = _env.BeginTransaction(); - var child = txn.BeginTransaction(); - - _env.Dispose(); - - Assert.Equal(LightningTransactionState.Aborted, child.State); + _env.RunTransactionScenario((tx, db) => + { + var child = tx.BeginTransaction(); + _env.Dispose(); + Assert.Equal(LightningTransactionState.Aborted, child.State); + }); } [Fact] public void ReadOnlyTransactionShouldChangeStateOnReset() { - var txn = _env.BeginTransaction(TransactionBeginFlags.ReadOnly); - - txn.Reset(); - - Assert.Equal(LightningTransactionState.Reseted, txn.State); + _env.RunTransactionScenario((tx, db) => + { + tx.Reset(); + Assert.Equal(LightningTransactionState.Reseted, tx.State); + }, transactionFlags: TransactionBeginFlags.ReadOnly); } [Fact] public void ReadOnlyTransactionShouldChangeStateOnRenew() { - var txn = _env.BeginTransaction(TransactionBeginFlags.ReadOnly); - txn.Reset(); - - txn.Renew(); - - Assert.Equal(LightningTransactionState.Active, txn.State); + _env.RunTransactionScenario((tx, db) => + { + tx.Reset(); + tx.Renew(); + Assert.Equal(LightningTransactionState.Active, tx.State); + }, transactionFlags: TransactionBeginFlags.ReadOnly); } [Fact] public void CanCountTransactionEntries() { - var txn = _env.BeginTransaction(); - var db = txn.OpenDatabase(); - - const int entriesCount = 10; - for (var i = 0; i < entriesCount; i++) - txn.Put(db, i.ToString(), i.ToString()); - - var count = txn.GetEntriesCount(db); + _env.RunTransactionScenario((tx, db) => + { + const int entriesCount = 10; + for (var i = 0; i < entriesCount; i++) + tx.Put(db, i.ToString(), i.ToString()); - Assert.Equal(entriesCount, count); + var count = tx.GetEntriesCount(db); + Assert.Equal(entriesCount, count); + }); } [Fact] @@ -216,8 +191,8 @@ public void TransactionShouldSupportCustomComparer() using (var c = txn.CreateCursor(db)) { int order = 0; - while (c.MoveNext()) - Assert.Equal(keysSorted[order++], BitConverter.ToInt32(c.Current.Key.CopyToNewArray(), 0)); + while (c.Next() == MDBResultCode.Success) + Assert.Equal(keysSorted[order++], BitConverter.ToInt32(c.GetCurrent().key.CopyToNewArray(), 0)); } } @@ -237,14 +212,14 @@ public void TransactionShouldSupportCustomDupSorter() Array.Sort(valuesSorted, new Comparison(comparison)); using (var c = txn.CreateCursor(db)) - c.PutMultiple(BitConverter.GetBytes(123), valuesUnsorted.Select(BitConverter.GetBytes).ToArray()); + c.Put(BitConverter.GetBytes(123), valuesUnsorted.Select(BitConverter.GetBytes).ToArray()); using (var c = txn.CreateCursor(db)) { int order = 0; - while (c.MoveNext()) - Assert.Equal(valuesSorted[order++], BitConverter.ToInt32(c.Current.Value.CopyToNewArray(), 0)); + while (c.Next() == MDBResultCode.Success) + Assert.Equal(valuesSorted[order++], BitConverter.ToInt32(c.GetCurrent().value.CopyToNewArray(), 0)); } } } diff --git a/src/LightningDB/CursorOperation.cs b/src/LightningDB/CursorOperation.cs index f7f2ccd..0b707d1 100644 --- a/src/LightningDB/CursorOperation.cs +++ b/src/LightningDB/CursorOperation.cs @@ -93,6 +93,6 @@ public enum CursorOperation /// /// Position at first key greater than or equal to specified key. /// - SetRange + SetRange, } } diff --git a/src/LightningDB/GetResult.cs b/src/LightningDB/GetResult.cs deleted file mode 100644 index edc30c1..0000000 --- a/src/LightningDB/GetResult.cs +++ /dev/null @@ -1,70 +0,0 @@ -namespace LightningDB -{ - /// - /// A struct describing the result of a TryGet operation - /// - public readonly struct GetResult - { - public GetResult(GetResultCode resultCode, int valueLength) - { - ResultCode = resultCode; - ValueLength = valueLength; - } - - /// - /// A success/error code for the Get operation - /// - public GetResultCode ResultCode { get; } - - /// - /// If the key was found, this represents the length of the value - /// in the database. - /// - /// - /// If the key was not found, this value is 0 and has no meaning. - /// - /// - public int ValueLength { get; } - } - - /// - /// An enumeration describing the result of a TryGet operation - /// - public enum GetResultCode - { - /// - /// Success! The requested key was found and the value was copied to - /// the provided buffer. - /// - /// GetResult.Length represents the length of the value found in - /// the database. (This is also the length of data copied into - /// the provided buffer.) - /// - /// - Success, - - /// - /// Failure. The requested key was found, but the provided buffer was - /// too small to contain the full value. No data has been retrived. - /// The Get operation should be retried with a buffer at least the - /// length represented in GetResult.Length - /// - /// GetResult.Length represents the length of the value found in - /// the database. (This is the minimum buffer length needed for - /// the operation to succeed) - /// - /// - DestinationTooSmall, - - /// - /// Failure. The requested key was not found in the database. No data has - /// been retrived. - /// - /// GetResult.Length has no meaning for this result. It's value - /// is set to zero. - /// - /// - KeyNotFound - } - -} diff --git a/src/LightningDB/HelperExtensions.cs b/src/LightningDB/HelperExtensions.cs deleted file mode 100644 index 906ee31..0000000 --- a/src/LightningDB/HelperExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace LightningDB -{ - public static class HelperExtensions - { - public static bool StartsWith(this byte[] source, byte[] prefix) - { - var length = prefix.Length; - for (var i = 0; i < length; ++i) - { - if (source[i] == prefix[i]) - continue; - return false; - } - return length != 0; - } - } -} \ No newline at end of file diff --git a/src/LightningDB/LightningCursor.cs b/src/LightningDB/LightningCursor.cs index 421e814..eaf835b 100644 --- a/src/LightningDB/LightningCursor.cs +++ b/src/LightningDB/LightningCursor.cs @@ -1,11 +1,7 @@ using System; -using System.Collections; -using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using LightningDB.Native; using static LightningDB.Native.Lmdb; namespace LightningDB @@ -13,11 +9,9 @@ namespace LightningDB /// /// Cursor to iterate over a database /// - public class LightningCursor : IEnumerator>, IEnumerable> + public class LightningCursor : IDisposable { private IntPtr _handle; - private MDBValue _currentKey; - private MDBValue _currentValue; /// /// Creates new instance of LightningCursor @@ -32,7 +26,7 @@ internal LightningCursor(LightningDatabase db, LightningTransaction txn) if (txn == null) throw new ArgumentNullException(nameof(txn)); - mdb_cursor_open(txn.Handle(), db.Handle(), out _handle); + mdb_cursor_open(txn.Handle(), db.Handle(), out _handle).ThrowOnError(); Transaction = txn; Transaction.Disposing += Dispose; @@ -52,37 +46,41 @@ public IntPtr Handle() public LightningTransaction Transaction { get; } /// - /// Equivilent to Renew + /// Position at specified key, if key is not found index will be positioned to closest match. /// - public void Reset() + /// Key + /// Returns + public MDBResultCode Set(byte[] key) { - Renew(); + return Get(CursorOperation.Set, key).resultCode; } - - object IEnumerator.Current => Current; - + /// - /// The current item the cursor is pointed to, or default KeyValuePair<byte[], byte[]> + /// Position at specified key, if key is not found index will be positioned to closest match. /// - public KeyValuePair Current => - _currentKey.size == IntPtr.Zero ? default : new KeyValuePair(_currentKey, _currentValue); + /// Key + /// Returns + public MDBResultCode Set(ReadOnlySpan key) + { + return Get(CursorOperation.Set, key).resultCode; + } /// - /// Position at specified key, Current will not be populated. + /// Moves to the key and populates Current with the values stored. /// /// Key - /// Returns true if the key was found. - public bool MoveTo(byte[] key) + /// Returns , and key/value + public (MDBResultCode resultCode, MDBValue key, MDBValue value) SetKey(byte[] key) { - return Get(CursorOperation.Set, key); + return Get(CursorOperation.SetKey, key); } - + /// /// Moves to the key and populates Current with the values stored. /// /// Key - /// Returns true if the key was found. - public bool MoveToAndGet(byte[] key) + /// Returns , and key/value + public (MDBResultCode resultCode, MDBValue key, MDBValue value) SetKey(ReadOnlySpan key) { return Get(CursorOperation.SetKey, key); } @@ -93,9 +91,20 @@ public bool MoveToAndGet(byte[] key) /// Key. /// Value /// Returns true if the key/value pair was found. - public bool MoveTo(byte[] key, byte[] value) + public MDBResultCode GetBoth(byte[] key, byte[] value) + { + return Get(CursorOperation.GetBoth, key, value).resultCode; + } + + /// + /// Position at key/data pair. Only for MDB_DUPSORT + /// + /// Key. + /// Value + /// Returns + public MDBResultCode GetBoth(ReadOnlySpan key, ReadOnlySpan value) { - return Get(CursorOperation.GetBoth, key, value); + return Get(CursorOperation.GetBoth, key, value).resultCode; } /// @@ -103,139 +112,160 @@ public bool MoveTo(byte[] key, byte[] value) /// /// Key /// Value - /// Returns true if the key/value pair is found. - public bool MoveToFirstValueAfter(byte[] key, byte[] value) + /// Returns + public MDBResultCode GetBothRange(byte[] key, byte[] value) { - return Get(CursorOperation.GetBothRange, key, value); + return Get(CursorOperation.GetBothRange, key, value).resultCode; + } + + /// + /// position at key, nearest data. Only for MDB_DUPSORT + /// + /// Key + /// Value + /// Returns + public MDBResultCode GetBothRange(ReadOnlySpan key, ReadOnlySpan value) + { + return Get(CursorOperation.GetBothRange, key, value).resultCode; } /// /// Position at first key greater than or equal to specified key. /// /// Key - /// Returns true if the key is found and had one more item after it to advance to. - public bool MoveToFirstAfter(byte[] key) + /// Returns + public MDBResultCode SetRange(byte[] key) + { + return Get(CursorOperation.SetRange, key).resultCode; + } + + /// + /// Position at first key greater than or equal to specified key. + /// + /// Key + /// Returns + public MDBResultCode SetRange(ReadOnlySpan key) { - return Get(CursorOperation.SetRange, key); + return Get(CursorOperation.SetRange, key).resultCode; } /// /// Position at first key/data item /// - /// True if first pair is found. - public bool MoveToFirst() + /// Returns + public MDBResultCode First() { - return Get(CursorOperation.First); + return Get(CursorOperation.First).resultCode; } /// /// Position at first data item of current key. Only for MDB_DUPSORT /// - /// True if first duplicate is found. - public bool MoveToFirstDuplicate() + /// Returns + public MDBResultCode FirstDuplicate() { - return Get(CursorOperation.FirstDuplicate); + return Get(CursorOperation.FirstDuplicate).resultCode; } /// /// Position at last key/data item /// - /// True if last pair is found. - public bool MoveToLast() + /// Returns + public MDBResultCode Last() { - return Get(CursorOperation.Last); + return Get(CursorOperation.Last).resultCode; } /// /// Position at last data item of current key. Only for MDB_DUPSORT /// - /// True if last duplicate is found. - public bool MoveToLastDuplicate() + /// Returns + public MDBResultCode LastDuplicate() { - return Get(CursorOperation.LastDuplicate); + return Get(CursorOperation.LastDuplicate).resultCode; } /// /// Return key/data at current cursor position /// /// Key/data at current cursor position - public KeyValuePair GetCurrent() + public (MDBResultCode resultCode, MDBValue key, MDBValue value) GetCurrent() { - Get(CursorOperation.GetCurrent); - return Current; + return Get(CursorOperation.GetCurrent); } /// /// Position at next data item /// - /// True if next item exists. - public bool MoveNext() + /// Returns + public MDBResultCode Next() { - return Get(CursorOperation.Next); + return Get(CursorOperation.Next).resultCode; } /// /// Position at next data item of current key. Only for MDB_DUPSORT /// - /// True if next duplicate exists. - public bool MoveNextDuplicate() + /// Returns + public MDBResultCode NextDuplicate() { - return Get(CursorOperation.NextDuplicate); + return Get(CursorOperation.NextDuplicate).resultCode; } /// /// Position at first data item of next key. Only for MDB_DUPSORT. /// - /// True if items exists without duplicates. - public bool MoveNextNoDuplicate() + /// Returns + public MDBResultCode NextNoDuplicate() { - return Get(CursorOperation.NextNoDuplicate); + return Get(CursorOperation.NextNoDuplicate).resultCode; } /// /// Return up to a page of duplicate data items at the next cursor position. Only for MDB_DUPFIXED /// It is assumed you know the array size to break up a single byte[] into byte[][]. /// - /// Returns true if duplicates are found. - public bool MoveNextMultiple() + /// Returns , and key will be empty here, values are 2D array + public (MDBResultCode resultCode, MDBValue key, MDBValue value) NextMultiple() { - return GetMultiple(CursorOperation.NextMultiple); + return Get(CursorOperation.NextMultiple); } /// /// Position at previous data item. /// - /// Returns true if previous item is found. - public bool MovePrev() + /// Returns + public MDBResultCode Previous() { - return Get(CursorOperation.Previous); + return Get(CursorOperation.Previous).resultCode; } /// /// Position at previous data item of current key. Only for MDB_DUPSORT. /// - /// Previous data item of current key. - public bool MovePrevDuplicate() + /// Returns + public MDBResultCode PreviousDuplicate() { - return Get(CursorOperation.PreviousDuplicate); + return Get(CursorOperation.PreviousDuplicate).resultCode; } /// /// Position at last data item of previous key. Only for MDB_DUPSORT. /// - /// True if previous entry without duplicate is found. - public bool MovePrevNoDuplicate() + /// Returns + public MDBResultCode PreviousNoDuplicate() { - return Get(CursorOperation.PreviousNoDuplicate); + return Get(CursorOperation.PreviousNoDuplicate).resultCode; } - private bool Get(CursorOperation operation) + private (MDBResultCode resultCode, MDBValue key, MDBValue value) Get(CursorOperation operation) { - _currentKey = _currentValue = default; - return mdb_cursor_get(_handle, ref _currentKey, ref _currentValue, operation) == 0; + var mdbKey = new MDBValue(); + var mdbValue = new MDBValue(); + return (mdb_cursor_get(_handle, ref mdbKey, ref mdbValue, operation), mdbKey, mdbValue); } - private bool Get(CursorOperation operation, byte[] key) + private (MDBResultCode resultCode, MDBValue key, MDBValue value) Get(CursorOperation operation, byte[] key) { if (key is null) throw new ArgumentNullException(nameof(key)); @@ -243,42 +273,32 @@ private bool Get(CursorOperation operation, byte[] key) return Get(operation, key.AsSpan()); } - private unsafe bool Get(CursorOperation operation, ReadOnlySpan key) + private unsafe (MDBResultCode resultCode, MDBValue key, MDBValue value) Get(CursorOperation operation, ReadOnlySpan key) { - _currentValue = default; fixed (byte* keyPtr = key) { - var findKey = new MDBValue(key.Length, keyPtr); - var found = mdb_cursor_get(_handle, ref findKey, ref _currentValue, operation) == 0; - if (found) - { - _currentKey = findKey; - } - return found; + var mdbKey = new MDBValue(key.Length, keyPtr); + var mdbValue = new MDBValue(); + return (mdb_cursor_get(_handle, ref mdbKey, ref mdbValue, operation), mdbKey, mdbValue); } } - private bool Get(CursorOperation operation, byte[] key, byte[] value) + private (MDBResultCode resultCode, MDBValue key, MDBValue value) Get(CursorOperation operation, byte[] key, byte[] value) { return Get(operation, key.AsSpan(), value.AsSpan()); } - private unsafe bool Get(CursorOperation operation, ReadOnlySpan key, ReadOnlySpan value) + private unsafe (MDBResultCode resultCode, MDBValue key, MDBValue value) Get(CursorOperation operation, ReadOnlySpan key, ReadOnlySpan value) { fixed(byte* keyPtr = key) fixed(byte* valPtr = value) { var mdbKey = new MDBValue(key.Length, keyPtr); var mdbValue = new MDBValue(value.Length, valPtr); - return mdb_cursor_get(_handle, ref mdbKey, ref mdbValue, operation) == 0; + return (mdb_cursor_get(_handle, ref mdbKey, ref mdbValue, operation), mdbKey, mdbValue); } } - private bool GetMultiple(CursorOperation operation) - { - return mdb_cursor_get_multiple(_handle, ref _currentKey, ref _currentValue, operation) == 0; - } - /// /// Store by cursor. /// This function stores key/data pairs into the database. The cursor is positioned at the new item, or on failure usually near it. @@ -297,9 +317,10 @@ private bool GetMultiple(CursorOperation operation) /// CursorPutOptions.AppendData - append the given key/data pair to the end of the database. No key comparisons are performed. This option allows fast bulk loading when keys are already known to be in the correct order. Loading unsorted keys with this flag will cause data corruption. /// CursorPutOptions.AppendDuplicateData - as above, but for sorted dup data. /// - public void Put(byte[] key, byte[] value, CursorPutOptions options) + /// Returns + public MDBResultCode Put(byte[] key, byte[] value, CursorPutOptions options) { - Put(key.AsSpan(), value.AsSpan(), options); + return Put(key.AsSpan(), value.AsSpan(), options); } @@ -321,7 +342,8 @@ public void Put(byte[] key, byte[] value, CursorPutOptions options) /// CursorPutOptions.AppendData - append the given key/data pair to the end of the database. No key comparisons are performed. This option allows fast bulk loading when keys are already known to be in the correct order. Loading unsorted keys with this flag will cause data corruption. /// CursorPutOptions.AppendDuplicateData - as above, but for sorted dup data. /// - public unsafe void Put(ReadOnlySpan key, ReadOnlySpan value, CursorPutOptions options) + /// Returns + public unsafe MDBResultCode Put(ReadOnlySpan key, ReadOnlySpan value, CursorPutOptions options) { fixed (byte* keyPtr = key) fixed (byte* valPtr = value) @@ -329,7 +351,7 @@ public unsafe void Put(ReadOnlySpan key, ReadOnlySpan value, CursorP var mdbKey = new MDBValue(key.Length, keyPtr); var mdbValue = new MDBValue(value.Length, valPtr); - mdb_cursor_put(_handle, mdbKey, mdbValue, options); + return mdb_cursor_put(_handle, mdbKey, mdbValue, options); } } @@ -341,9 +363,10 @@ public unsafe void Put(ReadOnlySpan key, ReadOnlySpan value, CursorP /// /// The key operated on. /// The data items operated on. - public unsafe void PutMultiple(byte[] key, byte[][] values) + /// Returns + public unsafe MDBResultCode Put(byte[] key, byte[][] values) { - const int StackAllocateLimit = 256;//I just made up a number, this can be much more agressive -arc + const int StackAllocateLimit = 256;//I just made up a number, this can be much more aggressive -arc int overallLength = values.Sum(arr => arr.Length);//probably allocates but boy is it handy... @@ -354,21 +377,21 @@ public unsafe void PutMultiple(byte[] key, byte[][] values) { Span contiguousValues = stackalloc byte[overallLength]; - InnerPutMultiple(contiguousValues); + return InnerPutMultiple(contiguousValues); } else { fixed (byte* contiguousValuesPtr = new byte[overallLength]) { Span contiguousValues = new Span(contiguousValuesPtr, overallLength); - InnerPutMultiple(contiguousValues); + return InnerPutMultiple(contiguousValues); } } //these local methods could be made static, but the compiler will emit these closures - //as structs with very little overhead. Also static local functions isn't availible + //as structs with very little overhead. Also static local functions isn't available //until C# 8 so I can't use it anyway... - void InnerPutMultiple(Span contiguousValuesBuffer) + MDBResultCode InnerPutMultiple(Span contiguousValuesBuffer) { FlattenInfo(contiguousValuesBuffer); var contiguousValuesPtr = (byte*)Unsafe.AsPointer(ref contiguousValuesBuffer.GetPinnableReference()); @@ -382,7 +405,7 @@ void InnerPutMultiple(Span contiguousValuesBuffer) { var mdbKey = new MDBValue(key.Length, keyPtr); - mdb_cursor_put(_handle, ref mdbKey, ref dataBuffer, CursorPutOptions.MultipleData); + return mdb_cursor_put(_handle, ref mdbKey, ref dataBuffer, CursorPutOptions.MultipleData); } } @@ -406,15 +429,14 @@ int GetSize() } } - /// /// Return up to a page of the duplicate data items at the current cursor position. Only for MDB_DUPFIXED /// It is assumed you know the array size to break up a single byte[] into byte[][]. /// - /// True if key and multiple items are found. - public bool GetMultiple() + /// Returns , and key will be empty here, values are 2D array + public (MDBResultCode resultCode, MDBValue key, MDBValue value) GetMultiple() { - return GetMultiple(CursorOperation.GetMultiple); + return Get(CursorOperation.GetMultiple); } /// @@ -423,42 +445,41 @@ public bool GetMultiple() /// /// Options for this operation. This parameter must be set to 0 or one of the values described here. /// MDB_NODUPDATA - delete all of the data items for the current key. This flag may only be specified if the database was opened with MDB_DUPSORT. - private void Delete(CursorDeleteOption option) + private MDBResultCode Delete(CursorDeleteOption option) { - mdb_cursor_del(_handle, option); + return mdb_cursor_del(_handle, option); } /// /// Delete current key/data pair. /// This function deletes the key/data range for which duplicates are found. /// - public void DeleteDuplicates() + public MDBResultCode DeleteDuplicateData() { - Delete(CursorDeleteOption.NoDuplicateData); + return Delete(CursorDeleteOption.NoDuplicateData); } /// /// Delete current key/data pair. /// This function deletes the key/data pair to which the cursor refers. /// - public void Delete() + public MDBResultCode Delete() { - Delete(CursorDeleteOption.None); + return Delete(CursorDeleteOption.None); } - //TODO: tests /// /// Renew a cursor handle. /// Cursors are associated with a specific transaction and database and may not span threads. /// Cursors that are only used in read-only transactions may be re-used, to avoid unnecessary malloc/free overhead. /// The cursor may be associated with a new read-only transaction, and referencing the same database handle as it was created with. /// - public void Renew() + /// Returns + public MDBResultCode Renew() { - Renew(Transaction); + return Renew(Transaction); } - //TODO: tests /// /// Renew a cursor handle. /// Cursors are associated with a specific transaction and database and may not span threads. @@ -466,7 +487,8 @@ public void Renew() /// The cursor may be associated with a new read-only transaction, and referencing the same database handle as it was created with. /// /// Transaction to renew in. - public void Renew(LightningTransaction txn) + /// Returns + public MDBResultCode Renew(LightningTransaction txn) { if(txn == null) throw new ArgumentNullException(nameof(txn)); @@ -474,7 +496,7 @@ public void Renew(LightningTransaction txn) if (!txn.IsReadOnly) throw new InvalidOperationException("Can't renew cursor on non-readonly transaction"); - mdb_cursor_renew(txn.Handle(), _handle); + return mdb_cursor_renew(txn.Handle(), _handle); } /// @@ -505,16 +527,6 @@ public void Dispose() Dispose(true); } - public IEnumerator> GetEnumerator() - { - return this; - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - ~LightningCursor() { Dispose(false); diff --git a/src/LightningDB/LightningDatabase.cs b/src/LightningDB/LightningDatabase.cs index 2c30ddb..c8d5f2d 100644 --- a/src/LightningDB/LightningDatabase.cs +++ b/src/LightningDB/LightningDatabase.cs @@ -1,5 +1,4 @@ using System; -using LightningDB.Native; using static LightningDB.Native.Lmdb; namespace LightningDB @@ -7,7 +6,7 @@ namespace LightningDB /// /// Lightning database. /// - public class LightningDatabase : IDisposable + public sealed class LightningDatabase : IDisposable { private uint _handle; private readonly DatabaseConfiguration _configuration; @@ -25,15 +24,12 @@ internal LightningDatabase(string name, LightningTransaction transaction, Databa if (transaction == null) throw new ArgumentNullException(nameof(transaction)); - if (configuration == null) - throw new ArgumentNullException(nameof(configuration)); - Name = name; - _configuration = configuration; + _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); Environment = transaction.Environment; _transaction = transaction; Environment.Disposing += Dispose; - mdb_dbi_open(transaction.Handle(), name, _configuration.Flags, out _handle); + mdb_dbi_open(transaction.Handle(), name, _configuration.Flags, out _handle).ThrowOnError(); _pinnedConfig = _configuration.ConfigureDatabase(transaction, this); IsOpened = true; } @@ -46,7 +42,7 @@ public uint Handle() /// /// Whether the database handle has been release from Dispose, or from unsuccessful OpenDatabase call. /// - public bool IsReleased => _handle == default(uint); + public bool IsReleased => _handle == default; /// /// Is database opened. @@ -57,8 +53,7 @@ public Stats DatabaseStats { get { - var nativeStat = new MDBStat(); - mdb_stat(_transaction.Handle(), Handle(), out nativeStat); + mdb_stat(_transaction.Handle(), Handle(), out var nativeStat).ThrowOnError(); return new Stats { BranchPages = nativeStat.ms_branch_pages.ToInt64(), @@ -81,36 +76,32 @@ public Stats DatabaseStats /// public LightningEnvironment Environment { get; } - /// - /// Flags with which the database was opened. - /// - public DatabaseOpenFlags OpenFlags { get; private set; } - /// /// Drops the database. /// - public void Drop(LightningTransaction transaction) + public MDBResultCode Drop(LightningTransaction transaction) { - mdb_drop(transaction.Handle(), _handle, true); + var result = mdb_drop(transaction.Handle(), _handle, true); IsOpened = false; - _handle = default(uint); + _handle = default; + return result; } /// /// Truncates all data from the database. /// - public void Truncate(LightningTransaction transaction) + public MDBResultCode Truncate(LightningTransaction transaction) { - mdb_drop(transaction.Handle(), _handle, false); + return mdb_drop(transaction.Handle(), _handle, false); } /// /// Deallocates resources opened by the database. /// /// true if called from Dispose. - protected virtual void Dispose(bool disposing) + private void Dispose(bool disposing) { - if (_handle == default(uint)) + if (_handle == default) return; if(!disposing) @@ -121,11 +112,11 @@ protected virtual void Dispose(bool disposing) _pinnedConfig.Dispose(); mdb_dbi_close(Environment.Handle(), _handle); GC.SuppressFinalize(this); - _handle = default(uint); + _handle = default; } /// - /// Deallocates resources opeened by the database. + /// Deallocates resources opened by the database. /// public void Dispose() { diff --git a/src/LightningDB/LightningEnvironment.cs b/src/LightningDB/LightningEnvironment.cs index 10a940e..c32116f 100644 --- a/src/LightningDB/LightningEnvironment.cs +++ b/src/LightningDB/LightningEnvironment.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using LightningDB.Native; using static LightningDB.Native.Lmdb; namespace LightningDB @@ -8,7 +7,7 @@ namespace LightningDB /// /// LMDB Environment. /// - public class LightningEnvironment : IDisposable + public sealed class LightningEnvironment : IDisposable { private readonly EnvironmentConfiguration _config = new EnvironmentConfiguration(); @@ -26,7 +25,7 @@ public LightningEnvironment(string path, EnvironmentConfiguration configuration if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Invalid directory name"); - mdb_env_create(out _handle); + mdb_env_create(out _handle).ThrowOnError(); Path = path; @@ -76,7 +75,7 @@ public long MapSize else _config.MapSize = value; - mdb_env_set_mapsize(_handle, _config.MapSize); + mdb_env_set_mapsize(_handle, _config.MapSize).ThrowOnError(); } } @@ -87,14 +86,15 @@ public int MaxReaders { get { - return _config.MaxReaders; + mdb_env_get_maxreaders(_handle, out var readers).ThrowOnError(); + return (int) readers; } set { if (IsOpened) throw new InvalidOperationException("Can't change MaxReaders of opened environment"); - mdb_env_set_maxreaders(_handle, (uint)value); + mdb_env_set_maxreaders(_handle, (uint)value).ThrowOnError(); _config.MaxReaders = value; } @@ -117,7 +117,7 @@ public int MaxDatabases if (value == _config.MaxDatabases) return; - mdb_env_set_maxdbs(_handle, (uint)value); + mdb_env_set_maxdbs(_handle, (uint)value).ThrowOnError(); _config.MaxDatabases = value; } @@ -178,7 +178,7 @@ public void Open(EnvironmentOpenFlags openFlags = EnvironmentOpenFlags.None, Uni try { - mdb_env_open(_handle, Path, openFlags, accessMode); + mdb_env_open(_handle, Path, openFlags, accessMode).ThrowOnError(); } catch(Exception ex) { @@ -206,7 +206,7 @@ public void Open(EnvironmentOpenFlags openFlags = EnvironmentOpenFlags.None, Uni /// /// New LightningTransaction /// - public LightningTransaction BeginTransaction(LightningTransaction parent, TransactionBeginFlags beginFlags) + public LightningTransaction BeginTransaction(LightningTransaction parent = null, TransactionBeginFlags beginFlags = LightningTransaction.DefaultTransactionBeginFlags) { if (!IsOpened) throw new InvalidOperationException("Environment must be opened before starting a transaction"); @@ -232,21 +232,6 @@ public LightningTransaction BeginTransaction(TransactionBeginFlags beginFlags) return BeginTransaction(null, beginFlags); } - /// - /// Create a transaction for use with the environment. - /// The transaction handle may be discarded using Abort() or Commit(). - /// Note: - /// Transactions may not span threads; a transaction must only be used by a single thread. Also, a thread may only have a single transaction. - /// Cursors may not span transactions; each cursor must be opened and closed within a single transaction. - /// - /// - /// New LightningTransaction - /// - public LightningTransaction BeginTransaction() - { - return BeginTransaction(null, LightningTransaction.DefaultTransactionBeginFlags); - } - /// /// Copy an MDB environment to the specified path. /// This function may be used to make a backup of an existing environment. @@ -271,9 +256,9 @@ public void CopyTo(string path, bool compact = false) /// MDB always flushes the OS buffers upon commit as well, unless the environment was opened with EnvironmentOpenFlags.NoSync or in part EnvironmentOpenFlags.NoMetaSync. /// /// If true, force a synchronous flush. Otherwise if the environment has the EnvironmentOpenFlags.NoSync flag set the flushes will be omitted, and with MDB_MAPASYNC they will be asynchronous. - public void Flush(bool force) + public MDBResultCode Flush(bool force) { - mdb_env_sync(_handle, force); + return mdb_env_sync(_handle, force); } private void EnsureOpened() @@ -286,7 +271,7 @@ private void EnsureOpened() /// Disposes the environment and deallocates all resources associated with it. /// /// True if called from Dispose. - protected virtual void Dispose(bool disposing) + private void Dispose(bool disposing) { if (_handle == IntPtr.Zero) return; diff --git a/src/LightningDB/LightningExtensions.cs b/src/LightningDB/LightningExtensions.cs new file mode 100644 index 0000000..d106d47 --- /dev/null +++ b/src/LightningDB/LightningExtensions.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using LightningDB.Native; + +namespace LightningDB +{ + public static class LightningExtensions + { + /// + /// Throws a on anything other than NotFound, or Success + /// + /// The result code to evaluate for errors + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MDBResultCode ThrowOnReadError(this MDBResultCode resultCode) + { + if (resultCode == MDBResultCode.NotFound) + return resultCode; + return resultCode.ThrowOnError(); + } + + /// + /// Throws a on anything other than Success + /// + /// The result code to evaluate for errors + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MDBResultCode ThrowOnError(this MDBResultCode resultCode) + { + if (resultCode == MDBResultCode.Success) + return resultCode; + var statusCode = (int) resultCode; + var message = mdb_strerror(statusCode); + throw new LightningException(message, statusCode); + } + + /// + /// Throws a on anything other than NotFound, or Success + /// + /// A representing the get result operation + /// The provided if no error occurs + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static (MDBResultCode resultCode, MDBValue key, MDBValue value) ThrowOnReadError( + this ValueTuple result) + { + result.Item1.ThrowOnReadError(); + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string mdb_strerror(int err) + { + var ptr = LmdbMethods.mdb_strerror(err); + return Marshal.PtrToStringAnsi(ptr); + } + + /// + /// Enumerates the key/value pairs of the starting at the current position. + /// + /// + /// key/value pairs of + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable> AsEnumerable(this LightningCursor cursor) + { + do + { + var (resultCode, key, value) = cursor.GetCurrent(); + if (resultCode == MDBResultCode.Success) + { + yield return (key, value); + } + } while (cursor.Next() == MDBResultCode.Success); + } + + /// + /// Tries to get a value by its key. + /// + /// The transaction. + /// The database to query. + /// A span containing the key to look up. + /// A byte array containing the value found in the database, if it exists. + /// True if key exists, false if not. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryGet(this LightningTransaction tx, LightningDatabase db, byte[] key, out byte[] value) + { + return TryGet(tx, db, key.AsSpan(), out value); + } + + /// + /// Tries to get a value by its key. + /// + /// The transaction. + /// The database to query. + /// A span containing the key to look up. + /// A byte array containing the value found in the database, if it exists. + /// True if key exists, false if not. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryGet(this LightningTransaction tx, LightningDatabase db, ReadOnlySpan key, out byte[] value) + { + var (resultCode, _, mdbValue) = tx.Get(db, key); + if (resultCode == MDBResultCode.Success) + { + value = mdbValue.CopyToNewArray(); + return true; + } + value = default; + return false; + } + + /// + /// Tries to get a value by its key. + /// + /// The transaction. + /// The database to query. + /// A span containing the key to look up. + /// + /// A buffer to receive the value data retrieved from the database + /// + /// True if key exists, false if not. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryGet(this LightningTransaction tx, LightningDatabase db, ReadOnlySpan key, byte[] destinationValueBuffer) + { + var (resultCode, _, mdbValue) = tx.Get(db, key); + if (resultCode != MDBResultCode.Success) + return false; + + var valueSpan = mdbValue.AsSpan(); + if (valueSpan.TryCopyTo(destinationValueBuffer)) + { + return true; + } + throw new LightningException("Incorrect buffer size given in destinationValueBuffer", (int)MDBResultCode.BadValSize); + } + + /// + /// Check whether data exists in database. + /// + /// The transaction. + /// The database to query. + /// A span containing the key to look up. + /// True if key exists, false if not. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ContainsKey(this LightningTransaction tx, LightningDatabase db, ReadOnlySpan key) + { + var (resultCode, _, _) = tx.Get(db, key); + return resultCode == MDBResultCode.Success; + } + + /// + /// Check whether data exists in database. + /// + /// The transaction. + /// The database to query. + /// A span containing the key to look up. + /// True if key exists, false if not. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ContainsKey(this LightningTransaction tx, LightningDatabase db, byte[] key) + { + return ContainsKey(tx, db, key.AsSpan()); + } + } +} \ No newline at end of file diff --git a/src/LightningDB/LightningTransaction.cs b/src/LightningDB/LightningTransaction.cs index 443884d..c3ce65f 100644 --- a/src/LightningDB/LightningTransaction.cs +++ b/src/LightningDB/LightningTransaction.cs @@ -1,7 +1,5 @@ using System; -using LightningDB.Native; - using static LightningDB.Native.Lmdb; namespace LightningDB @@ -9,7 +7,7 @@ namespace LightningDB /// /// Represents a transaction. /// - public class LightningTransaction : IDisposable + public sealed class LightningTransaction : IDisposable { /// /// Default options used to begin new transactions. @@ -39,7 +37,7 @@ internal LightningTransaction(LightningEnvironment environment, LightningTransac } var parentHandle = parent?.Handle() ?? IntPtr.Zero; - mdb_txn_begin(environment.Handle(), parentHandle, flags, out _handle); + mdb_txn_begin(environment.Handle(), parentHandle, flags, out _handle).ThrowOnError(); _originalHandle = _handle; } @@ -67,27 +65,18 @@ private void OnParentStateChanging(LightningTransactionState state) /// /// Current transaction state. /// - public LightningTransactionState State { get; internal set; } + public LightningTransactionState State { get; private set; } /// /// Begin a child transaction. /// /// Options for a new transaction. /// New child transaction. - public LightningTransaction BeginTransaction(TransactionBeginFlags beginFlags) + public LightningTransaction BeginTransaction(TransactionBeginFlags beginFlags = DefaultTransactionBeginFlags) { return new LightningTransaction(Environment, this, beginFlags); } - /// - /// Begins a child transaction. - /// - /// New child transaction with default options. - public LightningTransaction BeginTransaction() - { - return BeginTransaction(DefaultTransactionBeginFlags); - } - /// /// Opens a database in context of this transaction. /// @@ -104,17 +93,17 @@ public LightningDatabase OpenDatabase(string name = null, DatabaseConfiguration /// /// Drops the database. /// - public void DropDatabase(LightningDatabase database) + public MDBResultCode DropDatabase(LightningDatabase database) { - database.Drop(this); + return database.Drop(this); } /// /// Truncates all data from the database. /// - public void TruncateDatabase(LightningDatabase database) + public MDBResultCode TruncateDatabase(LightningDatabase database) { - database.Truncate(this); + return database.Truncate(this); } /// @@ -127,14 +116,13 @@ public LightningCursor CreateCursor(LightningDatabase db) return new LightningCursor(db, this); } - /// /// Get value from a database. /// /// The database to query. /// An array containing the key to look up. /// Requested value's byte array if exists, or null if not. - public byte[] Get(LightningDatabase db, byte[] key) + public (MDBResultCode resultCode, MDBValue key, MDBValue value) Get(LightningDatabase db, byte[] key) {//argument validation delegated to next call return Get(db, key.AsSpan()); @@ -146,33 +134,7 @@ public byte[] Get(LightningDatabase db, byte[] key) /// The database to query. /// A span containing the key to look up. /// Requested value's byte array if exists, or null if not. - public byte[] Get(LightningDatabase db, ReadOnlySpan key) - {//argument validation delegated to next call - byte[] value; - TryGet(db, key, out value); - return value; - } - - /// - /// Tries to get a value by its key. - /// - /// The database to query. - /// A span containing the key to look up. - /// A byte array containing the value found in the database, if it exists. - /// True if key exists, false if not. - public bool TryGet(LightningDatabase db, byte[] key, out byte[] value) - {//argument validation delegated to next call - return TryGet(db, key.AsSpan(), out value); - } - - /// - /// Tries to get a value by its key. - /// - /// The database to query. - /// A span containing the key to look up. - /// A byte array containing the value found in the database, if it exists. - /// True if key exists, false if not. - public unsafe bool TryGet(LightningDatabase db, ReadOnlySpan key, out byte[] value) + public unsafe (MDBResultCode resultCode, MDBValue key, MDBValue value) Get(LightningDatabase db, ReadOnlySpan key) { if (db == null) throw new ArgumentNullException(nameof(db)); @@ -181,81 +143,7 @@ public unsafe bool TryGet(LightningDatabase db, ReadOnlySpan key, out byte { var mdbKey = new MDBValue(key.Length, keyBuffer); - value = default; - var result = mdb_get(_handle, db.Handle(), ref mdbKey, out var newVal) != MDB_NOTFOUND; - if (result) - { - value = newVal.CopyToNewArray(); - } - return result; - } - } - - /// - /// Tries to lookup a value by its key and read it into the provided buffer. - /// - /// The database to query. - /// A span containing the key to look up. - /// - /// A buffer to receive the value data retrieved from the database - /// - /// True if key exists, false if not. - public unsafe GetResult TryGet(LightningDatabase db, ReadOnlySpan key, byte[] valueDestinationBuffer) - { - if (db == null) - throw new ArgumentNullException(nameof(db)); - - fixed (byte* keyBuffer = key) - { - var mdbKey = new MDBValue(key.Length, keyBuffer); - - if (mdb_get(_handle, db.Handle(), ref mdbKey, out MDBValue mdbValue) != MDB_NOTFOUND) - { - var valueSpan = mdbValue.AsSpan(); - - if(valueSpan.TryCopyTo(valueDestinationBuffer)) - { - return new GetResult(GetResultCode.Success, valueSpan.Length); - } - else - { - return new GetResult(GetResultCode.DestinationTooSmall, valueSpan.Length); - } - } - else - { - return new GetResult(GetResultCode.DestinationTooSmall, 0); - } - } - } - - /// - /// Check whether data exists in database. - /// - /// The database to query. - /// A span containing the key to look up. - /// True if key exists, false if not. - public bool ContainsKey(LightningDatabase db, byte[] key) - {//argument validation delegated to next call - return ContainsKey(db, key.AsSpan()); - } - - /// - /// Check whether data exists in database. - /// - /// The database to query. - /// A span containing the key to look up. - /// True if key exists, false if not. - public unsafe bool ContainsKey(LightningDatabase db, ReadOnlySpan key) - { - if (db is null) - throw new ArgumentNullException(nameof(db)); - - fixed (byte* keyBuffer = key) - { - var mdbKey = new MDBValue(key.Length, keyBuffer); - - return mdb_get(_handle, db.Handle(), ref mdbKey, out MDBValue mdbValue) != MDB_NOTFOUND; + return (mdb_get(_handle, db.Handle(), ref mdbKey, out var mdbValue), mdbKey, mdbValue); } } @@ -266,13 +154,11 @@ public unsafe bool ContainsKey(LightningDatabase db, ReadOnlySpan key) /// A span containing the key to look up. /// A byte array containing the value found in the database, if it exists. /// Operation options (optional). - public void Put(LightningDatabase db, byte[] key, byte[] value, PutOptions options = PutOptions.None) + public MDBResultCode Put(LightningDatabase db, byte[] key, byte[] value, PutOptions options = PutOptions.None) {//argument validation delegated to next call - - Put(db, key.AsSpan(), value.AsSpan(), options); + return Put(db, key.AsSpan(), value.AsSpan(), options); } - /// /// Put data into a database. /// @@ -280,7 +166,7 @@ public void Put(LightningDatabase db, byte[] key, byte[] value, PutOptions optio /// Key byte array. /// Value byte array. /// Operation options (optional). - public unsafe void Put(LightningDatabase db, ReadOnlySpan key, ReadOnlySpan value, PutOptions options = PutOptions.None) + public unsafe MDBResultCode Put(LightningDatabase db, ReadOnlySpan key, ReadOnlySpan value, PutOptions options = PutOptions.None) { if (db == null) throw new ArgumentNullException(nameof(db)); @@ -291,7 +177,7 @@ public unsafe void Put(LightningDatabase db, ReadOnlySpan key, ReadOnlySpa var mdbKey = new MDBValue(key.Length, keyPtr); var mdbValue = new MDBValue(value.Length, valuePtr); - mdb_put(_handle, db.Handle(), mdbKey, mdbValue, options); + return mdb_put(_handle, db.Handle(), mdbKey, mdbValue, options); } } @@ -306,9 +192,9 @@ public unsafe void Put(LightningDatabase db, ReadOnlySpan key, ReadOnlySpa /// A database handle returned by mdb_dbi_open() /// The key to delete from the database /// The data to delete (optional) - public void Delete(LightningDatabase db, byte[] key, byte[] value) + public MDBResultCode Delete(LightningDatabase db, byte[] key, byte[] value) {//argument validation delegated to next call - Delete(db, key.AsSpan(), value.AsSpan()); + return Delete(db, key.AsSpan(), value.AsSpan()); } /// @@ -322,7 +208,7 @@ public void Delete(LightningDatabase db, byte[] key, byte[] value) /// A database handle returned by mdb_dbi_open() /// The key to delete from the database /// The data to delete (optional) - public unsafe void Delete(LightningDatabase db, ReadOnlySpan key, ReadOnlySpan value) + public unsafe MDBResultCode Delete(LightningDatabase db, ReadOnlySpan key, ReadOnlySpan value) { if (db == null) throw new ArgumentNullException(nameof(db)); @@ -331,8 +217,12 @@ public unsafe void Delete(LightningDatabase db, ReadOnlySpan key, ReadOnly fixed (byte* valuePtr = value) { var mdbKey = new MDBValue(key.Length, keyPtr); + if (value == null) + { + return mdb_del(_handle, db.Handle(), mdbKey); + } var mdbValue = new MDBValue(value.Length, valuePtr); - mdb_del(_handle, db.Handle(), mdbKey, mdbValue); + return mdb_del(_handle, db.Handle(), mdbKey, mdbValue); } } @@ -346,9 +236,9 @@ public unsafe void Delete(LightningDatabase db, ReadOnlySpan key, ReadOnly /// /// A database handle returned by mdb_dbi_open() /// The key to delete from the database - public void Delete(LightningDatabase db, byte[] key) + public MDBResultCode Delete(LightningDatabase db, byte[] key) { - Delete(db, key.AsSpan()); + return Delete(db, key.AsSpan()); } @@ -362,11 +252,11 @@ public void Delete(LightningDatabase db, byte[] key) /// /// A database handle returned by mdb_dbi_open() /// The key to delete from the database - public unsafe void Delete(LightningDatabase db, ReadOnlySpan key) + public unsafe MDBResultCode Delete(LightningDatabase db, ReadOnlySpan key) { fixed(byte* ptr = key) { var mdbKey = new MDBValue(key.Length, ptr); - mdb_del(_handle, db.Handle(), mdbKey); + return mdb_del(_handle, db.Handle(), mdbKey); } } @@ -385,16 +275,17 @@ public void Reset() /// /// Renew current transaction. /// - public void Renew() + public MDBResultCode Renew() { if (!IsReadOnly) throw new InvalidOperationException("Can't renew non-readonly transaction"); if (State != LightningTransactionState.Reseted) - throw new InvalidOperationException("Transaction should be reseted first"); + throw new InvalidOperationException("Transaction should be reset first"); - mdb_txn_renew(_handle); + var result = mdb_txn_renew(_handle); State = LightningTransactionState.Active; + return result; } /// @@ -402,11 +293,11 @@ public void Renew() /// All cursors opened within the transaction will be closed by this call. /// The cursors and transaction handle will be freed and must not be used again after this call. /// - public void Commit() + public MDBResultCode Commit() { State = LightningTransactionState.Commited; StateChanging?.Invoke(State); - mdb_txn_commit(_handle); + return mdb_txn_commit(_handle); } /// @@ -428,8 +319,7 @@ public void Abort() /// The number of items. public long GetEntriesCount(LightningDatabase db) { - MDBStat stat; - mdb_stat(_handle, db.Handle(), out stat); + mdb_stat(_handle, db.Handle(), out var stat).ThrowOnError(); return stat.ms_entries.ToInt64(); } @@ -453,7 +343,7 @@ public long GetEntriesCount(LightningDatabase db) /// Abort this transaction and deallocate all resources associated with it (including databases). /// /// True if called from Dispose. - protected virtual void Dispose(bool disposing) + private void Dispose(bool disposing) { if (_handle == IntPtr.Zero) return; diff --git a/src/LightningDB/LightningVersionInfo.cs b/src/LightningDB/LightningVersionInfo.cs index cca2174..d85373f 100644 --- a/src/LightningDB/LightningVersionInfo.cs +++ b/src/LightningDB/LightningVersionInfo.cs @@ -10,9 +10,7 @@ public class LightningVersionInfo { internal static LightningVersionInfo Get() { - int minor, major, patch; - var version = mdb_version(out major, out minor, out patch); - + var version = mdb_version(out var major, out var minor, out var patch); return new LightningVersionInfo { Version = Marshal.PtrToStringAnsi(version), @@ -23,7 +21,8 @@ internal static LightningVersionInfo Get() } private LightningVersionInfo() - {} + { + } /// /// Major version number. diff --git a/src/LightningDB/MDBResultCode.cs b/src/LightningDB/MDBResultCode.cs new file mode 100644 index 0000000..6cb35ee --- /dev/null +++ b/src/LightningDB/MDBResultCode.cs @@ -0,0 +1,126 @@ +namespace LightningDB +{ + public enum MDBResultCode : int + { + /// + /// Successful result + /// + Success = 0, + /// + /// key/data pair already exists + /// + KeyExist = -30799, + /// + /// key/data pair not found (EOF) + /// + NotFound = -30798, + /// + /// Requested page not found - this usually indicates corruption + /// + PageNotFound = -30797, + /// + /// Located page was wrong type + /// + Corrupted = -30796, + /// + /// Update of meta page failed or environment had fatal error + /// + Panic = -30795, + /// + /// Environment version mismatch + /// + VersionMismatch = -30794, + /// + /// File is not a valid LMDB file + /// + Invalid = -30793, + /// + /// Environment mapsize reached + /// + MapFull = -30792, + /// + /// Environment maxdbs reached + /// + DbsFull = -30791, + /// + /// Environment maxreaders reached + /// + ReadersFull = -30790, + /// + /// Too many TLS keys in use - Windows only + /// + TLSFull = -30789, + /// + /// Txn has too many dirty pages + /// + TxnFull = -30788, + /// + /// Cursor stack too deep - internal error + /// + CursorFull = -30787, + /// + /// Page has not enough space - internal error + /// + PageFull = -30786, + /// + /// Database contents grew beyond environment mapsize + /// + MapResized = -30785, + /// + /// Operation and DB incompatible, or DB type changed. This can mean: + /// - The operation expects an #MDB_DUPSORT / #MDB_DUPFIXED database. + /// - Opening a named DB when the unnamed DB has #MDB_DUPSORT / #MDB_INTEGERKEY. + /// - Accessing a data record as a database, or vice versa. + /// - The database was dropped and recreated with different flags. + /// + Incompatible = -30784, + /// + /// Invalid reuse of reader locktable slot + /// + BadRSlot = -30783, + /// + /// Transaction must abort, has a child, or is invalid + /// + BadTxn = -30782, + /// + /// Unsupported size of key/DB name/data, or wrong DUPFIXED size + /// + BadValSize = -30781, + /// + /// The specified DBI was changed unexpectedly + /// + BadDBI = -30780, + /// + /// Unexpected problem - txn should abort + /// + Problem = -30779, + /// + /// ENOENT error from C-runtime + /// + FileNotFound = 2, + /// + /// EIO error from C-runtime + /// + AccessDenied = 5, + /// + /// ENOMEM error from C-runtime + /// + InvalidAccess = 12, + /// + /// EACCES error from C-runtime + /// + InvalidData = 13, + /// + /// EBUSY error from C-runtime + /// + CurrentDirectory = 16, + /// + /// EINVAL error from C-runtime + /// + BadCommand = 22, + /// + /// ENOSPC error from C-runtime + /// + OutOfPaper = 28 + } +} \ No newline at end of file diff --git a/src/LightningDB/Native/MDBValue.cs b/src/LightningDB/MDBValue.cs similarity index 81% rename from src/LightningDB/Native/MDBValue.cs rename to src/LightningDB/MDBValue.cs index 4876323..70bc243 100644 --- a/src/LightningDB/Native/MDBValue.cs +++ b/src/LightningDB/MDBValue.cs @@ -1,9 +1,6 @@ using System; -using System.Net.Http.Headers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -namespace LightningDB.Native +namespace LightningDB { /// /// A managed version of the native MDB_val type @@ -17,7 +14,7 @@ public unsafe struct MDBValue { /// /// We only expose this shape constructor to basically force you to use - /// a fixed statment to obtain the pointer. If we accepted a Span or + /// a fixed statement to obtain the pointer. If we accepted a Span or /// ReadOnlySpan here, we would have to do scarier things to pin/unpin /// the buffer. Since this library is geared towards safe and easy usage, /// this way somewhat forces you onto the correct path. @@ -25,7 +22,7 @@ public unsafe struct MDBValue /// /// The length of the buffer /// A pointer to a buffer. - /// The underlying memory may be managed(an array), unmanged or stack-allocated. + /// The underlying memory may be managed(an array), unmanaged or stack-allocated. /// If it is managed, it **MUST** be pinned via either GCHandle.Alloc or a fixed statement /// internal MDBValue(int bufferSize, byte* pinnedOrStackAllocBuffer) @@ -47,8 +44,8 @@ internal MDBValue(int bufferSize, byte* pinnedOrStackAllocBuffer) /// /// Copies the data of the buffer to a new array /// - /// A newly allocated array containing data copied from the dereferenced data pointer - /// Equivilent to AsSpan().ToArray() but makes intent a little more clear + /// A newly allocated array containing data copied from the de-referenced data pointer + /// Equivalent to AsSpan().ToArray() but makes intent a little more clear public byte[] CopyToNewArray() => AsSpan().ToArray(); } } diff --git a/src/LightningDB/Native/Lmdb.cs b/src/LightningDB/Native/Lmdb.cs index f8e4729..2ba3307 100644 --- a/src/LightningDB/Native/Lmdb.cs +++ b/src/LightningDB/Native/Lmdb.cs @@ -1,56 +1,10 @@ using System; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace LightningDB.Native { public static class Lmdb { - /// - /// Txn has too many dirty pages - /// - public const int MDB_TXN_FULL = -30788; - - /// - /// Environment mapsize reached - /// - public const int MDB_MAP_FULL = -30792; - - /// - /// File is not a valid MDB file. - /// - public const int MDB_INVALID = -30793; - - /// - /// Environment version mismatch. - /// - public const int MDB_VERSION_MISMATCH = -30794; - - /// - /// Update of meta page failed, probably I/O error - /// - public const int MDB_PANIC = -30795; - - /// - /// Database contents grew beyond environment mapsize - /// - public const int MDB_MAP_RESIZED = -30785; - - /// - /// Environment maxreaders reached - /// - public const int MDB_READERS_FULL = -30790; - - /// - /// Environment maxdbs reached - /// - public const int MDB_DBS_FULL = -30791; - - /// - /// key/data pair not found (EOF) - /// - public const int MDB_NOTFOUND = -30798; - /// /// Duplicate keys may be used in the database. (Or, from another perspective, keys may have multiple data items, stored in sorted order.) By default keys must be unique and may have only a single data item. /// @@ -61,24 +15,9 @@ public static class Lmdb /// public const int MDB_DUPFIXED = 0x10; - private static int check(int statusCode) - { - if (statusCode != 0) - { - var message = mdb_strerror(statusCode); - throw new LightningException(message, statusCode); - } - return statusCode; - } - - private static int checkRead(int statusCode) - { - return statusCode == MDB_NOTFOUND ? statusCode : check(statusCode); - } - - public static int mdb_env_create(out IntPtr env) + public static MDBResultCode mdb_env_create(out IntPtr env) { - return check(LmdbMethods.mdb_env_create(out env)); + return LmdbMethods.mdb_env_create(out env); } public static void mdb_env_close(IntPtr env) @@ -86,37 +25,34 @@ public static void mdb_env_close(IntPtr env) LmdbMethods.mdb_env_close(env); } - public static int mdb_env_open(IntPtr env, string path, EnvironmentOpenFlags flags, UnixAccessMode mode) + public static MDBResultCode mdb_env_open(IntPtr env, string path, EnvironmentOpenFlags flags, UnixAccessMode mode) { - return check(LmdbMethods.mdb_env_open(env, path, flags, mode)); + return LmdbMethods.mdb_env_open(env, path, flags, mode); } - public static int mdb_env_set_mapsize(IntPtr env, long size) + public static MDBResultCode mdb_env_set_mapsize(IntPtr env, long size) { - return check(LmdbMethods.mdb_env_set_mapsize(env, new IntPtr(size))); + return LmdbMethods.mdb_env_set_mapsize(env, new IntPtr(size)); } - public static int mdb_env_get_maxreaders(IntPtr env, out uint readers) + public static MDBResultCode mdb_env_get_maxreaders(IntPtr env, out uint readers) { - return check(LmdbMethods.mdb_env_get_maxreaders(env, out readers)); + return LmdbMethods.mdb_env_get_maxreaders(env, out readers); } - public static int mdb_env_set_maxreaders(IntPtr env, uint readers) + public static MDBResultCode mdb_env_set_maxreaders(IntPtr env, uint readers) { - return check(LmdbMethods.mdb_env_set_maxreaders(env, readers)); + return LmdbMethods.mdb_env_set_maxreaders(env, readers); } - public static int mdb_env_set_maxdbs(IntPtr env, uint dbs) + public static MDBResultCode mdb_env_set_maxdbs(IntPtr env, uint dbs) { - return check(LmdbMethods.mdb_env_set_maxdbs(env, dbs)); + return LmdbMethods.mdb_env_set_maxdbs(env, dbs); } - public static int mdb_dbi_open(IntPtr txn, string name, DatabaseOpenFlags flags, out uint db) + public static MDBResultCode mdb_dbi_open(IntPtr txn, string name, DatabaseOpenFlags flags, out uint db) { - var statusCode = LmdbMethods.mdb_dbi_open(txn, name, flags, out db); - if (statusCode == MDB_NOTFOUND) - throw new LightningException($"Error opening database {name}: {mdb_strerror(statusCode)}", statusCode); - return check(statusCode); + return LmdbMethods.mdb_dbi_open(txn, name, flags, out db); } public static void mdb_dbi_close(IntPtr env, uint dbi) @@ -124,19 +60,19 @@ public static void mdb_dbi_close(IntPtr env, uint dbi) LmdbMethods.mdb_dbi_close(env, dbi); } - public static int mdb_drop(IntPtr txn, uint dbi, bool del) + public static MDBResultCode mdb_drop(IntPtr txn, uint dbi, bool del) { - return check(LmdbMethods.mdb_drop(txn, dbi, del)); + return LmdbMethods.mdb_drop(txn, dbi, del); } - public static int mdb_txn_begin(IntPtr env, IntPtr parent, TransactionBeginFlags flags, out IntPtr txn) + public static MDBResultCode mdb_txn_begin(IntPtr env, IntPtr parent, TransactionBeginFlags flags, out IntPtr txn) { - return check(LmdbMethods.mdb_txn_begin(env, parent, flags, out txn)); + return LmdbMethods.mdb_txn_begin(env, parent, flags, out txn); } - public static int mdb_txn_commit(IntPtr txn) + public static MDBResultCode mdb_txn_commit(IntPtr txn) { - return check(LmdbMethods.mdb_txn_commit(txn)); + return LmdbMethods.mdb_txn_commit(txn); } public static void mdb_txn_abort(IntPtr txn) @@ -149,9 +85,9 @@ public static void mdb_txn_reset(IntPtr txn) LmdbMethods.mdb_txn_reset(txn); } - public static int mdb_txn_renew(IntPtr txn) + public static MDBResultCode mdb_txn_renew(IntPtr txn) { - return check(LmdbMethods.mdb_txn_renew(txn)); + return LmdbMethods.mdb_txn_renew(txn); } public static IntPtr mdb_version(out int major, out int minor, out int patch) @@ -159,73 +95,59 @@ public static IntPtr mdb_version(out int major, out int minor, out int patch) return LmdbMethods.mdb_version(out major, out minor, out patch); } - private static string mdb_strerror(int err) + public static MDBResultCode mdb_stat(IntPtr txn, uint dbi, out MDBStat stat) { - var ptr = LmdbMethods.mdb_strerror(err); - return Marshal.PtrToStringAnsi(ptr); + return LmdbMethods.mdb_stat(txn, dbi, out stat); } - public static int mdb_stat(IntPtr txn, uint dbi, out MDBStat stat) + public static MDBResultCode mdb_env_copy(IntPtr env, string path) { - return check(LmdbMethods.mdb_stat(txn, dbi, out stat)); + return LmdbMethods.mdb_env_copy(env, path); } - public static int mdb_env_copy(IntPtr env, string path) + public static MDBResultCode mdb_env_copy2(IntPtr env, string path, EnvironmentCopyFlags copyFlags) { - return check(LmdbMethods.mdb_env_copy(env, path)); + return LmdbMethods.mdb_env_copy2(env, path, copyFlags); } - public static int mdb_env_copy2(IntPtr env, string path, EnvironmentCopyFlags copyFlags) + public static MDBResultCode mdb_env_info(IntPtr env, out MDBEnvInfo stat) { - return check(LmdbMethods.mdb_env_copy2(env, path, copyFlags)); + return LmdbMethods.mdb_env_info(env, out stat); } - public static int mdb_env_info(IntPtr env, out MDBEnvInfo stat) + public static MDBResultCode mdb_env_stat(IntPtr env, out MDBStat stat) { - return check(LmdbMethods.mdb_env_info(env, out stat)); + return LmdbMethods.mdb_env_stat(env, out stat); } - public static int mdb_env_stat(IntPtr env, out MDBStat stat) + public static MDBResultCode mdb_env_sync(IntPtr env, bool force) { - return check(LmdbMethods.mdb_env_stat(env, out stat)); + return LmdbMethods.mdb_env_sync(env, force); } - public static int mdb_env_sync(IntPtr env, bool force) + public static MDBResultCode mdb_get(IntPtr txn, uint dbi, ref MDBValue key, out MDBValue value) { - return check(LmdbMethods.mdb_env_sync(env, force)); + return LmdbMethods.mdb_get(txn, dbi, ref key, out value); } - public static int mdb_get(IntPtr txn, uint dbi, ref MDBValue key, out MDBValue value) + public static MDBResultCode mdb_put(IntPtr txn, uint dbi, MDBValue key, MDBValue value, PutOptions flags) { - var result = checkRead(LmdbMethods.mdb_get(txn, dbi, ref key, out var data)); - if (result == MDB_NOTFOUND) - { - value = default; - return result; - } - - value = data; - return result; - } - - public static int mdb_put(IntPtr txn, uint dbi, MDBValue key, MDBValue value, PutOptions flags) - { - return check(LmdbMethods.mdb_put(txn, dbi, ref key, ref value, flags)); + return LmdbMethods.mdb_put(txn, dbi, ref key, ref value, flags); } - public static int mdb_del(IntPtr txn, uint dbi, MDBValue key, MDBValue value) + public static MDBResultCode mdb_del(IntPtr txn, uint dbi, MDBValue key, MDBValue value) { - return check(LmdbMethods.mdb_del(txn, dbi, ref key, ref value)); + return LmdbMethods.mdb_del(txn, dbi, ref key, ref value); } - public static int mdb_del(IntPtr txn, uint dbi, MDBValue key) + public static MDBResultCode mdb_del(IntPtr txn, uint dbi, MDBValue key) { - return check(LmdbMethods.mdb_del(txn, dbi, ref key, IntPtr.Zero)); + return LmdbMethods.mdb_del(txn, dbi, ref key, IntPtr.Zero); } - public static int mdb_cursor_open(IntPtr txn, uint dbi, out IntPtr cursor) + public static MDBResultCode mdb_cursor_open(IntPtr txn, uint dbi, out IntPtr cursor) { - return check(LmdbMethods.mdb_cursor_open(txn, dbi, out cursor)); + return LmdbMethods.mdb_cursor_open(txn, dbi, out cursor); } public static void mdb_cursor_close(IntPtr cursor) @@ -233,36 +155,19 @@ public static void mdb_cursor_close(IntPtr cursor) LmdbMethods.mdb_cursor_close(cursor); } - public static int mdb_cursor_renew(IntPtr txn, IntPtr cursor) - { - return check(LmdbMethods.mdb_cursor_renew(txn, cursor)); - } - - public unsafe static int mdb_cursor_get(IntPtr cursor, byte[] key, out MDBValue newKey, out MDBValue value, - CursorOperation op) - { - value = default; - - fixed (byte* keyPtr = key) - { - newKey = new MDBValue(key.Length, keyPtr); - return checkRead(LmdbMethods.mdb_cursor_get(cursor, ref newKey, ref value, op)); - } - } - - public static int mdb_cursor_get(IntPtr cursor, ref MDBValue key, ref MDBValue value, CursorOperation op) + public static MDBResultCode mdb_cursor_renew(IntPtr txn, IntPtr cursor) { - return checkRead(LmdbMethods.mdb_cursor_get(cursor, ref key, ref value, op)); + return LmdbMethods.mdb_cursor_renew(txn, cursor); } - public static int mdb_cursor_get_multiple(IntPtr cursor, ref MDBValue key, ref MDBValue value, CursorOperation op) + public static MDBResultCode mdb_cursor_get(IntPtr cursor, ref MDBValue key, ref MDBValue value, CursorOperation op) { - return checkRead(LmdbMethods.mdb_cursor_get(cursor, ref key, ref value, op)); + return LmdbMethods.mdb_cursor_get(cursor, ref key, ref value, op); } - public static int mdb_cursor_put(IntPtr cursor, MDBValue key, MDBValue value, CursorPutOptions flags) + public static MDBResultCode mdb_cursor_put(IntPtr cursor, MDBValue key, MDBValue value, CursorPutOptions flags) { - return check(LmdbMethods.mdb_cursor_put(cursor, ref key, ref value, flags)); + return LmdbMethods.mdb_cursor_put(cursor, ref key, ref value, flags); } /// @@ -270,25 +175,25 @@ public static int mdb_cursor_put(IntPtr cursor, MDBValue key, MDBValue value, Cu /// May only be used with MDB_DUPFIXED. /// /// This span must be pinned or stackalloc memory - public unsafe static int mdb_cursor_put(IntPtr cursor, ref MDBValue key, ref Span data, CursorPutOptions flags) + public static MDBResultCode mdb_cursor_put(IntPtr cursor, ref MDBValue key, ref Span data, CursorPutOptions flags) { ref var dataRef = ref MemoryMarshal.GetReference(data); - return check(LmdbMethods.mdb_cursor_put(cursor, ref key, ref dataRef, flags)); + return LmdbMethods.mdb_cursor_put(cursor, ref key, ref dataRef, flags); } - public static int mdb_cursor_del(IntPtr cursor, CursorDeleteOption flags) + public static MDBResultCode mdb_cursor_del(IntPtr cursor, CursorDeleteOption flags) { - return check(LmdbMethods.mdb_cursor_del(cursor, flags)); + return LmdbMethods.mdb_cursor_del(cursor, flags); } - public static int mdb_set_compare(IntPtr txn, uint dbi, CompareFunction cmp) + public static MDBResultCode mdb_set_compare(IntPtr txn, uint dbi, CompareFunction cmp) { - return check(LmdbMethods.mdb_set_compare(txn, dbi, cmp)); + return LmdbMethods.mdb_set_compare(txn, dbi, cmp); } - public static int mdb_set_dupsort(IntPtr txn, uint dbi, CompareFunction cmp) + public static MDBResultCode mdb_set_dupsort(IntPtr txn, uint dbi, CompareFunction cmp) { - return check(LmdbMethods.mdb_set_dupsort(txn, dbi, cmp)); + return LmdbMethods.mdb_set_dupsort(txn, dbi, cmp); } } } diff --git a/src/LightningDB/Native/LmdbMethods.cs b/src/LightningDB/Native/LmdbMethods.cs index 054fe96..d48c724 100644 --- a/src/LightningDB/Native/LmdbMethods.cs +++ b/src/LightningDB/Native/LmdbMethods.cs @@ -8,40 +8,40 @@ internal static class LmdbMethods private const string MDB_DLL_NAME = "lmdb"; [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_env_create(out IntPtr env); + public static extern MDBResultCode mdb_env_create(out IntPtr env); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern void mdb_env_close(IntPtr env); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - internal static extern int mdb_env_open(IntPtr env, string path, EnvironmentOpenFlags flags, UnixAccessMode mode); + internal static extern MDBResultCode mdb_env_open(IntPtr env, string path, EnvironmentOpenFlags flags, UnixAccessMode mode); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_env_set_mapsize(IntPtr env, IntPtr size); + public static extern MDBResultCode mdb_env_set_mapsize(IntPtr env, IntPtr size); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_env_get_maxreaders(IntPtr env, out uint readers); + public static extern MDBResultCode mdb_env_get_maxreaders(IntPtr env, out uint readers); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_env_set_maxreaders(IntPtr env, uint readers); + public static extern MDBResultCode mdb_env_set_maxreaders(IntPtr env, uint readers); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_env_set_maxdbs(IntPtr env, uint dbs); + public static extern MDBResultCode mdb_env_set_maxdbs(IntPtr env, uint dbs); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_dbi_open(IntPtr txn, string name, DatabaseOpenFlags flags, out uint db); + public static extern MDBResultCode mdb_dbi_open(IntPtr txn, string name, DatabaseOpenFlags flags, out uint db); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern void mdb_dbi_close(IntPtr env, uint dbi); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_drop(IntPtr txn, uint dbi, bool del); + public static extern MDBResultCode mdb_drop(IntPtr txn, uint dbi, bool del); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_txn_begin(IntPtr env, IntPtr parent, TransactionBeginFlags flags, out IntPtr txn); + public static extern MDBResultCode mdb_txn_begin(IntPtr env, IntPtr parent, TransactionBeginFlags flags, out IntPtr txn); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_txn_commit(IntPtr txn); + public static extern MDBResultCode mdb_txn_commit(IntPtr txn); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern void mdb_txn_abort(IntPtr txn); @@ -50,7 +50,7 @@ internal static class LmdbMethods public static extern void mdb_txn_reset(IntPtr txn); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_txn_renew(IntPtr txn); + public static extern MDBResultCode mdb_txn_renew(IntPtr txn); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr mdb_version(out int major, out int minor, out int patch); @@ -59,60 +59,60 @@ internal static class LmdbMethods public static extern IntPtr mdb_strerror(int err); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_stat(IntPtr txn, uint dbi, out MDBStat stat); + public static extern MDBResultCode mdb_stat(IntPtr txn, uint dbi, out MDBStat stat); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_env_copy(IntPtr env, string path); + public static extern MDBResultCode mdb_env_copy(IntPtr env, string path); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_env_copy2(IntPtr env, string path, EnvironmentCopyFlags copyFlags); + public static extern MDBResultCode mdb_env_copy2(IntPtr env, string path, EnvironmentCopyFlags copyFlags); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_env_info(IntPtr env, out MDBEnvInfo stat); + public static extern MDBResultCode mdb_env_info(IntPtr env, out MDBEnvInfo stat); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_env_stat(IntPtr env, out MDBStat stat); + public static extern MDBResultCode mdb_env_stat(IntPtr env, out MDBStat stat); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_env_sync(IntPtr env, bool force); + public static extern MDBResultCode mdb_env_sync(IntPtr env, bool force); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_get(IntPtr txn, uint dbi, ref MDBValue key, out MDBValue data); + public static extern MDBResultCode mdb_get(IntPtr txn, uint dbi, ref MDBValue key, out MDBValue data); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_put(IntPtr txn, uint dbi, ref MDBValue key, ref MDBValue data, PutOptions flags); + public static extern MDBResultCode mdb_put(IntPtr txn, uint dbi, ref MDBValue key, ref MDBValue data, PutOptions flags); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_del(IntPtr txn, uint dbi, ref MDBValue key, ref MDBValue data); + public static extern MDBResultCode mdb_del(IntPtr txn, uint dbi, ref MDBValue key, ref MDBValue data); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_del(IntPtr txn, uint dbi, ref MDBValue key, IntPtr data); + public static extern MDBResultCode mdb_del(IntPtr txn, uint dbi, ref MDBValue key, IntPtr data); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_cursor_open(IntPtr txn, uint dbi, out IntPtr cursor); + public static extern MDBResultCode mdb_cursor_open(IntPtr txn, uint dbi, out IntPtr cursor); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern void mdb_cursor_close(IntPtr cursor); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_cursor_renew(IntPtr txn, IntPtr cursor); + public static extern MDBResultCode mdb_cursor_renew(IntPtr txn, IntPtr cursor); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_cursor_get(IntPtr cursor, ref MDBValue key, ref MDBValue data, CursorOperation op); + public static extern MDBResultCode mdb_cursor_get(IntPtr cursor, ref MDBValue key, ref MDBValue data, CursorOperation op); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_cursor_put(IntPtr cursor, ref MDBValue key, ref MDBValue mdbValue, CursorPutOptions flags); + public static extern MDBResultCode mdb_cursor_put(IntPtr cursor, ref MDBValue key, ref MDBValue mdbValue, CursorPutOptions flags); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_cursor_del(IntPtr cursor, CursorDeleteOption flags); + public static extern MDBResultCode mdb_cursor_del(IntPtr cursor, CursorDeleteOption flags); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_set_compare(IntPtr txn, uint dbi, CompareFunction cmp); + public static extern MDBResultCode mdb_set_compare(IntPtr txn, uint dbi, CompareFunction cmp); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_set_dupsort(IntPtr txn, uint dbi, CompareFunction cmp); + public static extern MDBResultCode mdb_set_dupsort(IntPtr txn, uint dbi, CompareFunction cmp); [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int mdb_cursor_put(IntPtr cursor, ref MDBValue key, MDBValue[] value, CursorPutOptions flags); + public static extern MDBResultCode mdb_cursor_put(IntPtr cursor, ref MDBValue key, MDBValue[] value, CursorPutOptions flags); } } \ No newline at end of file diff --git a/src/LightningDB/UnixAccessMode.cs b/src/LightningDB/UnixAccessMode.cs index d9daa65..11c25c2 100644 --- a/src/LightningDB/UnixAccessMode.cs +++ b/src/LightningDB/UnixAccessMode.cs @@ -3,7 +3,7 @@ namespace LightningDB { /// - /// Unix file access privilegies + /// Unix file access privileges /// [Flags] public enum UnixAccessMode : uint diff --git a/src/SecondProcess/Program.cs b/src/SecondProcess/Program.cs index 1a10399..6c91726 100644 --- a/src/SecondProcess/Program.cs +++ b/src/SecondProcess/Program.cs @@ -16,7 +16,8 @@ static void Main(string[] args) using (var tx = env.BeginTransaction(TransactionBeginFlags.ReadOnly)) { using var db = tx.OpenDatabase(); - results = tx.Get(db, Encoding.UTF8.GetBytes("hello")); + var result = tx.Get(db, Encoding.UTF8.GetBytes("hello")); + results = result.value.CopyToNewArray(); tx.Commit(); }