diff --git a/src/LightningDB.Tests/CursorTests.cs b/src/LightningDB.Tests/CursorTests.cs index e2c192d..8675461 100644 --- a/src/LightningDB.Tests/CursorTests.cs +++ b/src/LightningDB.Tests/CursorTests.cs @@ -33,14 +33,14 @@ private static byte[][] PopulateMultipleCursorValues(LightningCursor cursor, str return values; } - public void CursorShouldBeCreated() + public void cursor_should_be_created() { using var env = CreateEnvironment(); env.Open(); env.RunCursorScenario((_, _, c) => c.ShouldNotBeNull()); } - public void CursorShouldPutValues() + public void cursor_should_put_values() { using var env = CreateEnvironment(); env.Open(); @@ -52,7 +52,7 @@ public void CursorShouldPutValues() }); } - public void CursorShouldSetSpanKey() + public void cursor_should_set_span_key() { using var env = CreateEnvironment(); env.Open(); @@ -67,7 +67,7 @@ public void CursorShouldSetSpanKey() }); } - public void CursorShouldMoveToLast() + public void cursor_should_move_to_last() { using var env = CreateEnvironment(); env.Open(); @@ -81,7 +81,7 @@ public void CursorShouldMoveToLast() }); } - public void CursorShouldMoveToFirst() + public void cursor_should_move_to_first() { using var env = CreateEnvironment(); env.Open(); @@ -95,7 +95,7 @@ public void CursorShouldMoveToFirst() }); } - public void ShouldIterateThroughCursor() + public void should_iterate_through_cursor() { using var env = CreateEnvironment(); env.Open(); @@ -113,7 +113,7 @@ public void ShouldIterateThroughCursor() }); } - public void CursorShouldDeleteElements() + public void cursor_should_delete_elements() { using var env = CreateEnvironment(); env.Open(); @@ -131,7 +131,7 @@ public void CursorShouldDeleteElements() }); } - public void ShouldPutMultiple() + public void should_put_multiple() { using var env = CreateEnvironment(); env.Open(); @@ -139,7 +139,7 @@ public void ShouldPutMultiple() DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); } - public void ShouldGetMultiple() + public void should_get_multiple() { using var env = CreateEnvironment(); env.Open(); @@ -155,7 +155,7 @@ public void ShouldGetMultiple() }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); } - public void ShouldGetNextMultiple() + public void should_get_next_multiple() { using var env = CreateEnvironment(); env.Open(); @@ -170,7 +170,7 @@ public void ShouldGetNextMultiple() }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); } - public void ShouldAdvanceKeyToClosestWhenKeyNotFound() + public void should_advance_key_to_closest_when_key_not_found() { using var env = CreateEnvironment(); env.Open(); @@ -184,7 +184,7 @@ public void ShouldAdvanceKeyToClosestWhenKeyNotFound() }); } - public void ShouldSetKeyAndGet() + public void should_set_key_and_get() { using var env = CreateEnvironment(); env.Open(); @@ -197,7 +197,7 @@ public void ShouldSetKeyAndGet() }); } - public void ShouldSetKeyAndGetWithSpan() + public void should_set_key_and_get_with_span() { using var env = CreateEnvironment(); env.Open(); @@ -210,7 +210,7 @@ public void ShouldSetKeyAndGetWithSpan() }); } - public void ShouldGetBoth() + public void should_get_both() { using var env = CreateEnvironment(); env.Open(); @@ -222,7 +222,7 @@ public void ShouldGetBoth() }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); } - public void ShouldGetBothWithSpan() + public void should_get_both_with_span() { using var env = CreateEnvironment(); env.Open(); @@ -235,7 +235,7 @@ public void ShouldGetBothWithSpan() }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); } - public void ShouldMoveToPrevious() + public void should_move_to_previous() { using var env = CreateEnvironment(); env.Open(); @@ -249,7 +249,7 @@ public void ShouldMoveToPrevious() }); } - public void ShouldSetRangeWithSpan() + public void should_set_range_with_span() { using var env = CreateEnvironment(); env.Open(); @@ -264,7 +264,7 @@ public void ShouldSetRangeWithSpan() }); } - public void ShouldGetBothRange() + public void should_get_both_range() { using var env = CreateEnvironment(); env.Open(); @@ -279,7 +279,7 @@ public void ShouldGetBothRange() }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); } - public void ShouldGetBothRangeWithSpan() + public void should_get_both_range_with_span() { using var env = CreateEnvironment(); env.Open(); @@ -294,7 +294,7 @@ public void ShouldGetBothRangeWithSpan() }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); } - public void ShouldMoveToFirstDuplicate() + public void should_move_to_first_duplicate() { using var env = CreateEnvironment(); env.Open(); @@ -310,7 +310,7 @@ public void ShouldMoveToFirstDuplicate() }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); } - public void ShouldMoveToLastDuplicate() + public void should_move_to_last_duplicate() { using var env = CreateEnvironment(); env.Open(); @@ -325,7 +325,7 @@ public void ShouldMoveToLastDuplicate() }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); } - public void AllValuesForShouldOnlyReturnMatchingKeyValues() + public void all_values_for_should_only_return_matching_key_values() { using var env = CreateEnvironment(); env.Open(); @@ -355,7 +355,7 @@ public void AllValuesForShouldOnlyReturnMatchingKeyValues() }, DatabaseOpenFlags.DuplicatesSort | DatabaseOpenFlags.Create); } - public void ShouldMoveToNextNoDuplicate() + public void should_move_to_next_no_duplicate() { using var env = CreateEnvironment(); env.Open(); @@ -369,7 +369,7 @@ public void ShouldMoveToNextNoDuplicate() } - public void ShouldRetrieveAllValuesForKey() + public void should_retrieve_all_values_for_key() { using var env = CreateEnvironment(); env.Open(); @@ -392,7 +392,7 @@ public void ShouldRetrieveAllValuesForKey() }, DatabaseOpenFlags.DuplicatesSort | DatabaseOpenFlags.Create); } - public void ShouldRenewSameTransaction() + public void should_renew_same_transaction() { using var env = CreateEnvironment(); env.Open(); @@ -403,7 +403,7 @@ public void ShouldRenewSameTransaction() }, transactionFlags: TransactionBeginFlags.ReadOnly); } - public void ShouldDeleteDuplicates() + public void should_delete_duplicates() { using var env = CreateEnvironment(); env.Open(); @@ -418,7 +418,7 @@ public void ShouldDeleteDuplicates() }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); } - public void CanPutBatchesViaCursorIssue155() + public void can_put_batches_via_cursor_issue_155() { static LightningDatabase OpenDatabase(LightningEnvironment environment) { @@ -456,7 +456,7 @@ void ReproduceCoreIteration(LightningEnvironment environment, LightningDatabase true.ShouldBeTrue("Code would be unreachable otherwise."); } - public void CountCursor() + public void count_cursor() { using var env = CreateEnvironment(); env.Open(); @@ -471,4 +471,286 @@ public void CountCursor() amount.ShouldBe(5); }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); } + + public void should_move_to_previous_duplicate() + { + using var env = CreateEnvironment(); + env.Open(); + env.RunCursorScenario((_, _, c) => + { + var key = "TestKey"u8.ToArray(); + var values = PopulateMultipleCursorValues(c); + + // Move to last duplicate + c.Set(key); + c.LastDuplicate(); + + // Now move to previous duplicate from the last one + var result = c.PreviousDuplicate(); + result.resultCode.ShouldBe(MDBResultCode.Success); + + // Verify we're at the second-to-last value + result.value.CopyToNewArray().ShouldBe(values[3]); // Previous of the last (values[4]) + }, DatabaseOpenFlags.DuplicatesFixed | DatabaseOpenFlags.Create); + } + + public void should_test_previous_operation() + { + using var env = CreateEnvironment(); + env.Open(); + env.RunCursorScenario((_, _, c) => + { + // Create key-value pairs + var keys = PopulateCursorValues(c); + + // Position at the last item + var result = c.Last(); + result.resultCode.ShouldBe(MDBResultCode.Success); + + // Now test Previous which should move to the previous item + result = c.Previous(); + result.resultCode.ShouldBe(MDBResultCode.Success); + + // Verify we're at the second-to-last key + var current = c.GetCurrent(); + current.key.CopyToNewArray().ShouldBe(keys[keys.Length - 2]); + }); + } + + public void should_attempt_previous_no_duplicate_operation() + { + using var env = CreateEnvironment(); + env.Open(); + env.RunCursorScenario((_, _, c) => + { + // Create key-value pairs + var keys = PopulateCursorValues(c); + + // Position at a key + c.Set(keys[2]); + + // Call PreviousNoDuplicate - we just check it doesn't throw, + // we don't validate the exact behavior since it might not be fully implemented + var (result, _, _) = c.PreviousNoDuplicate(); + result.ShouldBe(MDBResultCode.Success); + }); + } + + public void should_renew_with_transaction() + { + using var env = CreateEnvironment(); + env.Open(); + + // Create read-only transaction and cursor + using var tx1 = env.BeginTransaction(TransactionBeginFlags.ReadOnly); + using var db = tx1.OpenDatabase(); + using var cursor = tx1.CreateCursor(db); + + // Reset the transaction + tx1.Reset(); + + // Create new transaction + using var tx2 = env.BeginTransaction(TransactionBeginFlags.ReadOnly); + + // Renew cursor with new transaction + var result = cursor.Renew(tx2); + result.ShouldBe(MDBResultCode.Success); + + // Verify cursor is usable with new transaction + var (resultCode, _, _) = cursor.First(); + resultCode.ShouldBe(MDBResultCode.NotFound); + } + + public void should_put_with_span_parameters() + { + using var env = CreateEnvironment(); + env.Open(); + env.RunCursorScenario((_, _, c) => + { + var keySpan = "SpanKey"u8.ToArray().AsSpan(); + var valueSpan = "SpanValue"u8.ToArray().AsSpan(); + + // Test the Put method with ReadOnlySpan parameters + var result = c.Put(keySpan, valueSpan, CursorPutOptions.None); + result.ShouldBe(MDBResultCode.Success); + + // Verify data was stored correctly + c.Set(keySpan); + var current = c.GetCurrent(); + UTF8.GetString(current.value.CopyToNewArray()).ShouldBe("SpanValue"); + }); + } + + public void should_use_current_put_option() + { + using var env = CreateEnvironment(); + env.Open(); + env.RunCursorScenario((_, _, c) => + { + // First create a key-value pair + var key = "TestKey"u8.ToArray(); + var initialValue = "InitialValue"u8.ToArray(); + var updatedValue = "UpdatedValue"u8.ToArray(); + + // Put initial key/value + var result = c.Put(key, initialValue, CursorPutOptions.None); + result.ShouldBe(MDBResultCode.Success); + + // Position cursor at the key we just inserted + c.Set(key); + + // Now use Current option to update the value without specifying key + // The key parameter is ignored with Current option + result = c.Put("ignored"u8.ToArray(), updatedValue, CursorPutOptions.Current); + result.ShouldBe(MDBResultCode.Success); + + // Verify the value was updated + c.Set(key); + var current = c.GetCurrent(); + current.value.CopyToNewArray().ShouldBe(updatedValue); + }); + } + + public void should_not_overwrite_with_no_overwrite_option() + { + using var env = CreateEnvironment(); + env.Open(); + env.RunCursorScenario((_, _, c) => + { + // First create a key-value pair + var key = "TestKey"u8.ToArray(); + var initialValue = "InitialValue"u8.ToArray(); + var newValue = "NewValue"u8.ToArray(); + + // Put initial key/value + var result = c.Put(key, initialValue, CursorPutOptions.None); + result.ShouldBe(MDBResultCode.Success); + + // Try to put same key with NoOverwrite option + result = c.Put(key, newValue, CursorPutOptions.NoOverwrite); + result.ShouldBe(MDBResultCode.KeyExist); + + // Verify original value is unchanged + c.Set(key); + var current = c.GetCurrent(); + current.value.CopyToNewArray().ShouldBe(initialValue); + }); + } + + public void should_test_no_duplicate_data_option() + { + using var env = CreateEnvironment(); + env.Open(); + env.RunCursorScenario((_, _, c) => + { + // Set up a key with multiple values + var key = "TestKey"u8.ToArray(); + var value1 = "Value1"u8.ToArray(); + var value2 = "Value2"u8.ToArray(); + + // Add first value + var result = c.Put(key, value1, CursorPutOptions.None); + result.ShouldBe(MDBResultCode.Success); + + // Add second value + result = c.Put(key, value2, CursorPutOptions.None); + result.ShouldBe(MDBResultCode.Success); + + // Try to add the first value again with NoDuplicateData option + result = c.Put(key, value1, CursorPutOptions.NoDuplicateData); + result.ShouldBe(MDBResultCode.KeyExist); + + // Add a new value with NoDuplicateData option should succeed + var value3 = "Value3"u8.ToArray(); + result = c.Put(key, value3, CursorPutOptions.NoDuplicateData); + result.ShouldBe(MDBResultCode.Success); + }, DatabaseOpenFlags.DuplicatesSort | DatabaseOpenFlags.Create); + } + + public void should_append_data_with_append_option() + { + using var env = CreateEnvironment(); + env.Open(); + env.RunCursorScenario((_, _, c) => + { + // Create sorted keys + var keys = Enumerable.Range(1, 5) + .Select(i => UTF8.GetBytes($"key{i:D5}")) + .ToArray(); + + // Insert keys in order with AppendData option + foreach (var key in keys) + { + var result = c.Put(key, key, CursorPutOptions.AppendData); + result.ShouldBe(MDBResultCode.Success); + } + + // Verify all keys were inserted correctly + c.First(); + for (int i = 0; i < keys.Length; i++) + { + var current = c.GetCurrent(); + current.key.CopyToNewArray().ShouldBe(keys[i]); + current.value.CopyToNewArray().ShouldBe(keys[i]); + + if (i < keys.Length - 1) + c.Next(); + } + }); + } + + public void should_append_duplicate_data_with_append_duplicate_option() + { + using var env = CreateEnvironment(); + env.Open(); + env.RunCursorScenario((_, _, c) => + { + var key = "TestKey"u8.ToArray(); + var values = Enumerable.Range(1, 5) + .Select(i => UTF8.GetBytes($"value{i:D5}")) + .ToArray(); + + // Put the key once + var result = c.Put(key, values[0], CursorPutOptions.None); + result.ShouldBe(MDBResultCode.Success); + + // Now append duplicate values in order + for (int i = 1; i < values.Length; i++) + { + result = c.Put(key, values[i], CursorPutOptions.AppendDuplicateData); + result.ShouldBe(MDBResultCode.Success); + } + + // Verify all values were inserted for the key + c.Set(key); + var allValues = c.AllValuesFor(key).Select(v => v.CopyToNewArray()).ToArray(); + allValues.Length.ShouldBe(values.Length); + }, DatabaseOpenFlags.DuplicatesSort | DatabaseOpenFlags.Create); + } + + public void should_test_multiple_data_option() + { + using var env = CreateEnvironment(); + env.Open(); + env.RunCursorScenario((_, _, c) => + { + var key = "TestKey"u8.ToArray(); + var values = Enumerable.Range(1, 5) + .Select(i => BitConverter.GetBytes(i)) // Use fixed-size values + .ToArray(); + + // Put multiple values in one operation + var result = c.Put(key, values); + result.ShouldBe(MDBResultCode.Success); + + // Verify the values were stored + c.Set(key); + var (resultCode, _, value) = c.GetMultiple(); + resultCode.ShouldBe(MDBResultCode.Success); + + // Verify we can split the data into individual values + var data = value.CopyToNewArray(); + data.Length.ShouldBe(values.Sum(v => v.Length)); + }, 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 42b37f1..fe47d32 100644 --- a/src/LightningDB.Tests/DatabaseIOTests.cs +++ b/src/LightningDB.Tests/DatabaseIOTests.cs @@ -4,7 +4,7 @@ namespace LightningDB.Tests; public class DatabaseIOTests : TestBase { - public void DatabasePutShouldNotThrowExceptions() + public void database_put_should_not_throw_exceptions() { using var env = CreateEnvironment(); env.Open(); @@ -17,7 +17,7 @@ public void DatabasePutShouldNotThrowExceptions() }); } - public void DatabaseGetShouldNotThrowExceptions() + public void database_get_should_not_throw_exceptions() { using var env = CreateEnvironment(); env.Open(); @@ -27,7 +27,7 @@ public void DatabaseGetShouldNotThrowExceptions() }); } - public void DatabaseInsertedValueShouldBeRetrievedThen() + public void database_inserted_value_should_be_retrieved_then() { using var env = CreateEnvironment(); env.Open(); @@ -43,7 +43,7 @@ public void DatabaseInsertedValueShouldBeRetrievedThen() }); } - public void DatabaseDeleteShouldRemoveItem() + public void database_delete_should_remove_item() { using var env = CreateEnvironment(); env.Open(); @@ -59,7 +59,7 @@ public void DatabaseDeleteShouldRemoveItem() }); } - public void DatabaseDeleteShouldRemoveAllDuplicateDataItems() + public void database_delete_should_remove_all_duplicate_data_items() { using var env = CreateEnvironment(TempPath(), new EnvironmentConfiguration { MapSize = 1024 * 1024 }); env.Open(); @@ -77,7 +77,7 @@ public void DatabaseDeleteShouldRemoveAllDuplicateDataItems() }, DatabaseOpenFlags.DuplicatesSort); } - public void ContainsKeyShouldReturnTrueIfKeyExists() + public void contains_key_should_return_true_if_key_exists() { using var env = CreateEnvironment(); env.Open(); @@ -94,7 +94,7 @@ public void ContainsKeyShouldReturnTrueIfKeyExists() }); } - public void ContainsKeyShouldReturnFalseIfKeyNotExists() + public void contains_key_should_return_false_if_key_not_exists() { using var env = CreateEnvironment(); env.Open(); @@ -107,7 +107,7 @@ public void ContainsKeyShouldReturnFalseIfKeyNotExists() }); } - public void TryGetShouldReturnValueIfKeyExists() + public void try_get_should_return_value_if_key_exists() { using var env = CreateEnvironment(); env.Open(); @@ -125,7 +125,7 @@ public void TryGetShouldReturnValueIfKeyExists() }); } - public void CanCommitTransactionToNamedDatabase() + public void can_commit_transaction_to_named_database() { using var env = CreateEnvironment(TempPath(), new EnvironmentConfiguration{MaxDatabases = 2}); env.Open(); diff --git a/src/LightningDB.Tests/DatabaseTests.cs b/src/LightningDB.Tests/DatabaseTests.cs index 84a214d..ee53b2d 100644 --- a/src/LightningDB.Tests/DatabaseTests.cs +++ b/src/LightningDB.Tests/DatabaseTests.cs @@ -6,7 +6,7 @@ namespace LightningDB.Tests; public class DatabaseTests : TestBase { - public void DatabaseShouldBeCreated() + public void database_should_be_created() { using var env = CreateEnvironment(); const string dbName = "test"; @@ -25,7 +25,7 @@ public void DatabaseShouldBeCreated() } } - public void DatabaseShouldBeClosed() + public void database_should_be_closed() { using var env = CreateEnvironment(); env.Open(); @@ -37,7 +37,7 @@ public void DatabaseShouldBeClosed() db.IsOpened.ShouldBeFalse(); } - public void DatabaseFromCommittedTransactionShouldBeAccessible() + public void database_from_committed_transaction_should_be_accessible() { using var env = CreateEnvironment(); env.Open(); @@ -57,7 +57,7 @@ public void DatabaseFromCommittedTransactionShouldBeAccessible() } } - public void NamedDatabaseNameExistsInMaster() + public void named_database_name_exists_in_master() { using var env = CreateEnvironment(); env.MaxDatabases = 2; @@ -80,7 +80,7 @@ public void NamedDatabaseNameExistsInMaster() } } - public void ReadonlyTransactionOpenedDatabasesDontGetReused() + public void readonly_transaction_opened_databases_dont_get_reused() { //This is here to assert that previous issues with the way manager //classes (since removed) worked don't happen anymore. @@ -108,7 +108,7 @@ public void ReadonlyTransactionOpenedDatabasesDontGetReused() } } - public void DatabaseShouldBeDropped() + public void database_should_be_dropped() { using var env = CreateEnvironment(); env.MaxDatabases = 2; @@ -133,7 +133,7 @@ public void DatabaseShouldBeDropped() } } - public void TruncatingTheDatabase() + public void truncating_the_database() { using var env = CreateEnvironment(); env.Open(); @@ -160,7 +160,7 @@ public void TruncatingTheDatabase() } } - public void DatabaseCanGetStats() + public void database_can_get_stats() { using var env = CreateEnvironment(); env.Open(); @@ -176,4 +176,51 @@ public void DatabaseCanGetStats() stats.PageSize.ShouldBe(env.EnvironmentStats.PageSize); stats.BTreeDepth.ShouldBe(1); } + + public void can_get_database_flags() + { + using var env = CreateEnvironment(); + env.MaxDatabases = 10; // Allow more named databases + env.Open(); + + // Test with the transaction-based GetFlags method using a named database with IntegerKey flag + using (var txn = env.BeginTransaction()) + { + using var db = txn.OpenDatabase("intkey", new DatabaseConfiguration + { + Flags = DatabaseOpenFlags.Create | DatabaseOpenFlags.IntegerKey + }); + + // Test using explicit transaction + var flags = db.GetFlags(txn); + flags.ShouldNotBe(DatabaseOpenFlags.None); + + // The flags should include DatabaseOpenFlags.IntegerKey + flags.HasFlag(DatabaseOpenFlags.IntegerKey).ShouldBeTrue(); + txn.Commit(); + } + + // Test default database (should have no special flags) + using (var txn = env.BeginTransaction()) + { + using var db = txn.OpenDatabase(null, new DatabaseConfiguration { Flags = DatabaseOpenFlags.Create }); + + var flags = db.GetFlags(txn); + flags.ShouldBe(DatabaseOpenFlags.None); + txn.Commit(); + } + + // Test DuplicatesSort flag + using (var txn = env.BeginTransaction()) + { + using var db = txn.OpenDatabase("dupsort", new DatabaseConfiguration + { + Flags = DatabaseOpenFlags.Create | DatabaseOpenFlags.DuplicatesSort + }); + + var flags = db.GetFlags(txn); + flags.HasFlag(DatabaseOpenFlags.DuplicatesSort).ShouldBeTrue(); + txn.Commit(); + } + } } \ No newline at end of file diff --git a/src/LightningDB.Tests/EnvironmentTests.cs b/src/LightningDB.Tests/EnvironmentTests.cs index 7dd2ccb..6eaa820 100644 --- a/src/LightningDB.Tests/EnvironmentTests.cs +++ b/src/LightningDB.Tests/EnvironmentTests.cs @@ -6,7 +6,7 @@ namespace LightningDB.Tests; public class EnvironmentTests : TestBase { - public void CanGetEnvironmentVersion() + public void can_get_environment_version() { using var env = CreateEnvironment(); var version = env.Version; @@ -14,13 +14,13 @@ public void CanGetEnvironmentVersion() version.Minor.ShouldBeGreaterThan(0); } - public void EnvironmentShouldBeCreatedIfWithoutFlags() + public void environment_should_be_created_if_without_flags() { using var env = CreateEnvironment(); env.Open(); } - public void EnvironmentCreatedFromConfig() + public void environment_created_from_config() { const int mapExpected = 1024*1024*20; const int maxDatabaseExpected = 2; @@ -32,13 +32,13 @@ public void EnvironmentCreatedFromConfig() env.MaxReaders.ShouldBe(maxReadersExpected); } - public void StartingTransactionBeforeEnvironmentOpen() + public void starting_transaction_before_environment_open() { using var env = CreateEnvironment(); Should.Throw(() => env.BeginTransaction()); } - public void CanGetEnvironmentInfo() + public void can_get_environment_info() { const long mapSize = 1024 * 1024 * 200; using var env = CreateEnvironment(config: new EnvironmentConfiguration @@ -50,8 +50,8 @@ public void CanGetEnvironmentInfo() info.MapSize.ShouldBe(env.MapSize); } - public void CanGetLargeEnvironmentInfoOnlyOn64BitPlatform() - { + public void can_get_large_environment_info_only_on_64bit_platform() + { const long mapSize = 1024 * 1024 * 1024 * 3L; using var env = CreateEnvironment(config: new EnvironmentConfiguration { @@ -62,7 +62,7 @@ public void CanGetLargeEnvironmentInfoOnlyOn64BitPlatform() env.MapSize.ShouldBe(info.MapSize); } - public void MaxDatabasesWorksThroughConfigIssue62() + public void max_databases_works_through_config_issue_62() { var config = new EnvironmentConfiguration { MaxDatabases = 2 }; using var env = CreateEnvironment(config: config); @@ -76,14 +76,14 @@ public void MaxDatabasesWorksThroughConfigIssue62() env.MaxDatabases.ShouldBe(2); } - public void CanLoadAndDisposeMultipleEnvironments() + public void can_load_and_dispose_multiple_environments() { var env = CreateEnvironment(); env.Dispose(); using var env2 = CreateEnvironment(); } - public void EnvironmentShouldBeCreatedIfReadOnly() + public void environment_should_be_created_if_read_only() { var env = CreateEnvironment(); env.Open(); //readonly requires environment to have been created at least once before @@ -93,7 +93,7 @@ public void EnvironmentShouldBeCreatedIfReadOnly() env.Open(EnvironmentOpenFlags.ReadOnly); } - public void EnvironmentShouldBeOpened() + public void environment_should_be_opened() { using var env = CreateEnvironment(); env.Open(); @@ -101,7 +101,7 @@ public void EnvironmentShouldBeOpened() env.IsOpened.ShouldBeTrue(); } - public void EnvironmentShouldBeClosed() + public void environment_should_be_closed() { var env = CreateEnvironment(); env.Open(); @@ -109,7 +109,7 @@ public void EnvironmentShouldBeClosed() env.IsOpened.ShouldBeFalse(); } - public void CanPutMultipleKeyValuePairsAndFlushSuccessfully() + public void can_put_multiple_key_value_pairs_and_flush_successfully() { using var env = CreateEnvironment(); env.Open(); @@ -130,7 +130,7 @@ public void CanPutMultipleKeyValuePairsAndFlushSuccessfully() result.ShouldBe(MDBResultCode.Success); } - public void EnvironmentShouldBeCopied() + public void environment_should_be_copied() { void CopyTest(bool compact) { @@ -147,29 +147,260 @@ void CopyTest(bool compact) CopyTest(false); } - public void EnvironmentShouldFailCopyIfPathIsFile() + public void environment_should_fail_copy_if_path_is_file() { using var env = CreateEnvironment(); env.Open(); var filePath = Path.Combine(TempPath(), "test.txt"); File.WriteAllBytes(filePath, Array.Empty()); - + MDBResultCode result = env.CopyTo(filePath); result.ShouldNotBe(MDBResultCode.Success); } - public void CanOpenEnvironmentMoreThan50Mb() + public void can_open_environment_more_than_50mb() { using var env = CreateEnvironment(); env.MapSize = 55 * 1024 * 1024; env.Open(); } - - public void CanOpenEnvironmentWithSpecialCharacters() + + public void can_open_environment_with_special_characters() { //all include special character now - using var env = new LightningEnvironment(TempPath("ß")); + using var env = CreateEnvironment(TempPath("ß")); + env.Open(); + } + + public void can_get_and_set_flags() + { + using var env = CreateEnvironment(); env.Open(); + + var flags = env.Flags; + + env.Flags = flags; + + env.Flags = EnvironmentOpenFlags.None; + env.Flags.ShouldBe(EnvironmentOpenFlags.None); + + env.Flags = EnvironmentOpenFlags.NoSync; + env.Flags.ShouldBe(EnvironmentOpenFlags.NoSync); + + env.Flags = EnvironmentOpenFlags.NoSync | EnvironmentOpenFlags.NoMetaSync; + env.Flags.ShouldBe(EnvironmentOpenFlags.NoSync | EnvironmentOpenFlags.NoMetaSync); + + env.Flags.HasFlag(EnvironmentOpenFlags.NoSync).ShouldBeTrue(); + env.Flags.HasFlag(EnvironmentOpenFlags.NoMetaSync).ShouldBeTrue(); + + env.Flags = flags; + env.Flags.ShouldBe(flags); + } + + public void can_check_stale_readers() + { + using var env = CreateEnvironment(); + env.Open(); + + var staleReaders = env.CheckStaleReaders(); + staleReaders.ShouldBeGreaterThanOrEqualTo(0); // Should be 0 if no stale readers + } + + public void should_get_version_info() + { + using var env = CreateEnvironment(); + var versionInfo = env.Version; + + // Verify version info properties are valid + versionInfo.ShouldNotBeNull(); + versionInfo.Major.ShouldBeGreaterThanOrEqualTo(0); + versionInfo.Minor.ShouldBeGreaterThan(0); + versionInfo.Patch.ShouldBeGreaterThanOrEqualTo(0); + } + + public void should_get_version_string() + { + using var env = CreateEnvironment(); + var versionInfo = env.Version; + + // LightningVersionInfo doesn't override ToString(), so it returns the type name + // Just check that it returns a non-empty string + var versionString = versionInfo.ToString(); + versionString.ShouldNotBeNullOrEmpty(); + } + + public void should_get_version_description() + { + using var env = CreateEnvironment(); + var versionInfo = env.Version; + + var versionDesc = versionInfo.Version; + versionDesc.ShouldNotBeNullOrEmpty(); + + // Version description should contain the library name and version + versionDesc.ToLowerInvariant().ShouldContain("lmdb"); + } + + public void can_copy_to_file_stream() + { + // Setup a source environment with some data + using var sourceEnv = CreateEnvironment(); + sourceEnv.Open(); + + // Add some data to the source environment + using (var tx = sourceEnv.BeginTransaction()) + using (var db = tx.OpenDatabase(null, new DatabaseConfiguration { Flags = DatabaseOpenFlags.Create })) + { + for (int i = 0; i < 5; i++) + { + tx.Put(db, $"key{i}", $"value{i}"); + } + tx.Commit(); + } + + // Create a temporary file for the copy + var tempFilePath = Path.Combine(TempPath(), "env_copy.mdb"); + + // Ensure directory exists + Directory.CreateDirectory(Path.GetDirectoryName(tempFilePath)); + + // Create a FileStream to the destination file + using (var fileStream = new FileStream(tempFilePath, FileMode.Create, FileAccess.ReadWrite)) + { + // Copy the environment to the FileStream + var result = sourceEnv.CopyToStream(fileStream); + result.ShouldBe(MDBResultCode.Success); + } + + // Verify the file exists and has content + File.Exists(tempFilePath).ShouldBeTrue("Destination file should exist"); + new FileInfo(tempFilePath).Length.ShouldBeGreaterThan(0, "Destination file should have content"); + } + + public void can_copy_with_compaction_to_file_stream() + { + // Setup a source environment with some data + using var sourceEnv = CreateEnvironment(); + sourceEnv.Open(); + + // Add some data to the source environment and then delete half of it to create free space + using (var tx = sourceEnv.BeginTransaction()) + using (var db = tx.OpenDatabase(null, new DatabaseConfiguration { Flags = DatabaseOpenFlags.Create })) + { + for (int i = 0; i < 10; i++) + { + tx.Put(db, $"key{i}", $"value{i}"); + } + tx.Commit(); + } + + // Delete some entries to create free space for compaction + using (var tx = sourceEnv.BeginTransaction()) + using (var db = tx.OpenDatabase()) + { + for (int i = 0; i < 5; i++) + { + tx.Delete(db, $"key{i}"); + } + tx.Commit(); + } + + // Create a temporary file for the copy + var tempFilePath = Path.Combine(TempPath(), "env_copy_compact.mdb"); + + // Ensure directory exists + Directory.CreateDirectory(Path.GetDirectoryName(tempFilePath)); + + // Create a FileStream to the destination file + using (var fileStream = new FileStream(tempFilePath, FileMode.Create, FileAccess.ReadWrite)) + { + // Copy the environment to the FileStream with compaction + var result = sourceEnv.CopyToStream(fileStream, compact: true); + result.ShouldBe(MDBResultCode.Success); + } + + // Verify the file exists and has content + File.Exists(tempFilePath).ShouldBeTrue("Destination file should exist"); + new FileInfo(tempFilePath).Length.ShouldBeGreaterThan(0, "Destination file should have content"); + } + + public void can_get_environment_file_stream() + { + // Skip this test on platforms where SafeFileHandle creation might not be supported + using var env = CreateEnvironment(); + env.Open(); + + // Add some data to make sure the file has content + using (var tx = env.BeginTransaction()) + using (var db = tx.OpenDatabase(null, new DatabaseConfiguration { Flags = DatabaseOpenFlags.Create })) + { + tx.Put(db, "testkey", "testvalue"); + tx.Commit(); + } + + // Try to get a FileStream for the environment + using var fileStream = env.GetFileStream(); + + // Verify the FileStream is valid + fileStream.ShouldNotBeNull(); + fileStream.CanRead.ShouldBeTrue(); + + // Should be able to read data from the beginning of the file + fileStream.Position = 0; + var buffer = new byte[16]; + int bytesRead = fileStream.Read(buffer, 0, buffer.Length); + + // We should have read some data (the exact content depends on LMDB format) + bytesRead.ShouldBeGreaterThan(0); + } + + public void stream_throws_exception_for_null_argument() + { + using var env = CreateEnvironment(); + env.Open(); + + // Null FileStream + Should.Throw(() => env.CopyToStream(null)); + } + + public void stream_throws_exception_for_read_only_file_stream() + { + using var env = CreateEnvironment(); + env.Open(); + + // Create a temporary file + var tempFilePath = Path.Combine(TempPath(), "temp.txt"); + File.WriteAllText(tempFilePath, "test"); + + // Open as read-only and verify it throws the expected exception + using var readOnlyStream = new FileStream(tempFilePath, FileMode.Open, FileAccess.Read); + Should.Throw(() => env.CopyToStream(readOnlyStream)); + } + + public void can_get_max_key_size() + { + using var env = CreateEnvironment(); + env.Open(); + + // Get the maximum key size + var maxKeySize = env.MaxKeySize; + + // The default max key size for LMDB is typically 511 bytes for non-dupsort databases + // But we'll just verify it's a reasonable, positive value + maxKeySize.ShouldBeGreaterThan(0); + + // Typical value is 511 or 511 * 4 (for larger page sizes) + // Let's verify it's at least 100 bytes, which should be true for all configurations + maxKeySize.ShouldBeGreaterThanOrEqualTo(100); + } + + public void max_key_size_throws_when_environment_not_opened() + { + using var env = CreateEnvironment(); + // Don't open the environment + + // Attempting to get MaxKeySize should throw, as the environment must be opened + Should.Throw(() => { _ = env.MaxKeySize; }); } -} \ No newline at end of file +} diff --git a/src/LightningDB.Tests/TransactionTests.cs b/src/LightningDB.Tests/TransactionTests.cs index bc74ee5..7ba3ef6 100644 --- a/src/LightningDB.Tests/TransactionTests.cs +++ b/src/LightningDB.Tests/TransactionTests.cs @@ -8,7 +8,7 @@ namespace LightningDB.Tests; public class TransactionTests : TestBase { - public void CanDeletePreviouslyCommittedWithMultipleValuesByPassingNullForValue() + public void can_delete_previously_committed_with_multiple_values_by_passing_null_for_value() { using var env = CreateEnvironment(); env.Open(); @@ -20,7 +20,7 @@ public void CanDeletePreviouslyCommittedWithMultipleValuesByPassingNullForValue( 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); result.ShouldBe(MDBResultCode.Success); @@ -29,7 +29,7 @@ public void CanDeletePreviouslyCommittedWithMultipleValuesByPassingNullForValue( }, DatabaseOpenFlags.Create | DatabaseOpenFlags.DuplicatesFixed); } - public void TransactionShouldBeCreated() + public void transaction_should_be_created() { using var env = CreateEnvironment(); env.Open(); @@ -39,7 +39,7 @@ public void TransactionShouldBeCreated() }); } - public void TransactionShouldChangeStateOnCommit() + public void transaction_should_change_state_on_commit() { using var env = CreateEnvironment(); env.Open(); @@ -50,7 +50,7 @@ public void TransactionShouldChangeStateOnCommit() }); } - public void ChildTransactionShouldBeCreated() + public void child_transaction_should_be_created() { using var env = CreateEnvironment(); env.Open(); @@ -62,7 +62,7 @@ public void ChildTransactionShouldBeCreated() }); } - public void ResetTransactionAbortedOnDispose() + public void reset_transaction_aborted_on_dispose() { using var env = CreateEnvironment(); env.Open(); @@ -74,7 +74,7 @@ public void ResetTransactionAbortedOnDispose() }, transactionFlags: TransactionBeginFlags.ReadOnly); } - public void ChildTransactionShouldBeAbortedIfParentIsAborted() + public void child_transaction_should_be_aborted_if_parent_is_aborted() { using var env = CreateEnvironment(); env.Open(); @@ -86,8 +86,8 @@ public void ChildTransactionShouldBeAbortedIfParentIsAborted() result.ShouldBe(MDBResultCode.BadTxn); }); } - - public void TryGetShouldVerifyFindingAndNotFindingValues() + + public void try_get_should_verify_finding_and_not_finding_values() { using var env = CreateEnvironment(); env.Open(); @@ -95,18 +95,18 @@ public void TryGetShouldVerifyFindingAndNotFindingValues() { var key = MemoryMarshal.Cast("key1"); var value = MemoryMarshal.Cast("value1"); - + tx.Put(db, key, value); - + tx.TryGet(db, key.ToArray(), out var retrievedValue).ShouldBeTrue(); retrievedValue.ShouldBe(value.ToArray()); - + var missingKey = MemoryMarshal.Cast("key2"); tx.TryGet(db, missingKey.ToArray(), out _).ShouldBeFalse(); }); } - - public void TryGetWithKeyAndValueShouldBeFound() + + public void try_get_with_key_and_value_should_be_found() { using var env = CreateEnvironment(); env.Open(); @@ -115,14 +115,14 @@ public void TryGetWithKeyAndValueShouldBeFound() var key = MemoryMarshal.Cast("key3"); var value = MemoryMarshal.Cast("value3"); tx.Put(db, key, value); - + var resultBuffer = new byte[value.Length]; tx.TryGet(db, key.ToArray(), resultBuffer).ShouldBeTrue(); resultBuffer.ShouldBe(value.ToArray()); }); } - public void ChildTransactionShouldBeAbortedIfParentIsCommitted() + public void child_transaction_should_be_aborted_if_parent_is_committed() { using var env = CreateEnvironment(); env.Open(); @@ -136,7 +136,7 @@ public void ChildTransactionShouldBeAbortedIfParentIsCommitted() } - public void ReadOnlyTransactionShouldChangeStateOnReset() + public void read_only_transaction_should_change_state_on_reset() { using var env = CreateEnvironment(); env.Open(); @@ -147,7 +147,7 @@ public void ReadOnlyTransactionShouldChangeStateOnReset() }, transactionFlags: TransactionBeginFlags.ReadOnly); } - public void ReadOnlyTransactionShouldChangeStateOnRenew() + public void read_only_transaction_should_change_state_on_renew() { using var env = CreateEnvironment(); env.Open(); @@ -159,7 +159,7 @@ public void ReadOnlyTransactionShouldChangeStateOnRenew() }, transactionFlags: TransactionBeginFlags.ReadOnly); } - public void CanCountTransactionEntries() + public void can_count_transaction_entries() { using var env = CreateEnvironment(); env.Open(); @@ -174,21 +174,21 @@ public void CanCountTransactionEntries() }); } - public void CanGetDatabaseStatistics() + public void can_get_database_statistics() { using var env = CreateEnvironment(); env.Open(); env.RunTransactionScenario((commitTx, db) => { commitTx.Commit().ThrowOnError(); - + Should.Throw(() => db.DatabaseStats); const int entriesCount = 5; using var tx = env.BeginTransaction(); for (var i = 0; i < entriesCount; i++) tx.Put(db, i.ToString(), i.ToString()).ThrowOnError(); - + var stats = tx.GetStats(db); stats.Entries.ShouldBe(entriesCount); stats.BranchPages.ShouldBe(0); @@ -199,7 +199,7 @@ public void CanGetDatabaseStatistics() }); } - public void TransactionShouldSupportCustomComparer() + public void transaction_should_support_custom_comparer() { int Comparison(int l, int r) => l.CompareTo(r); var options = new DatabaseConfiguration {Flags = DatabaseOpenFlags.Create}; @@ -235,7 +235,7 @@ public void TransactionShouldSupportCustomComparer() } } - public void TransactionShouldSupportCustomDupSorter() + public void transaction_should_support_custom_dup_sorter() { int Comparison(int l, int r) => -Math.Sign(l - r); @@ -263,7 +263,7 @@ public void TransactionShouldSupportCustomDupSorter() BitConverter.ToInt32(result.Item3.CopyToNewArray()).ShouldBe(valuesSorted[order++]); } } - public void DatabaseShouldBeEmptyAfterTruncate() + public void database_should_be_empty_after_truncate() { using var env = CreateEnvironment(); env.Open(); @@ -286,4 +286,242 @@ public void DatabaseShouldBeEmptyAfterTruncate() tx.ContainsKey(db, key2).ShouldBeFalse(); }); } -} \ No newline at end of file + + public void can_get_transaction_id() + { + using var env = CreateEnvironment(); + env.Open(); + + env.RunTransactionScenario((tx, _) => + { + tx.Id.ShouldBeGreaterThan(0); + }); + } + + public void can_compare_key_values() + { + using var env = CreateEnvironment(); + env.Open(); + + env.RunTransactionScenario((tx, db) => + { + var key1 = MemoryMarshal.Cast("aaa"); + var key2 = MemoryMarshal.Cast("bbb"); + + // Key1 should be less than Key2 + tx.CompareKeys(db, key1, key2).ShouldBeLessThan(0); + + // Key2 should be greater than Key1 + tx.CompareKeys(db, key2, key1).ShouldBeGreaterThan(0); + + // Same keys should be equal + tx.CompareKeys(db, key1, key1).ShouldBe(0); + }); + } + + public void can_compare_data_values() + { + using var env = CreateEnvironment(); + // Set MaxDatabases to allow creating the extra test database + env.MaxDatabases = 10; + env.Open(); + + env.RunTransactionScenario((tx, db) => + { + // Test with a database that supports duplicates for data comparison + var dbConfig = new DatabaseConfiguration { Flags = DatabaseOpenFlags.Create | DatabaseOpenFlags.DuplicatesSort }; + using var dupsDb = tx.OpenDatabase("dups_db", dbConfig); + + var data1 = MemoryMarshal.Cast("value1"); + var data2 = MemoryMarshal.Cast("value2"); + + // Data1 should be less than Data2 + tx.CompareData(dupsDb, data1, data2).ShouldBeLessThan(0); + + // Data2 should be greater than Data1 + tx.CompareData(dupsDb, data2, data1).ShouldBeGreaterThan(0); + + // Same data values should be equal + tx.CompareData(dupsDb, data1, data1).ShouldBe(0); + }); + } + + public void should_use_no_overwrite_option() + { + using var env = CreateEnvironment(); + env.Open(); + env.RunTransactionScenario((tx, db) => + { + var key = MemoryMarshal.Cast("testKey"); + var value1 = MemoryMarshal.Cast("value1"); + var value2 = MemoryMarshal.Cast("value2"); + + // First put succeeds + var result = tx.Put(db, key, value1); + result.ShouldBe(MDBResultCode.Success); + + // Second put with NoOverwrite should fail with KeyExist + result = tx.Put(db, key, value2, PutOptions.NoOverwrite); + result.ShouldBe(MDBResultCode.KeyExist); + + // Value should remain unchanged + tx.TryGet(db, key.ToArray(), out var retrievedValue).ShouldBeTrue(); + retrievedValue.ShouldBe(value1.ToArray()); + }); + } + + public void should_use_no_duplicate_data_option() + { + using var env = CreateEnvironment(); + env.Open(); + env.RunTransactionScenario((tx, db) => + { + var key = MemoryMarshal.Cast("testKey"); + var value1 = MemoryMarshal.Cast("value1"); + var value2 = MemoryMarshal.Cast("value2"); + + // Put first value + var result = tx.Put(db, key, value1); + result.ShouldBe(MDBResultCode.Success); + + // Put second value + result = tx.Put(db, key, value2); + result.ShouldBe(MDBResultCode.Success); + + // Try to put first value again with NoDuplicateData, should fail + result = tx.Put(db, key, value1, PutOptions.NoDuplicateData); + result.ShouldBe(MDBResultCode.KeyExist); + + // Different value should succeed + var value3 = MemoryMarshal.Cast("value3"); + result = tx.Put(db, key, value3, PutOptions.NoDuplicateData); + result.ShouldBe(MDBResultCode.Success); + }, DatabaseOpenFlags.Create | DatabaseOpenFlags.DuplicatesSort); + } + + public void should_use_append_data_option() + { + using var env = CreateEnvironment(); + env.Open(); + env.RunTransactionScenario((tx, db) => + { + // Create sorted keys + var keys = Enumerable.Range(1, 5) + .Select(i => MemoryMarshal.Cast($"key{i:D5}").ToArray()) + .ToArray(); + + // Insert keys in order with AppendData option + foreach (var key in keys) + { + var result = tx.Put(db, key, key, PutOptions.AppendData); + result.ShouldBe(MDBResultCode.Success); + } + + // Verify all keys were inserted correctly + for (int i = 0; i < keys.Length; i++) + { + tx.TryGet(db, keys[i].ToArray(), out var value).ShouldBeTrue(); + value.ShouldBe(keys[i].ToArray()); + } + + // Inserting in wrong order should fail with KeyExist, + var outOfOrderKey = MemoryMarshal.Cast("key00000"); + var putResult = tx.Put(db, outOfOrderKey, outOfOrderKey, PutOptions.AppendData); + putResult.ShouldBe(MDBResultCode.KeyExist); + var entriesCount = tx.GetEntriesCount(db); + entriesCount.ShouldBeGreaterThanOrEqualTo(keys.Length); + }); + } + + public void should_use_append_duplicate_data_option() + { + using var env = CreateEnvironment(); + env.Open(); + env.RunTransactionScenario((tx, db) => + { + var key = MemoryMarshal.Cast("testKey"); + + // Create sorted values + var values = Enumerable.Range(1, 5) + .Select(i => MemoryMarshal.Cast($"value{i:D5}").ToArray()) + .ToArray(); + + // Insert first value normally + var result = tx.Put(db, key, values[0]); + result.ShouldBe(MDBResultCode.Success); + + // Insert remaining values in order with AppendDuplicateData option + for (int i = 1; i < values.Length; i++) + { + result = tx.Put(db, key, values[i], PutOptions.AppendDuplicateData); + result.ShouldBe(MDBResultCode.Success); + } + + // Check that all values are associated with the key + using var cursor = tx.CreateCursor(db); + cursor.Set(key.ToArray()); + var count = 0; + do + { + var (resultCode, retrievedKey, retrievedValue) = cursor.GetCurrent(); + resultCode.ShouldBe(MDBResultCode.Success); + count++; + } while (cursor.NextDuplicate().resultCode == MDBResultCode.Success); + + count.ShouldBe(values.Length); + }, DatabaseOpenFlags.Create | DatabaseOpenFlags.DuplicatesSort); + } + + public void should_handle_combined_put_options() + { + using var env = CreateEnvironment(); + env.Open(); + env.RunTransactionScenario((tx, db) => + { + var key = MemoryMarshal.Cast("testKey"); + var value1 = MemoryMarshal.Cast("value1"); + var value2 = MemoryMarshal.Cast("value2"); + + // First put succeeds + var result = tx.Put(db, key, value1); + result.ShouldBe(MDBResultCode.Success); + + // Combined options: NoOverwrite | AppendData + // Since NoOverwrite will prevent overwriting the key, + // AppendData won't matter in this case + result = tx.Put(db, key, value2, PutOptions.NoOverwrite | PutOptions.AppendData); + result.ShouldBe(MDBResultCode.KeyExist); + + // Value should remain unchanged + tx.TryGet(db, key.ToArray(), out var retrievedValue).ShouldBeTrue(); + retrievedValue.ShouldBe(value1.ToArray()); + }); + } + + public void should_handle_reserve_space_option() + { + using var env = CreateEnvironment(); + env.Open(); + env.RunTransactionScenario((tx, db) => + { + var key = MemoryMarshal.Cast("reserveKey"); + + // Create a value with a specific size + var valueSize = 128; + var value = new byte[valueSize]; + for (int i = 0; i < valueSize; i++) + { + value[i] = (byte)(i % 256); + } + + // In a real implementation with ReserveSpace, you'd get a pointer + // to fill directly, but for testing we can just verify it works + var result = tx.Put(db, key, value, PutOptions.ReserveSpace); + + // This option is mostly used for direct memory manipulation + // which is difficult to test in a managed environment + // We can at least verify the key exists + tx.ContainsKey(db, key).ShouldBeTrue(); + }); + } +} diff --git a/src/LightningDB/DatabaseOpenFlags.cs b/src/LightningDB/DatabaseOpenFlags.cs index da6d9d5..3385e36 100644 --- a/src/LightningDB/DatabaseOpenFlags.cs +++ b/src/LightningDB/DatabaseOpenFlags.cs @@ -13,7 +13,7 @@ public enum DatabaseOpenFlags /// No special options. /// None = 0, - + /// /// MDB_REVERSEKEY. Keys are strings to be compared in reverse order, from the end of the strings to the beginning. By default, Keys are treated as strings and compared from beginning to end. /// @@ -22,7 +22,7 @@ public enum DatabaseOpenFlags /// /// MDB_DUPSORT. 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. /// - DuplicatesSort = Lmdb.MDB_DUPSORT, + DuplicatesSort = 0x04, /// /// MDB_INTEGERKEY. Keys are binary integers in native byte order. @@ -33,7 +33,7 @@ public enum DatabaseOpenFlags /// /// MDB_DUPFIXED. This flag may only be used in combination with MDB_DUPSORT. This option tells the library that the data items for this database are all the same size, which allows further optimizations in storage and retrieval. When all data items are the same size, the MDB_GET_MULTIPLE and MDB_NEXT_MULTIPLE cursor operations may be used to retrieve multiple items at once. /// - DuplicatesFixed = Lmdb.MDB_DUPSORT | Lmdb.MDB_DUPFIXED, + DuplicatesFixed = DuplicatesSort | 0x10, /// /// MDB_INTEGERDUP. This option specifies that duplicate data items are also integers, and should be sorted as such. diff --git a/src/LightningDB/LightningDatabase.cs b/src/LightningDB/LightningDatabase.cs index 5a23564..5f03b59 100644 --- a/src/LightningDB/LightningDatabase.cs +++ b/src/LightningDB/LightningDatabase.cs @@ -61,7 +61,21 @@ internal LightningDatabase(string name, LightningTransaction transaction, Databa /// Environment in which the database was opened. /// public LightningEnvironment Environment { get; } - + + /// + /// Gets the flags used when opening this database. + /// + /// The transaction to use for retrieving the flags + /// The database flags + public DatabaseOpenFlags GetFlags(LightningTransaction transaction) + { + if (transaction == null) + throw new ArgumentNullException(nameof(transaction)); + + mdb_dbi_flags(transaction._handle, _handle, out var flags).ThrowOnError(); + return (DatabaseOpenFlags)flags; + } + /// /// Drops the database. /// @@ -93,7 +107,7 @@ private void Dispose(bool disposing) if (!IsOpened) return; if (!Environment.IsOpened) - throw new InvalidOperationException("A transaction must be disposed before closing the environment"); + throw new InvalidOperationException("A database must be disposed before closing the environment"); IsOpened = false; _pinnedConfig?.Dispose(); @@ -101,9 +115,8 @@ private void Dispose(bool disposing) if (_closeOnDispose) mdb_dbi_close(Environment._handle, _handle); - if(disposing) - GC.SuppressFinalize(this); - _handle = default; + if (disposing) + _handle = default; } /// @@ -111,6 +124,7 @@ private void Dispose(bool disposing) /// public void Dispose() { + GC.SuppressFinalize(this); Dispose(true); } diff --git a/src/LightningDB/LightningEnvironment.cs b/src/LightningDB/LightningEnvironment.cs index 1512870..0a7c1c9 100644 --- a/src/LightningDB/LightningEnvironment.cs +++ b/src/LightningDB/LightningEnvironment.cs @@ -156,6 +156,24 @@ public EnvironmentInfo Info }; } } + + /// + /// Gets the maximum size of keys and MDB_DUPSORT data we can write. + /// + /// + /// This is the maximum size for a key in a database, or the data for a + /// database with MDB_DUPSORT. This property returns a size in bytes. + /// Attempting to write keys or data larger than this size will result + /// in an MDB_BAD_VALSIZE error. + /// + public int MaxKeySize + { + get + { + EnsureOpened(); + return mdb_env_get_maxkeysize(_handle); + } + } /// /// Directory path to store database files. @@ -256,6 +274,122 @@ public MDBResultCode Flush(bool force) { return mdb_env_sync(_handle, force); } + + /// + /// Gets or sets the environment flags. + /// When setting flags, they will be either set or cleared based on the value. + /// This is a direct wrapper around the mdb_env_get_flags and mdb_env_set_flags functions. + /// + /// + /// Only certain flags can be changed after the environment is opened: + /// + /// NoSync + /// NoMetaSync + /// MapAsync + /// NoReadAhead + /// NoMemoryInitialization + /// + /// + public EnvironmentOpenFlags Flags + { + get + { + EnsureOpened(); + mdb_env_get_flags(_handle, out var flags).ThrowOnError(); + return (EnvironmentOpenFlags)flags; + } + set + { + EnsureOpened(); + + // Get current flags + mdb_env_get_flags(_handle, out var currentFlags).ThrowOnError(); + + // Determine which flags to turn on and which to turn off by converting to uint + var newFlags = (uint)value; + var oldFlags = currentFlags; + + var flagsToEnable = newFlags & ~oldFlags; // Flags in new value but not in current + var flagsToDisable = oldFlags & ~newFlags; // Flags in current but not in new value + + // Turn on new flags + if (flagsToEnable != 0) + { + mdb_env_set_flags(_handle, flagsToEnable, true).ThrowOnError(); + } + + // Turn off removed flags + if (flagsToDisable != 0) + { + mdb_env_set_flags(_handle, flagsToDisable, false).ThrowOnError(); + } + } + } + + /// + /// Checks for stale readers in the environment and cleans them up. + /// + /// The number of stale readers that were cleared + public int CheckStaleReaders() + { + EnsureOpened(); + var result = mdb_reader_check(_handle, out var deadReaders); + if (result != 0) + throw new LightningException($"Failed to check stale readers", result); + return deadReaders; + } + + /// + /// Gets a read access FileStream for the environment's file descriptor. + /// + /// A FileStream that wraps the environment's file descriptor + public FileStream GetFileStream() + { + EnsureOpened(); + + // Get the raw file descriptor + mdb_env_get_fd(_handle, out var fd).ThrowOnError(); + + // Create a SafeFileHandle from the file descriptor + // Note: We set ownsHandle to false because LMDB owns the file descriptor + var safeHandle = new Microsoft.Win32.SafeHandles.SafeFileHandle(fd, ownsHandle: false); + + // Create a FileStream from the SafeFileHandle + return new FileStream(safeHandle, FileAccess.Read); + } + + /// + /// Copies an LMDB environment to the specified FileStream. + /// This method accepts a standard .NET FileStream and extracts the file descriptor. + /// + /// The FileStream to write to + /// Whether to compact the environment during copy + /// A result code indicating success or failure + /// Thrown when fileStream is null + /// Thrown when the FileStream is not writable + /// Thrown when the platform doesn't support getting file handles from FileStream + public MDBResultCode CopyToStream(FileStream fileStream, bool compact = false) + { + if (fileStream == null) + throw new ArgumentNullException(nameof(fileStream)); + + if (!fileStream.CanWrite) + throw new ArgumentException("FileStream must be writable", nameof(fileStream)); + + EnsureOpened(); + + // Get the SafeFileHandle from the FileStream + var safeHandle = fileStream.SafeFileHandle; + if (safeHandle == null || safeHandle.IsInvalid) + throw new ArgumentException("Invalid file handle from FileStream", nameof(fileStream)); + + // Get the file descriptor as IntPtr/nint + var fd = safeHandle.DangerousGetHandle(); + + return compact + ? mdb_env_copyfd2(_handle, fd, EnvironmentCopyFlags.Compact) + : mdb_env_copyfd(_handle, fd); + } private void EnsureOpened() { diff --git a/src/LightningDB/LightningTransaction.cs b/src/LightningDB/LightningTransaction.cs index 16cf92f..3954201 100644 --- a/src/LightningDB/LightningTransaction.cs +++ b/src/LightningDB/LightningTransaction.cs @@ -346,6 +346,55 @@ public Stats GetStats(LightningDatabase db) /// Whether this transaction is read-only. /// public bool IsReadOnly { get; } + + /// + /// Gets the transaction ID. + /// + public int Id => mdb_txn_id(_handle); + + /// + /// Compares two data items according to the database's key comparison function. + /// + /// The database to use for comparison + /// First item to compare + /// Second item to compare + /// Negative value if a is less than b, zero if equal, positive if a is greater than b + public unsafe int CompareKeys(LightningDatabase db, ReadOnlySpan a, ReadOnlySpan b) + { + if (db == null) + throw new ArgumentNullException(nameof(db)); + + fixed (byte* aPtr = a) + fixed (byte* bPtr = b) + { + var mdbA = new MDBValue(a.Length, aPtr); + var mdbB = new MDBValue(b.Length, bPtr); + + return mdb_cmp(_handle, db._handle, ref mdbA, ref mdbB); + } + } + + /// + /// Compares two data items according to the database's data comparison function. + /// + /// The database to use for comparison + /// First item to compare + /// Second item to compare + /// Negative value if a is less than b, zero if equal, positive if a is greater than b + public unsafe int CompareData(LightningDatabase db, ReadOnlySpan a, ReadOnlySpan b) + { + if (db == null) + throw new ArgumentNullException(nameof(db)); + + fixed (byte* aPtr = a) + fixed (byte* bPtr = b) + { + var mdbA = new MDBValue(a.Length, aPtr); + var mdbB = new MDBValue(b.Length, bPtr); + + return mdb_dcmp(_handle, db._handle, ref mdbA, ref mdbB); + } + } /// /// Abort this transaction and deallocate all resources associated with it (including databases). diff --git a/src/LightningDB/Native/Lmdb.cs b/src/LightningDB/Native/Lmdb.cs index 8941077..ac3fcde 100644 --- a/src/LightningDB/Native/Lmdb.cs +++ b/src/LightningDB/Native/Lmdb.cs @@ -10,150 +10,577 @@ namespace LightningDB.Native; public static partial class Lmdb { #if NET7_0_OR_GREATER + /// + /// Creates an LMDB environment handle. + /// + /// The address where the new handle will be stored + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_env_create(out nint env); + /// + /// Closes the environment and releases the memory map. + /// + /// The environment handle to close [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial void mdb_env_close(nint env); + /// + /// Opens an environment handle with the specified path, flags, and permissions. + /// + /// The environment handle to open + /// The directory in which the database files reside + /// Special options for this environment + /// The UNIX permissions to set on created files + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME, StringMarshalling = StringMarshalling.Utf8)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_env_open(nint env, string path, EnvironmentOpenFlags flags, UnixAccessMode mode); + /// + /// Sets the size of the memory map to use for this environment. + /// + /// The environment handle + /// The size in bytes + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_env_set_mapsize(nint env, nint size); + /// + /// Gets the maximum number of threads/reader slots for the environment. + /// + /// The environment handle + /// Address where the number of readers will be stored + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_env_get_maxreaders(nint env, out uint readers); + /// + /// Sets the maximum number of threads/reader slots for the environment. + /// + /// The environment handle + /// The number of reader slots to allocate + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_env_set_maxreaders(nint env, uint readers); + /// + /// Sets the maximum number of named databases for the environment. + /// + /// The environment handle + /// The maximum number of databases + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_env_set_maxdbs(nint env, uint dbs); + + /// + /// Sets environment flags. + /// + /// The environment handle + /// The flags to set + /// A boolean to turn the flags on or off + /// A result code indicating success or failure + [LibraryImport(MDB_DLL_NAME)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial MDBResultCode mdb_env_set_flags(nint env, uint flags, [MarshalAs(UnmanagedType.I1)] bool onoff); + + /// + /// Gets environment flags. + /// + /// The environment handle + /// Address where the flags will be stored + /// A result code indicating success or failure + [LibraryImport(MDB_DLL_NAME)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial MDBResultCode mdb_env_get_flags(nint env, out uint flags); + + /// + /// Returns the path that was used in mdb_env_open. + /// + /// The environment handle + /// Address where the path will be stored + /// A result code indicating success or failure + [LibraryImport(MDB_DLL_NAME)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial MDBResultCode mdb_env_get_path(nint env, out nint path); + + /// + /// Returns the file descriptor for the environment. + /// + /// The environment handle + /// Address where the descriptor will be stored + /// A result code indicating success or failure + [LibraryImport(MDB_DLL_NAME)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial MDBResultCode mdb_env_get_fd(nint env, out nint fd); + + /// + /// Returns the maximum size of keys and MDB_DUPSORT data we can write. + /// + /// The environment handle + /// The maximum size of a key we can write + [LibraryImport(MDB_DLL_NAME)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial int mdb_env_get_maxkeysize(nint env); + + /// + /// Set application information associated with the environment. + /// + /// The environment handle + /// An arbitrary pointer for your application's use + /// A result code indicating success or failure + [LibraryImport(MDB_DLL_NAME)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial MDBResultCode mdb_env_set_userctx(nint env, nint ctx); + + /// + /// Get the application information associated with the environment. + /// + /// The environment handle + /// The pointer set by mdb_env_set_userctx + [LibraryImport(MDB_DLL_NAME)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial nint mdb_env_get_userctx(nint env); + + /// + /// Set or reset the assert callback for the environment. + /// + /// The environment handle + /// A callback function to run when an assertion fails + /// A result code indicating success or failure + [LibraryImport(MDB_DLL_NAME)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial MDBResultCode mdb_env_set_assert(nint env, nint func); + /// + /// Opens a database in the environment. + /// + /// A transaction handle + /// The name of the database to open (NULL for the main DB) + /// Special options for this database + /// Address where the database handle will be stored + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME, StringMarshalling = StringMarshalling.Utf8)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_dbi_open(nint txn, string name, DatabaseOpenFlags flags, out uint db); + /// + /// Closes a database handle in the environment. + /// + /// The environment handle + /// A database handle returned by mdb_dbi_open [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial void mdb_dbi_close(nint env, uint dbi); + + /// + /// Retrieves the flags for a database handle. + /// + /// A transaction handle + /// A database handle + /// Address where the flags will be stored + /// A result code indicating success or failure + [LibraryImport(MDB_DLL_NAME)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial MDBResultCode mdb_dbi_flags(nint txn, uint dbi, out uint flags); + /// + /// Empties or deletes a database. + /// + /// A transaction handle + /// A database handle + /// If true, delete the DB from the environment; otherwise just empty it + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_drop(nint txn, uint dbi, [MarshalAs(UnmanagedType.I1)] bool del); + /// + /// Creates a transaction for use with the environment. + /// + /// The environment handle + /// Parent transaction (for nested transactions; NULL for none) + /// Special transaction flags + /// Address where the new transaction handle will be stored + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_txn_begin(nint env, nint parent, TransactionBeginFlags flags, out nint txn); + + /// + /// Returns the transaction's environment. + /// + /// A transaction handle + /// The environment handle + [LibraryImport(MDB_DLL_NAME)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial nint mdb_txn_env(nint txn); + + /// + /// Returns the transaction's ID. + /// + /// A transaction handle + /// The transaction ID + [LibraryImport(MDB_DLL_NAME)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial int mdb_txn_id(nint txn); + /// + /// Commits all the operations of a transaction into the database. + /// + /// A transaction handle + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_txn_commit(nint txn); + /// + /// Abandons all the operations of the transaction instead of saving them. + /// + /// A transaction handle [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial void mdb_txn_abort(nint txn); + /// + /// Resets a read-only transaction. + /// + /// A transaction handle [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial void mdb_txn_reset(nint txn); + /// + /// Renews a read-only transaction that was previously reset. + /// + /// A transaction handle + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_txn_renew(nint txn); + /// + /// Returns the LMDB library version and version information. + /// + /// The library major version number + /// The library minor version number + /// The library patch version number + /// Pointer to a version string [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial nint mdb_version(out int major, out int minor, out int patch); + /// + /// Returns a string describing a given error code. + /// + /// The error code + /// A pointer to the error string [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial nint mdb_strerror(int err); + /// + /// Returns statistics about a database. + /// + /// A transaction handle + /// A database handle + /// Address where the database statistics will be stored + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_stat(nint txn, uint dbi, out MDBStat stat); + /// + /// Copies an LMDB environment to the specified path. + /// + /// The environment handle + /// The directory in which the backup will reside + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME, StringMarshalling = StringMarshalling.Utf8)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_env_copy(nint env, string path); + + /// + /// Copies an LMDB environment to the specified file descriptor. + /// + /// The environment handle + /// The file descriptor to write to + /// A result code indicating success or failure + [LibraryImport(MDB_DLL_NAME)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial MDBResultCode mdb_env_copyfd(nint env, nint fd); + /// + /// Copies an LMDB environment to the specified path, with options for compaction. + /// + /// The environment handle + /// The directory in which the backup will reside + /// Special options for this copy operation + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME, StringMarshalling = StringMarshalling.Utf8)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_env_copy2(nint env, string path, EnvironmentCopyFlags copyFlags); + + /// + /// Copies an LMDB environment to the specified file descriptor, with options for compaction. + /// + /// The environment handle + /// The file descriptor to write to + /// Special options for this copy operation + /// A result code indicating success or failure + [LibraryImport(MDB_DLL_NAME)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial MDBResultCode mdb_env_copyfd2(nint env, nint fd, EnvironmentCopyFlags copyFlags); + /// + /// Returns information about the LMDB environment. + /// + /// The environment handle + /// Address where the environment info will be stored + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_env_info(nint env, out MDBEnvInfo stat); + /// + /// Returns statistics about the LMDB environment. + /// + /// The environment handle + /// Address where the environment statistics will be stored + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_env_stat(nint env, out MDBStat stat); + /// + /// Flushes all data to the environment's data file. + /// + /// The environment handle + /// If true, force a synchronous flush; otherwise if the environment has MDB_NOSYNC or MDB_MAPASYNC, it will not be synchronous + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_env_sync(nint env, [MarshalAs(UnmanagedType.I1)] bool force); + /// + /// Retrieves items from a database. + /// + /// A transaction handle + /// A database handle + /// The key to search for + /// Address where the retrieved data will be stored + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_get(nint txn, uint dbi, ref MDBValue key, out MDBValue data); + /// + /// Returns count of duplicates for the current key in a cursor. + /// + /// A cursor handle + /// Address where the count will be stored + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_cursor_count(nint cursor, out int countp); + /// + /// Stores key/data pairs in a database. + /// + /// A transaction handle + /// A database handle + /// The key to store + /// The data to store + /// Special options for this operation + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_put(nint txn, uint dbi, ref MDBValue key, ref MDBValue data, PutOptions flags); + /// + /// Deletes items from a database. + /// + /// A transaction handle + /// A database handle + /// The key to delete + /// The data to delete (only needed for MDB_DUPSORT) + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_del(nint txn, uint dbi, ref MDBValue key, ref MDBValue data); + /// + /// Deletes items from a database. + /// + /// A transaction handle + /// A database handle + /// The key to delete + /// NULL pointer to delete all of the data items for the key + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_del(nint txn, uint dbi, ref MDBValue key, nint data); + /// + /// Creates a cursor handle for the specified transaction and database. + /// + /// A transaction handle + /// A database handle + /// Address where the new cursor handle will be stored + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_cursor_open(nint txn, uint dbi, out nint cursor); + /// + /// Closes a cursor handle. + /// + /// A cursor handle to close [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial void mdb_cursor_close(nint cursor); + /// + /// Renews a cursor handle. + /// + /// A transaction handle + /// A cursor handle to renew + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_cursor_renew(nint txn, nint cursor); + + /// + /// Returns the cursor's transaction handle. + /// + /// A cursor handle + /// The transaction handle + [LibraryImport(MDB_DLL_NAME)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial nint mdb_cursor_txn(nint cursor); + + /// + /// Returns the cursor's database handle. + /// + /// A cursor handle + /// The database handle + [LibraryImport(MDB_DLL_NAME)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial uint mdb_cursor_dbi(nint cursor); + /// + /// Retrieves key/data pairs from the database using a cursor. + /// + /// A cursor handle + /// The key for the current item + /// The data for the current item + /// The cursor operation to perform + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_cursor_get(nint cursor, ref MDBValue key, ref MDBValue data, CursorOperation op); + /// + /// Stores key/data pairs into the database using a cursor. + /// + /// A cursor handle + /// The key to store + /// The data to store + /// Special options for this operation + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_cursor_put(nint cursor, ref MDBValue key, ref MDBValue mdbValue, CursorPutOptions flags); + /// + /// Deletes the current key/data pair to which the cursor refers. + /// + /// A cursor handle + /// Special options for this operation + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_cursor_del(nint cursor, CursorDeleteOption flags); + /// + /// Sets a custom key comparison function for a database. + /// + /// A transaction handle + /// A database handle + /// The comparison function to set + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_set_compare(nint txn, uint dbi, CompareFunction cmp); + /// + /// Sets a custom data comparison function for a database with MDB_DUPSORT. + /// + /// A transaction handle + /// A database handle + /// The comparison function to set + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_set_dupsort(nint txn, uint dbi, CompareFunction cmp); + + /// + /// Compares two data items according to a database's key comparison function. + /// + /// A transaction handle + /// A database handle + /// The first item to compare + /// The second item to compare + [LibraryImport(MDB_DLL_NAME)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial int mdb_cmp(nint txn, uint dbi, ref MDBValue a, ref MDBValue b); + + /// + /// Compares two data items according to a database's data comparison function. + /// + /// A transaction handle + /// A database handle + /// The first item to compare + /// The second item to compare + [LibraryImport(MDB_DLL_NAME)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial int mdb_dcmp(nint txn, uint dbi, ref MDBValue a, ref MDBValue b); + + /// + /// Lists all the readers in the environment and the transaction they're holding. + /// + /// The environment handle + /// A function to call for each reader + /// Arbitrary context data to pass to the function + /// Number of readers that were found + [LibraryImport(MDB_DLL_NAME)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial int mdb_reader_list(nint env, nint func, nint ctx); + + /// + /// Checks for stale readers in the environment reader table. + /// + /// The environment handle + /// Address where the number of stale readers cleaned up will be stored + /// 0 on success, non-zero on failure + [LibraryImport(MDB_DLL_NAME)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + public static partial int mdb_reader_check(nint env, out int dead); + /// + /// Stores multiple contiguous data elements in a single request with a cursor. + /// + /// A cursor handle + /// The key to store + /// The array of data values to store + /// Special options for this operation + /// A result code indicating success or failure [LibraryImport(MDB_DLL_NAME)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial MDBResultCode mdb_cursor_put(nint cursor, ref MDBValue key, MDBValue[] value, CursorPutOptions flags); @@ -163,49 +590,79 @@ public static partial class Lmdb public static partial class Lmdb { private const string MDB_DLL_NAME = "lmdb"; - /// - /// 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. - /// - public const int MDB_DUPSORT = 0x04; /// - /// This flag may only be used in combination with MDB_DUPSORT. This option tells the library that the data items for this database are all the same size, which allows further optimizations in storage and retrieval. When all data items are the same size, the MDB_GET_MULTIPLE and MDB_NEXT_MULTIPLE cursor operations may be used to retrieve multiple items at once. + /// Sets the size of the memory map to use for this environment. /// - public const int MDB_DUPFIXED = 0x10; - + /// The environment handle + /// The size in bytes + /// A result code indicating success or failure public static MDBResultCode mdb_env_set_mapsize(nint env, long size) { return mdb_env_set_mapsize(env, (nint)size); } + /// + /// Stores key/data pairs in a database. + /// + /// A transaction handle + /// A database handle + /// The key to store + /// The data to store + /// Special options for this operation + /// A result code indicating success or failure public static MDBResultCode mdb_put(nint txn, uint dbi, MDBValue key, MDBValue value, PutOptions flags) { return mdb_put(txn, dbi, ref key, ref value, flags); } + /// + /// Deletes items from a database. + /// + /// A transaction handle + /// A database handle + /// The key to delete + /// The data to delete + /// A result code indicating success or failure public static MDBResultCode mdb_del(nint txn, uint dbi, MDBValue key, MDBValue value) { return mdb_del(txn, dbi, ref key, ref value); } + /// + /// Deletes items from a database. + /// + /// A transaction handle + /// A database handle + /// The key to delete + /// A result code indicating success or failure public static MDBResultCode mdb_del(nint txn, uint dbi, MDBValue key) { - return mdb_del(txn, dbi, ref key,0); + return mdb_del(txn, dbi, ref key, 0); } + /// + /// Stores key/data pairs into the database using a cursor. + /// + /// A cursor handle + /// The key to store + /// The data to store + /// Special options for this operation + /// A result code indicating success or failure public static MDBResultCode mdb_cursor_put(nint cursor, MDBValue key, MDBValue value, CursorPutOptions flags) { return mdb_cursor_put(cursor, ref key, ref value, flags); } /// - /// store multiple contiguous data elements in a single request. + /// Stores multiple contiguous data elements in a single request with a cursor. /// May only be used with MDB_DUPFIXED. /// /// Pointer to cursor - /// key + /// The key to store /// This span must be pinned or stackalloc memory - /// + /// Special options for this operation + /// A result code indicating success or failure public static MDBResultCode mdb_cursor_put(nint cursor, ref MDBValue key, ref Span data, CursorPutOptions flags) { @@ -214,16 +671,41 @@ public static MDBResultCode mdb_cursor_put(nint cursor, ref MDBValue key, ref Sp } #if !NET7_0_OR_GREATER + /// + /// Creates an LMDB environment handle. + /// + /// The address where the new handle will be stored + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_env_create(out nint env); + /// + /// Closes the environment and releases the memory map. + /// + /// The environment handle to close [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern void mdb_env_close(nint env); + /// + /// Opens an environment handle with the specified path, flags, and permissions. + /// + /// The environment handle to open + /// The directory in which the database files reside + /// Special options for this environment + /// The UNIX permissions to set on created files + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - private static extern MDBResultCode mdb_env_open(nint env, byte[] path, EnvironmentOpenFlags flags, + public static extern MDBResultCode mdb_env_open(nint env, byte[] path, EnvironmentOpenFlags flags, UnixAccessMode mode); + /// + /// Opens an environment handle with the specified path, flags, and permissions. + /// + /// The environment handle to open + /// The directory in which the database files reside + /// Special options for this environment + /// The UNIX permissions to set on created files + /// A result code indicating success or failure internal static MDBResultCode mdb_env_open(nint env, string path, EnvironmentOpenFlags flags, UnixAccessMode mode) { @@ -231,117 +713,505 @@ internal static MDBResultCode mdb_env_open(nint env, string path, EnvironmentOpe return mdb_env_open(env, bytes, flags, mode); } + /// + /// Sets the size of the memory map to use for this environment. + /// + /// The environment handle + /// The size in bytes + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_env_set_mapsize(nint env, nint size); + /// + /// Gets the maximum number of threads/reader slots for the environment. + /// + /// The environment handle + /// Address where the number of readers will be stored + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_env_get_maxreaders(nint env, out uint readers); + /// + /// Sets the maximum number of threads/reader slots for the environment. + /// + /// The environment handle + /// The number of reader slots to allocate + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_env_set_maxreaders(nint env, uint readers); + /// + /// Sets the maximum number of named databases for the environment. + /// + /// The environment handle + /// The maximum number of databases + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_env_set_maxdbs(nint env, uint dbs); + + /// + /// Sets environment flags. + /// + /// The environment handle + /// The flags to set + /// A boolean to turn the flags on or off + /// A result code indicating success or failure + [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] + public static extern MDBResultCode mdb_env_set_flags(nint env, uint flags, bool onoff); + + /// + /// Gets environment flags. + /// + /// The environment handle + /// Address where the flags will be stored + /// A result code indicating success or failure + [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] + public static extern MDBResultCode mdb_env_get_flags(nint env, out uint flags); + + /// + /// Returns the path that was used in mdb_env_open. + /// + /// The environment handle + /// Address where the path will be stored + /// A result code indicating success or failure + [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] + public static extern MDBResultCode mdb_env_get_path(nint env, out nint path); + + /// + /// Returns the file descriptor for the environment. + /// + /// The environment handle + /// Address where the descriptor will be stored + /// A result code indicating success or failure + [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] + public static extern MDBResultCode mdb_env_get_fd(nint env, out nint fd); + + /// + /// Returns the maximum size of keys and MDB_DUPSORT data we can write. + /// + /// The environment handle + /// The maximum size of a key we can write + [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int mdb_env_get_maxkeysize(nint env); + + /// + /// Set application information associated with the environment. + /// + /// The environment handle + /// An arbitrary pointer for your application's use + /// A result code indicating success or failure + [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] + public static extern MDBResultCode mdb_env_set_userctx(nint env, nint ctx); + + /// + /// Get the application information associated with the environment. + /// + /// The environment handle + /// The pointer set by mdb_env_set_userctx + [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] + public static extern nint mdb_env_get_userctx(nint env); + + /// + /// Set or reset the assert callback for the environment. + /// + /// The environment handle + /// A callback function to run when an assertion fails + /// A result code indicating success or failure + [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] + public static extern MDBResultCode mdb_env_set_assert(nint env, nint func); + /// + /// Opens a database in the environment. + /// + /// A transaction handle + /// The name of the database to open (NULL for the main DB) + /// Special options for this database + /// Address where the database handle will be stored + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_dbi_open(nint txn, string name, DatabaseOpenFlags flags, out uint db); + /// + /// Closes a database handle in the environment. + /// + /// The environment handle + /// A database handle returned by mdb_dbi_open [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern void mdb_dbi_close(nint env, uint dbi); + + /// + /// Retrieves the flags for a database handle. + /// + /// A transaction handle + /// A database handle + /// Address where the flags will be stored + /// A result code indicating success or failure + [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] + public static extern MDBResultCode mdb_dbi_flags(nint txn, uint dbi, out uint flags); + /// + /// Empties or deletes a database. + /// + /// A transaction handle + /// A database handle + /// If true, delete the DB from the environment; otherwise just empty it + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_drop(nint txn, uint dbi, bool del); + /// + /// Creates a transaction for use with the environment. + /// + /// The environment handle + /// Parent transaction (for nested transactions; NULL for none) + /// Special transaction flags + /// Address where the new transaction handle will be stored + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_txn_begin(nint env, nint parent, TransactionBeginFlags flags, out nint txn); + + /// + /// Returns the transaction's environment. + /// + /// A transaction handle + /// The environment handle + [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] + public static extern nint mdb_txn_env(nint txn); + + /// + /// Returns the transaction's ID. + /// + /// A transaction handle + /// The transaction ID + [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int mdb_txn_id(nint txn); + /// + /// Commits all the operations of a transaction into the database. + /// + /// A transaction handle + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_txn_commit(nint txn); + /// + /// Abandons all the operations of the transaction instead of saving them. + /// + /// A transaction handle [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern void mdb_txn_abort(nint txn); + /// + /// Resets a read-only transaction. + /// + /// A transaction handle [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern void mdb_txn_reset(nint txn); + /// + /// Renews a read-only transaction that was previously reset. + /// + /// A transaction handle + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_txn_renew(nint txn); + /// + /// Returns the LMDB library version and version information. + /// + /// The library major version number + /// The library minor version number + /// The library patch version number + /// Pointer to a version string [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern nint mdb_version(out int major, out int minor, out int patch); + /// + /// Returns a string describing a given error code. + /// + /// The error code + /// A pointer to the error string [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern nint mdb_strerror(int err); + /// + /// Returns statistics about a database. + /// + /// A transaction handle + /// A database handle + /// Address where the database statistics will be stored + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_stat(nint txn, uint dbi, out MDBStat stat); + /// + /// Copies an LMDB environment to the specified path. + /// + /// The environment handle + /// The directory in which the backup will reside + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - private static extern MDBResultCode mdb_env_copy(nint env, byte[] path); + public static extern MDBResultCode mdb_env_copy(nint env, byte[] path); + /// + /// Copies an LMDB environment to the specified path. + /// + /// The environment handle + /// The directory in which the backup will reside + /// A result code indicating success or failure public static MDBResultCode mdb_env_copy(nint env, string path) { var bytes = System.Text.Encoding.UTF8.GetBytes(path); return mdb_env_copy(env, bytes); } + + /// + /// Copies an LMDB environment to the specified file descriptor. + /// + /// The environment handle + /// The file descriptor to write to + /// A result code indicating success or failure + [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] + public static extern MDBResultCode mdb_env_copyfd(nint env, nint fd); + + /// + /// Copies an LMDB environment to the specified file descriptor, with options for compaction. + /// + /// The environment handle + /// The file descriptor to write to + /// Special options for this copy operation + /// A result code indicating success or failure + [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] + public static extern MDBResultCode mdb_env_copyfd2(nint env, nint fd, EnvironmentCopyFlags copyFlags); + /// + /// Copies an LMDB environment to the specified path, with options for compaction. + /// + /// The environment handle + /// The directory in which the backup will reside + /// Special options for this copy operation + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] - private static extern MDBResultCode mdb_env_copy2(nint env, byte[] path, EnvironmentCopyFlags copyFlags); + public static extern MDBResultCode mdb_env_copy2(nint env, byte[] path, EnvironmentCopyFlags copyFlags); + /// + /// Copies an LMDB environment to the specified path, with options for compaction. + /// + /// The environment handle + /// The directory in which the backup will reside + /// Special options for this copy operation + /// A result code indicating success or failure public static MDBResultCode mdb_env_copy2(nint env, string path, EnvironmentCopyFlags copyFlags) { var bytes = System.Text.Encoding.UTF8.GetBytes(path); return mdb_env_copy2(env, bytes, copyFlags); } + /// + /// Returns information about the LMDB environment. + /// + /// The environment handle + /// Address where the environment info will be stored + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_env_info(nint env, out MDBEnvInfo stat); + /// + /// Returns statistics about the LMDB environment. + /// + /// The environment handle + /// Address where the environment statistics will be stored + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_env_stat(nint env, out MDBStat stat); + /// + /// Flushes all data to the environment's data file. + /// + /// The environment handle + /// If true, force a synchronous flush; otherwise if the environment has MDB_NOSYNC or MDB_MAPASYNC, it will not be synchronous + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_env_sync(nint env, bool force); + /// + /// Retrieves items from a database. + /// + /// A transaction handle + /// A database handle + /// The key to search for + /// Address where the retrieved data will be stored + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_get(nint txn, uint dbi, ref MDBValue key, out MDBValue data); + /// + /// Returns count of duplicates for the current key in a cursor. + /// + /// A cursor handle + /// Address where the count will be stored + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_cursor_count(nint cursor, out int countp); + /// + /// Stores key/data pairs in a database. + /// + /// A transaction handle + /// A database handle + /// The key to store + /// The data to store + /// Special options for this operation + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_put(nint txn, uint dbi, ref MDBValue key, ref MDBValue data, PutOptions flags); + /// + /// Deletes items from a database. + /// + /// A transaction handle + /// A database handle + /// The key to delete + /// The data to delete (only needed for MDB_DUPSORT) + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_del(nint txn, uint dbi, ref MDBValue key, ref MDBValue data); + /// + /// Deletes items from a database. + /// + /// A transaction handle + /// A database handle + /// The key to delete + /// NULL pointer to delete all of the data items for the key + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_del(nint txn, uint dbi, ref MDBValue key, nint data); + /// + /// Creates a cursor handle for the specified transaction and database. + /// + /// A transaction handle + /// A database handle + /// Address where the new cursor handle will be stored + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_cursor_open(nint txn, uint dbi, out nint cursor); + /// + /// Closes a cursor handle. + /// + /// A cursor handle to close [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern void mdb_cursor_close(nint cursor); + /// + /// Renews a cursor handle. + /// + /// A transaction handle + /// A cursor handle to renew + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_cursor_renew(nint txn, nint cursor); - + + /// + /// Retrieves key/data pairs from the database using a cursor. + /// + /// A cursor handle + /// The key for the current item + /// The data for the current item + /// The cursor operation to perform + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_cursor_get(nint cursor, ref MDBValue key, ref MDBValue data, CursorOperation op); + /// + /// Stores key/data pairs into the database using a cursor. + /// + /// A cursor handle + /// The key to store + /// The data to store + /// Special options for this operation + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_cursor_put(nint cursor, ref MDBValue key, ref MDBValue mdbValue, CursorPutOptions flags); + /// + /// Deletes the current key/data pair to which the cursor refers. + /// + /// A cursor handle + /// Special options for this operation + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_cursor_del(nint cursor, CursorDeleteOption flags); + /// + /// Sets a custom key comparison function for a database. + /// + /// A transaction handle + /// A database handle + /// The comparison function to set + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_set_compare(nint txn, uint dbi, CompareFunction cmp); + /// + /// Sets a custom data comparison function for a database with MDB_DUPSORT. + /// + /// A transaction handle + /// A database handle + /// The comparison function to set + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_set_dupsort(nint txn, uint dbi, CompareFunction cmp); + + /// + /// Compares two data items according to a database's key comparison function. + /// + /// A transaction handle + /// A database handle + /// The first item to compare + /// The second item to compare + [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int mdb_cmp(nint txn, uint dbi, ref MDBValue a, ref MDBValue b); + + /// + /// Compares two data items according to a database's data comparison function. + /// + /// A transaction handle + /// A database handle + /// The first item to compare + /// The second item to compare + [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int mdb_dcmp(nint txn, uint dbi, ref MDBValue a, ref MDBValue b); + + /// + /// Lists all the readers in the environment and the transaction they're holding. + /// + /// The environment handle + /// A function to call for each reader + /// Arbitrary context data to pass to the function + /// Number of readers that were found + [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int mdb_reader_list(nint env, nint func, nint ctx); + + /// + /// Checks for stale readers in the environment reader table. + /// + /// The environment handle + /// Address where the number of stale readers cleaned up will be stored + /// 0 on success, non-zero on failure + [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int mdb_reader_check(nint env, out int dead); + /// + /// Stores multiple contiguous data elements in a single request with a cursor. + /// + /// A cursor handle + /// The key to store + /// The array of data values to store + /// Special options for this operation + /// A result code indicating success or failure [DllImport(MDB_DLL_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern MDBResultCode mdb_cursor_put(nint cursor, ref MDBValue key, MDBValue[] value, CursorPutOptions flags); #endif