Skip to content

Commit

Permalink
Improve LevelDBStore (#3607)
Browse files Browse the repository at this point in the history
* Improve `LevelDBStore`

* Added `DebuggerTypeProxy`

* Fixed up `null` for debuggerview

* revert `null` check

* Fixed test

* Reverted `DebuggerTypeProxy`

* Added caution comment to dataset classes
  • Loading branch information
cschuchardt88 authored Dec 5, 2024
1 parent 79554f2 commit cc2ad03
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 42 deletions.
41 changes: 19 additions & 22 deletions src/Plugins/LevelDBStore/IO/Data/LevelDB/DB.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

#nullable enable

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;

Expand All @@ -20,14 +20,15 @@ namespace Neo.IO.Storage.LevelDB
/// <summary>
/// A DB is a persistent ordered map from keys to values.
/// A DB is safe for concurrent access from multiple threads without any external synchronization.
/// <code>Iterating over the whole dataset can be time-consuming. Depending upon how large the dataset is.</code>
/// </summary>
public class DB : LevelDBHandle
public class DB : LevelDBHandle, IEnumerable<KeyValuePair<byte[], byte[]>>
{
private DB(IntPtr handle) : base(handle) { }
private DB(nint handle) : base(handle) { }

protected override void FreeUnManagedObjects()
{
if (Handle != IntPtr.Zero)
if (Handle != nint.Zero)
{
Native.leveldb_close(Handle);
}
Expand All @@ -40,7 +41,7 @@ protected override void FreeUnManagedObjects()
/// </summary>
public void Delete(WriteOptions options, byte[] key)
{
Native.leveldb_delete(Handle, options.Handle, key, (UIntPtr)key.Length, out var error);
Native.leveldb_delete(Handle, options.Handle, key, (nuint)key.Length, out var error);
NativeHelper.CheckError(error);
}

Expand All @@ -50,24 +51,24 @@ public void Delete(WriteOptions options, byte[] key)
/// </summary>
public byte[] Get(ReadOptions options, byte[] key)
{
var value = Native.leveldb_get(Handle, options.Handle, key, (UIntPtr)key.Length, out var length, out var error);
var value = Native.leveldb_get(Handle, options.Handle, key, (nuint)key.Length, out var length, out var error);
try
{
NativeHelper.CheckError(error);
return value.ToByteArray(length);
}
finally
{
if (value != IntPtr.Zero) Native.leveldb_free(value);
if (value != nint.Zero) Native.leveldb_free(value);
}
}

public bool Contains(ReadOptions options, byte[] key)
{
var value = Native.leveldb_get(Handle, options.Handle, key, (UIntPtr)key.Length, out _, out var error);
var value = Native.leveldb_get(Handle, options.Handle, key, (nuint)key.Length, out _, out var error);
NativeHelper.CheckError(error);

if (value != IntPtr.Zero)
if (value != nint.Zero)
{
Native.leveldb_free(value);
return true;
Expand All @@ -76,12 +77,12 @@ public bool Contains(ReadOptions options, byte[] key)
return false;
}

public Snapshot GetSnapshot()
public Snapshot CreateSnapshot()
{
return new Snapshot(Handle);
}

public Iterator NewIterator(ReadOptions options)
public Iterator CreateIterator(ReadOptions options)
{
return new Iterator(Native.leveldb_create_iterator(Handle, options.Handle));
}
Expand All @@ -104,7 +105,7 @@ public static DB Open(string name, Options options)
/// </summary>
public void Put(WriteOptions options, byte[] key, byte[] value)
{
Native.leveldb_put(Handle, options.Handle, key, (UIntPtr)key.Length, value, (UIntPtr)value.Length, out var error);
Native.leveldb_put(Handle, options.Handle, key, (nuint)key.Length, value, (nuint)value.Length, out var error);
NativeHelper.CheckError(error);
}

Expand All @@ -126,18 +127,14 @@ public void Write(WriteOptions options, WriteBatch write_batch)
NativeHelper.CheckError(error);
}

public IEnumerable<KeyValuePair<byte[], byte[]>> GetAll(Snapshot? snapshot = null)
public IEnumerator<KeyValuePair<byte[], byte[]>> GetEnumerator()
{
using var options = new ReadOptions();
if (snapshot != null) options.Snapshot = snapshot;

using var iterator = NewIterator(options);
iterator.SeekToFirst();
while (iterator.Valid())
{
using var iterator = CreateIterator(ReadOptions.Default);
for (iterator.SeekToFirst(); iterator.Valid(); iterator.Next())
yield return new KeyValuePair<byte[], byte[]>(iterator.Key(), iterator.Value());
iterator.Next();
}
}

IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
}
}
2 changes: 1 addition & 1 deletion src/Plugins/LevelDBStore/IO/Data/LevelDB/Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static class Helper
{
public static IEnumerable<(byte[], byte[])> Seek(this DB db, ReadOptions options, byte[] prefix, SeekDirection direction)
{
using Iterator it = db.NewIterator(options);
using Iterator it = db.CreateIterator(options);
if (direction == SeekDirection.Forward)
{
for (it.Seek(prefix); it.Valid(); it.Next())
Expand Down
10 changes: 4 additions & 6 deletions src/Plugins/LevelDBStore/IO/Data/LevelDB/Iterator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,14 @@
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using System;

namespace Neo.IO.Storage.LevelDB
{
/// <summary>
/// An iterator yields a sequence of key/value pairs from a database.
/// </summary>
public class Iterator : LevelDBHandle
{
internal Iterator(IntPtr handle) : base(handle) { }
internal Iterator(nint handle) : base(handle) { }

private void CheckError()
{
Expand All @@ -28,7 +26,7 @@ private void CheckError()

protected override void FreeUnManagedObjects()
{
if (Handle != IntPtr.Zero)
if (Handle != nint.Zero)
{
Native.leveldb_iter_destroy(Handle);
}
Expand Down Expand Up @@ -67,9 +65,9 @@ public void Prev()
/// The iterator is Valid() after this call if the source contains
/// an entry that comes at or past target.
/// </summary>
public void Seek(byte[] target)
public void Seek(byte[] key)
{
Native.leveldb_iter_seek(Handle, target, (UIntPtr)target.Length);
Native.leveldb_iter_seek(Handle, key, (nuint)key.Length);
}

public void SeekToFirst()
Expand Down
4 changes: 2 additions & 2 deletions src/Plugins/LevelDBStore/IO/Data/LevelDB/Native.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ namespace Neo.IO.Storage.LevelDB
{
public enum CompressionType : byte
{
kNoCompression = 0x0,
kSnappyCompression = 0x1
NoCompression = 0x0,
SnappyCompression = 0x1
}

public static class Native
Expand Down
4 changes: 2 additions & 2 deletions src/Plugins/LevelDBStore/IO/Data/LevelDB/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,12 @@ public int BlockRestartInterval
/// incompressible, the kSnappyCompression implementation will
/// efficiently detect that and will switch to uncompressed mode.
/// </summary>
public CompressionType Compression
public CompressionType CompressionLevel
{
set { Native.leveldb_options_set_compression(Handle, value); }
}

public IntPtr FilterPolicy
public nint FilterPolicy
{
set { Native.leveldb_options_set_filter_policy(Handle, value); }
}
Expand Down
8 changes: 3 additions & 5 deletions src/Plugins/LevelDBStore/IO/Data/LevelDB/Snapshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using System;

namespace Neo.IO.Storage.LevelDB
{
/// <summary>
Expand All @@ -19,16 +17,16 @@ namespace Neo.IO.Storage.LevelDB
/// </summary>
public class Snapshot : LevelDBHandle
{
internal IntPtr _db;
internal nint _db;

internal Snapshot(IntPtr db) : base(Native.leveldb_create_snapshot(db))
internal Snapshot(nint db) : base(Native.leveldb_create_snapshot(db))
{
_db = db;
}

protected override void FreeUnManagedObjects()
{
if (Handle != IntPtr.Zero)
if (Handle != nint.Zero)
{
Native.leveldb_release_snapshot(_db, Handle);
}
Expand Down
18 changes: 16 additions & 2 deletions src/Plugins/LevelDBStore/Plugins/Storage/Snapshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@

using Neo.IO.Storage.LevelDB;
using Neo.Persistence;
using System.Collections;
using System.Collections.Generic;
using LSnapshot = Neo.IO.Storage.LevelDB.Snapshot;

namespace Neo.Plugins.Storage
{
internal class Snapshot : ISnapshot
/// <summary>
/// <code>Iterating over the whole dataset can be time-consuming. Depending upon how large the dataset is.</code>
/// </summary>
internal class Snapshot : ISnapshot, IEnumerable<KeyValuePair<byte[], byte[]>>
{
private readonly DB _db;
private readonly LSnapshot _snapshot;
Expand All @@ -27,7 +31,7 @@ internal class Snapshot : ISnapshot
public Snapshot(DB db)
{
_db = db;
_snapshot = db.GetSnapshot();
_snapshot = db.CreateSnapshot();
_readOptions = new ReadOptions { FillCache = false, Snapshot = _snapshot };
_batch = new WriteBatch();
}
Expand Down Expand Up @@ -76,5 +80,15 @@ public bool TryGet(byte[] key, out byte[] value)
value = _db.Get(_readOptions, key);
return value != null;
}

public IEnumerator<KeyValuePair<byte[], byte[]>> GetEnumerator()
{
using var iterator = _db.CreateIterator(_readOptions);
for (iterator.SeekToFirst(); iterator.Valid(); iterator.Next())
yield return new KeyValuePair<byte[], byte[]>(iterator.Key(), iterator.Value());
}

IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
}
}
19 changes: 17 additions & 2 deletions src/Plugins/LevelDBStore/Plugins/Storage/Store.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,27 @@

using Neo.IO.Storage.LevelDB;
using Neo.Persistence;
using System.Collections;
using System.Collections.Generic;

namespace Neo.Plugins.Storage
{
internal class Store : IStore
/// <summary>
/// <code>Iterating over the whole dataset can be time-consuming. Depending upon how large the dataset is.</code>
/// </summary>
internal class Store : IStore, IEnumerable<KeyValuePair<byte[], byte[]>>
{
private readonly DB _db;
private readonly Options _options;

public Store(string path)
{
_options = new Options { CreateIfMissing = true, FilterPolicy = Native.leveldb_filterpolicy_create_bloom(15) };
_options = new Options
{
CreateIfMissing = true,
FilterPolicy = Native.leveldb_filterpolicy_create_bloom(15),
CompressionLevel = CompressionType.SnappyCompression,
};
_db = DB.Open(path, _options);
}

Expand Down Expand Up @@ -60,5 +69,11 @@ public bool TryGet(byte[] key, out byte[] value)

public IEnumerable<(byte[], byte[])> Seek(byte[] prefix, SeekDirection direction = SeekDirection.Forward) =>
_db.Seek(ReadOptions.Default, prefix, direction);

public IEnumerator<KeyValuePair<byte[], byte[]>> GetEnumerator() =>
_db.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
}
}
15 changes: 15 additions & 0 deletions tests/Neo.Plugins.Storage.Tests/StoreTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// modifications are permitted.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.IO.Storage.LevelDB;
using Neo.Persistence;
using System.IO;
using System.Linq;
Expand Down Expand Up @@ -72,6 +73,20 @@ public void TestLevelDb()
TestPersistenceRead(levelDbStore.GetStore(path_leveldb), false);
}

[TestMethod]
public void TestLevelDbDatabase()
{
using var db = DB.Open(Path.GetRandomFileName(), new() { CreateIfMissing = true });

db.Put(WriteOptions.Default, [0x00, 0x00, 0x01], [0x01]);
db.Put(WriteOptions.Default, [0x00, 0x00, 0x02], [0x02]);
db.Put(WriteOptions.Default, [0x00, 0x00, 0x03], [0x03]);

CollectionAssert.AreEqual(new byte[] { 0x01, }, db.Get(ReadOptions.Default, [0x00, 0x00, 0x01]));
CollectionAssert.AreEqual(new byte[] { 0x02, }, db.Get(ReadOptions.Default, [0x00, 0x00, 0x02]));
CollectionAssert.AreEqual(new byte[] { 0x03, }, db.Get(ReadOptions.Default, [0x00, 0x00, 0x03]));
}

[TestMethod]
public void TestLevelDbSnapshot()
{
Expand Down

0 comments on commit cc2ad03

Please sign in to comment.