Skip to content

Commit a60d751

Browse files
authored
Merge pull request #126 from koculu/codex/create-inmemory-filestreamprovider-and-tests
Add in-memory FileStreamProvider
2 parents 89e7d6e + ae1d2f1 commit a60d751

File tree

4 files changed

+228
-1
lines changed

4 files changed

+228
-1
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Tenray.ZoneTree.AbstractFileStream;
2+
using Tenray.ZoneTree.Logger;
3+
using Tenray.ZoneTree.Serializers;
4+
using Tenray.ZoneTree.WAL;
5+
6+
namespace Tenray.ZoneTree.UnitTests;
7+
8+
public sealed class InMemoryFileStreamProviderTests
9+
{
10+
[Test]
11+
public void WalWithInMemoryProvider()
12+
{
13+
var serializer = new UnicodeStringSerializer();
14+
var provider = new InMemoryFileStreamProvider();
15+
var wal = new SyncFileSystemWriteAheadLog<string, string>(
16+
new ConsoleLogger(),
17+
provider,
18+
serializer,
19+
serializer,
20+
"test.wal");
21+
wal.Append("hello", "world", 0);
22+
var result = wal.ReadLogEntries(false, false, true);
23+
Assert.That(result.Success, Is.True);
24+
Assert.That(result.Keys[0], Is.EqualTo("hello"));
25+
Assert.That(result.Values[0], Is.EqualTo("world"));
26+
wal.Drop();
27+
}
28+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System;
2+
using System.IO;
3+
4+
namespace Tenray.ZoneTree.AbstractFileStream;
5+
6+
public sealed class InMemoryFileStream : MemoryStream, IFileStream
7+
{
8+
readonly InMemoryFileStreamProvider Provider;
9+
10+
public string FilePath { get; }
11+
12+
public InMemoryFileStream(
13+
InMemoryFileStreamProvider provider,
14+
string path,
15+
byte[] buffer)
16+
{
17+
Provider = provider;
18+
FilePath = path;
19+
if (buffer.Length > 0)
20+
Write(buffer, 0, buffer.Length);
21+
Position = 0;
22+
}
23+
24+
public void Flush(bool flushToDisk)
25+
{
26+
Flush();
27+
}
28+
29+
public int ReadFaster(byte[] buffer, int offset, int count)
30+
{
31+
int totalRead = 0;
32+
while (totalRead < count)
33+
{
34+
int read = Read(buffer, offset + totalRead, count - totalRead);
35+
if (read == 0)
36+
throw new EndOfStreamException();
37+
totalRead += read;
38+
}
39+
return totalRead;
40+
}
41+
42+
public Stream ToStream() => this;
43+
44+
protected override void Dispose(bool disposing)
45+
{
46+
if (disposing)
47+
{
48+
Provider.UpdateFile(FilePath, ToArray());
49+
}
50+
base.Dispose(disposing);
51+
}
52+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
using System.Text;
2+
3+
namespace Tenray.ZoneTree.AbstractFileStream;
4+
5+
public sealed class InMemoryFileStreamProvider : IFileStreamProvider
6+
{
7+
readonly Dictionary<string, byte[]> Files = new();
8+
readonly HashSet<string> Directories = new();
9+
10+
public IFileStream CreateFileStream(
11+
string path,
12+
FileMode mode,
13+
FileAccess access,
14+
FileShare share,
15+
int bufferSize = 4096,
16+
FileOptions options = FileOptions.None)
17+
{
18+
lock (this)
19+
{
20+
if (!Files.ContainsKey(path))
21+
{
22+
if (mode == FileMode.Open)
23+
throw new FileNotFoundException(path);
24+
Files[path] = Array.Empty<byte>();
25+
}
26+
else if (mode == FileMode.CreateNew)
27+
{
28+
throw new IOException($"File {path} already exists.");
29+
}
30+
else if (mode == FileMode.Create)
31+
{
32+
Files[path] = Array.Empty<byte>();
33+
}
34+
else if (mode == FileMode.Truncate)
35+
{
36+
Files[path] = Array.Empty<byte>();
37+
}
38+
var bytes = Files[path];
39+
var stream = new InMemoryFileStream(this, path, bytes);
40+
if (mode == FileMode.Append)
41+
stream.Seek(0, SeekOrigin.End);
42+
return stream;
43+
}
44+
}
45+
46+
public bool FileExists(string path)
47+
{
48+
lock (this) return Files.ContainsKey(path);
49+
}
50+
51+
public bool DirectoryExists(string path)
52+
{
53+
lock (this) return Directories.Contains(path);
54+
}
55+
56+
public void CreateDirectory(string path)
57+
{
58+
lock (this) Directories.Add(path);
59+
}
60+
61+
public void DeleteFile(string path)
62+
{
63+
lock (this) Files.Remove(path);
64+
}
65+
66+
public void DeleteDirectory(string path, bool recursive)
67+
{
68+
lock (this)
69+
{
70+
Directories.Remove(path);
71+
if (recursive)
72+
{
73+
var toRemove = Files.Keys.Where(x => x.StartsWith(path)).ToList();
74+
foreach (var f in toRemove)
75+
Files.Remove(f);
76+
}
77+
}
78+
}
79+
80+
public string ReadAllText(string path)
81+
{
82+
lock (this) return Encoding.UTF8.GetString(Files[path]);
83+
}
84+
85+
public byte[] ReadAllBytes(string path)
86+
{
87+
lock (this)
88+
{
89+
var b = Files[path];
90+
var copy = new byte[b.Length];
91+
Buffer.BlockCopy(b, 0, copy, 0, b.Length);
92+
return copy;
93+
}
94+
}
95+
96+
public void Replace(
97+
string sourceFileName,
98+
string destinationFileName,
99+
string destinationBackupFileName)
100+
{
101+
lock (this)
102+
{
103+
if (destinationBackupFileName != null && Files.ContainsKey(destinationFileName))
104+
{
105+
Files[destinationBackupFileName] = Files[destinationFileName];
106+
}
107+
Files[destinationFileName] = Files.ContainsKey(sourceFileName) ? Files[sourceFileName] : Array.Empty<byte>();
108+
Files.Remove(sourceFileName);
109+
}
110+
}
111+
112+
public DurableFileWriter GetDurableFileWriter()
113+
{
114+
return new DurableFileWriter(this);
115+
}
116+
117+
public IReadOnlyList<string> GetDirectories(string path)
118+
{
119+
lock (this)
120+
{
121+
return Directories.Where(x => x.StartsWith(path)).ToArray();
122+
}
123+
}
124+
125+
public string CombinePaths(string path1, string path2)
126+
{
127+
return Path.Combine(path1, path2);
128+
}
129+
130+
internal void UpdateFile(string path, byte[] data)
131+
{
132+
lock (this)
133+
{
134+
Files[path] = data;
135+
}
136+
}
137+
}

src/ZoneTree/docs/ZoneTree/guide/quick-start.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,14 @@ while(iterator.Next()) {
162162
var key = iterator.CurrentKey;
163163
var value = iterator.CurrentValue;
164164
}
165-
```
165+
```
166+
## Using the InMemoryFileStreamProvider
167+
168+
The `InMemoryFileStreamProvider` keeps all files entirely in memory. It is useful for unit testing or scenarios that require fast, temporary storage without touching the disk.
169+
170+
```csharp
171+
var provider = new InMemoryFileStreamProvider();
172+
using var zoneTree = new ZoneTreeFactory<int, string>(provider)
173+
.OpenOrCreate();
174+
zoneTree.Upsert(1, "value");
175+
```

0 commit comments

Comments
 (0)