Reformat / cleanup
This commit is contained in:
@@ -6,44 +6,37 @@ namespace ZB.MOM.WW.CBDD.Tests;
|
||||
public class BTreeDeleteUnderflowTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes Delete_HeavyWorkload_Should_Remain_Queryable_After_Merges.
|
||||
/// Executes Delete_HeavyWorkload_Should_Remain_Queryable_After_Merges.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Delete_HeavyWorkload_Should_Remain_Queryable_After_Merges()
|
||||
{
|
||||
var dbPath = Path.Combine(Path.GetTempPath(), $"btree_underflow_{Guid.NewGuid():N}.db");
|
||||
string dbPath = Path.Combine(Path.GetTempPath(), $"btree_underflow_{Guid.NewGuid():N}.db");
|
||||
|
||||
try
|
||||
{
|
||||
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
|
||||
var index = new BTreeIndex(storage, IndexOptions.CreateBTree("k"));
|
||||
|
||||
var insertTxn = storage.BeginTransaction().TransactionId;
|
||||
for (int i = 1; i <= 240; i++)
|
||||
{
|
||||
ulong insertTxn = storage.BeginTransaction().TransactionId;
|
||||
for (var i = 1; i <= 240; i++)
|
||||
index.Insert(IndexKey.Create(i), new DocumentLocation((uint)(1000 + i), 0), insertTxn);
|
||||
}
|
||||
storage.CommitTransaction(insertTxn);
|
||||
|
||||
var deleteTxn = storage.BeginTransaction().TransactionId;
|
||||
for (int i = 1; i <= 190; i++)
|
||||
{
|
||||
ulong deleteTxn = storage.BeginTransaction().TransactionId;
|
||||
for (var i = 1; i <= 190; i++)
|
||||
index.Delete(IndexKey.Create(i), new DocumentLocation((uint)(1000 + i), 0), deleteTxn).ShouldBeTrue();
|
||||
}
|
||||
storage.CommitTransaction(deleteTxn);
|
||||
|
||||
for (int i = 1; i <= 190; i++)
|
||||
{
|
||||
index.TryFind(IndexKey.Create(i), out _, 0).ShouldBeFalse();
|
||||
}
|
||||
for (var i = 1; i <= 190; i++) index.TryFind(IndexKey.Create(i), out _, 0).ShouldBeFalse();
|
||||
|
||||
for (int i = 191; i <= 240; i++)
|
||||
for (var i = 191; i <= 240; i++)
|
||||
{
|
||||
index.TryFind(IndexKey.Create(i), out var location, 0).ShouldBeTrue();
|
||||
location.PageId.ShouldBe((uint)(1000 + i));
|
||||
}
|
||||
|
||||
var remaining = index.GreaterThan(IndexKey.Create(190), orEqual: false, 0).ToList();
|
||||
var remaining = index.GreaterThan(IndexKey.Create(190), false, 0).ToList();
|
||||
remaining.Count.ShouldBe(50);
|
||||
remaining.First().Key.ShouldBe(IndexKey.Create(191));
|
||||
remaining.Last().Key.ShouldBe(IndexKey.Create(240));
|
||||
@@ -51,8 +44,8 @@ public class BTreeDeleteUnderflowTests
|
||||
finally
|
||||
{
|
||||
if (File.Exists(dbPath)) File.Delete(dbPath);
|
||||
var walPath = Path.ChangeExtension(dbPath, ".wal");
|
||||
string walPath = Path.ChangeExtension(dbPath, ".wal");
|
||||
if (File.Exists(walPath)) File.Delete(walPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,20 +8,20 @@ namespace ZB.MOM.WW.CBDD.Tests;
|
||||
public class CollectionIndexManagerAndDefinitionTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests find best index should prefer unique index.
|
||||
/// Tests find best index should prefer unique index.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void FindBestIndex_Should_Prefer_Unique_Index()
|
||||
{
|
||||
var dbPath = NewDbPath();
|
||||
string dbPath = NewDbPath();
|
||||
try
|
||||
{
|
||||
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
|
||||
var mapper = new ZB_MOM_WW_CBDD_Shared_PersonMapper();
|
||||
using var manager = new CollectionIndexManager<int, Person>(storage, mapper, "people_idx_pref_unique");
|
||||
|
||||
manager.CreateIndex(p => p.Age, name: "idx_age", unique: false);
|
||||
manager.CreateIndex(p => p.Age, name: "idx_age_unique", unique: true);
|
||||
manager.CreateIndex(p => p.Age, "idx_age");
|
||||
manager.CreateIndex(p => p.Age, "idx_age_unique", true);
|
||||
|
||||
var best = manager.FindBestIndex("Age");
|
||||
|
||||
@@ -36,12 +36,12 @@ public class CollectionIndexManagerAndDefinitionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests find best compound index should choose longest prefix.
|
||||
/// Tests find best compound index should choose longest prefix.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void FindBestCompoundIndex_Should_Choose_Longest_Prefix()
|
||||
{
|
||||
var dbPath = NewDbPath();
|
||||
string dbPath = NewDbPath();
|
||||
try
|
||||
{
|
||||
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
|
||||
@@ -76,12 +76,12 @@ public class CollectionIndexManagerAndDefinitionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests drop index should remove metadata and be idempotent.
|
||||
/// Tests drop index should remove metadata and be idempotent.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DropIndex_Should_Remove_Metadata_And_Be_Idempotent()
|
||||
{
|
||||
var dbPath = NewDbPath();
|
||||
string dbPath = NewDbPath();
|
||||
const string collectionName = "people_idx_drop";
|
||||
|
||||
try
|
||||
@@ -91,7 +91,7 @@ public class CollectionIndexManagerAndDefinitionTests
|
||||
|
||||
using (var manager = new CollectionIndexManager<int, Person>(storage, mapper, collectionName))
|
||||
{
|
||||
manager.CreateIndex(p => p.Age, name: "idx_age", unique: false);
|
||||
manager.CreateIndex(p => p.Age, "idx_age");
|
||||
manager.DropIndex("idx_age").ShouldBeTrue();
|
||||
manager.DropIndex("idx_age").ShouldBeFalse();
|
||||
manager.GetIndexInfo().ShouldBeEmpty();
|
||||
@@ -107,7 +107,7 @@ public class CollectionIndexManagerAndDefinitionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests collection index definition should respect query support rules.
|
||||
/// Tests collection index definition should respect query support rules.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void CollectionIndexDefinition_Should_Respect_Query_Support_Rules()
|
||||
@@ -129,7 +129,7 @@ public class CollectionIndexManagerAndDefinitionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests collection index info to string should include diagnostics.
|
||||
/// Tests collection index info to string should include diagnostics.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void CollectionIndexInfo_ToString_Should_Include_Diagnostics()
|
||||
@@ -150,16 +150,18 @@ public class CollectionIndexManagerAndDefinitionTests
|
||||
}
|
||||
|
||||
private static string NewDbPath()
|
||||
=> Path.Combine(Path.GetTempPath(), $"idx_mgr_{Guid.NewGuid():N}.db");
|
||||
{
|
||||
return Path.Combine(Path.GetTempPath(), $"idx_mgr_{Guid.NewGuid():N}.db");
|
||||
}
|
||||
|
||||
private static void CleanupFiles(string dbPath)
|
||||
{
|
||||
if (File.Exists(dbPath)) File.Delete(dbPath);
|
||||
|
||||
var walPath = Path.ChangeExtension(dbPath, ".wal");
|
||||
string walPath = Path.ChangeExtension(dbPath, ".wal");
|
||||
if (File.Exists(walPath)) File.Delete(walPath);
|
||||
|
||||
var altWalPath = dbPath + "-wal";
|
||||
string altWalPath = dbPath + "-wal";
|
||||
if (File.Exists(altWalPath)) File.Delete(altWalPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,16 @@
|
||||
using ZB.MOM.WW.CBDD.Core.Storage;
|
||||
using ZB.MOM.WW.CBDD.Core.Indexing;
|
||||
using ZB.MOM.WW.CBDD.Bson;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.CBDD.Core.Storage;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests;
|
||||
|
||||
public class CursorTests : IDisposable
|
||||
{
|
||||
private readonly string _testFile;
|
||||
private readonly StorageEngine _storage;
|
||||
private readonly BTreeIndex _index;
|
||||
private readonly StorageEngine _storage;
|
||||
private readonly string _testFile;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CursorTests"/> class.
|
||||
/// Initializes a new instance of the <see cref="CursorTests" /> class.
|
||||
/// </summary>
|
||||
public CursorTests()
|
||||
{
|
||||
@@ -25,9 +23,18 @@ public class CursorTests : IDisposable
|
||||
SeedData();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the resources used by this instance.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_storage.Dispose();
|
||||
if (File.Exists(_testFile)) File.Delete(_testFile);
|
||||
}
|
||||
|
||||
private void SeedData()
|
||||
{
|
||||
var txnId = _storage.BeginTransaction().TransactionId;
|
||||
ulong txnId = _storage.BeginTransaction().TransactionId;
|
||||
|
||||
// Insert 10, 20, 30
|
||||
_index.Insert(IndexKey.Create(10), new DocumentLocation(1, 0), txnId);
|
||||
@@ -38,7 +45,7 @@ public class CursorTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests move to first should position at first.
|
||||
/// Tests move to first should position at first.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MoveToFirst_ShouldPositionAtFirst()
|
||||
@@ -49,7 +56,7 @@ public class CursorTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests move to last should position at last.
|
||||
/// Tests move to last should position at last.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MoveToLast_ShouldPositionAtLast()
|
||||
@@ -60,7 +67,7 @@ public class CursorTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests move next should traverse forward.
|
||||
/// Tests move next should traverse forward.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MoveNext_ShouldTraverseForward()
|
||||
@@ -78,7 +85,7 @@ public class CursorTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests move prev should traverse backward.
|
||||
/// Tests move prev should traverse backward.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MovePrev_ShouldTraverseBackward()
|
||||
@@ -96,7 +103,7 @@ public class CursorTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests seek should position exact or next.
|
||||
/// Tests seek should position exact or next.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Seek_ShouldPositionExact_OrNext()
|
||||
@@ -116,13 +123,4 @@ public class CursorTests : IDisposable
|
||||
// Current should throw invalid
|
||||
Should.Throw<InvalidOperationException>(() => cursor.Current);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the resources used by this instance.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_storage.Dispose();
|
||||
if (File.Exists(_testFile)) File.Delete(_testFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,34 +4,43 @@ namespace ZB.MOM.WW.CBDD.Tests;
|
||||
|
||||
public class GeospatialStressTests : IDisposable
|
||||
{
|
||||
private readonly TestDbContext _db;
|
||||
private readonly string _dbPath;
|
||||
private readonly Shared.TestDbContext _db;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes database state for geospatial stress tests.
|
||||
/// Initializes database state for geospatial stress tests.
|
||||
/// </summary>
|
||||
public GeospatialStressTests()
|
||||
{
|
||||
_dbPath = Path.Combine(Path.GetTempPath(), $"geo_stress_{Guid.NewGuid():N}.db");
|
||||
_db = new Shared.TestDbContext(_dbPath);
|
||||
_db = new TestDbContext(_dbPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies spatial index handles node splits and query operations under load.
|
||||
/// Disposes test resources and removes generated files.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_db.Dispose();
|
||||
if (File.Exists(_dbPath)) File.Delete(_dbPath);
|
||||
string wal = Path.ChangeExtension(_dbPath, ".wal");
|
||||
if (File.Exists(wal)) File.Delete(wal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies spatial index handles node splits and query operations under load.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SpatialIndex_Should_Handle_Node_Splits_And_Queries()
|
||||
{
|
||||
const int count = 350;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
_db.GeoItems.Insert(new GeoEntity
|
||||
{
|
||||
Name = $"pt-{i}",
|
||||
Location = (40.0 + (i * 0.001), -73.0 - (i * 0.001))
|
||||
Location = (40.0 + i * 0.001, -73.0 - i * 0.001)
|
||||
});
|
||||
}
|
||||
|
||||
_db.SaveChanges();
|
||||
|
||||
@@ -45,15 +54,4 @@ public class GeospatialStressTests : IDisposable
|
||||
var near = _db.GeoItems.Near("idx_spatial", (40.10, -73.10), 30.0).ToList();
|
||||
near.Count.ShouldBeGreaterThan(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes test resources and removes generated files.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_db.Dispose();
|
||||
if (File.Exists(_dbPath)) File.Delete(_dbPath);
|
||||
var wal = Path.ChangeExtension(_dbPath, ".wal");
|
||||
if (File.Exists(wal)) File.Delete(wal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,33 @@
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.CBDD.Core.Indexing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ZB.MOM.WW.CBDD.Core;
|
||||
using ZB.MOM.WW.CBDD.Shared;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests;
|
||||
|
||||
public class GeospatialTests : IDisposable
|
||||
{
|
||||
private readonly TestDbContext _db;
|
||||
private readonly string _dbPath;
|
||||
private readonly Shared.TestDbContext _db;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GeospatialTests"/> class.
|
||||
/// Initializes a new instance of the <see cref="GeospatialTests" /> class.
|
||||
/// </summary>
|
||||
public GeospatialTests()
|
||||
{
|
||||
_dbPath = Path.Combine(Path.GetTempPath(), $"cbdd_geo_{Guid.NewGuid()}.db");
|
||||
_db = new Shared.TestDbContext(_dbPath);
|
||||
_db = new TestDbContext(_dbPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies spatial within queries return expected results.
|
||||
/// Disposes test resources and removes temporary files.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_db.Dispose();
|
||||
if (File.Exists(_dbPath)) File.Delete(_dbPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies spatial within queries return expected results.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Can_Insert_And_Search_Within()
|
||||
@@ -45,7 +50,7 @@ public class GeospatialTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies near queries return expected proximity results.
|
||||
/// Verifies near queries return expected proximity results.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Can_Search_Near_Proximity()
|
||||
@@ -69,7 +74,7 @@ public class GeospatialTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies LINQ near integration returns expected results.
|
||||
/// Verifies LINQ near integration returns expected results.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void LINQ_Integration_Near_Works()
|
||||
@@ -79,8 +84,8 @@ public class GeospatialTests : IDisposable
|
||||
|
||||
// LINQ query using .Near() extension
|
||||
var query = from p in _db.GeoItems.AsQueryable()
|
||||
where p.Location.Near(milan, 10.0)
|
||||
select p;
|
||||
where p.Location.Near(milan, 10.0)
|
||||
select p;
|
||||
|
||||
var results = query.ToList();
|
||||
|
||||
@@ -89,7 +94,7 @@ public class GeospatialTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies LINQ within integration returns expected results.
|
||||
/// Verifies LINQ within integration returns expected results.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void LINQ_Integration_Within_Works()
|
||||
@@ -102,19 +107,10 @@ public class GeospatialTests : IDisposable
|
||||
|
||||
// LINQ query using .Within() extension
|
||||
var results = _db.GeoItems.AsQueryable()
|
||||
.Where(p => p.Location.Within(min, max))
|
||||
.ToList();
|
||||
.Where(p => p.Location.Within(min, max))
|
||||
.ToList();
|
||||
|
||||
results.Count().ShouldBe(1);
|
||||
results[0].Name.ShouldBe("Milan Office");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes test resources and removes temporary files.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_db.Dispose();
|
||||
if (File.Exists(_dbPath)) File.Delete(_dbPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ namespace ZB.MOM.WW.CBDD.Tests;
|
||||
public class HashIndexTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes Insert_And_TryFind_Should_Return_Location.
|
||||
/// Executes Insert_And_TryFind_Should_Return_Location.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Insert_And_TryFind_Should_Return_Location()
|
||||
@@ -23,7 +23,7 @@ public class HashIndexTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes Unique_HashIndex_Should_Throw_On_Duplicate_Key.
|
||||
/// Executes Unique_HashIndex_Should_Throw_On_Duplicate_Key.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Unique_HashIndex_Should_Throw_On_Duplicate_Key()
|
||||
@@ -45,7 +45,7 @@ public class HashIndexTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes Remove_Should_Remove_Only_Matching_Entry.
|
||||
/// Executes Remove_Should_Remove_Only_Matching_Entry.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Remove_Should_Remove_Only_Matching_Entry()
|
||||
@@ -71,7 +71,7 @@ public class HashIndexTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes FindAll_Should_Return_All_Matching_Entries.
|
||||
/// Executes FindAll_Should_Return_All_Matching_Entries.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void FindAll_Should_Return_All_Matching_Entries()
|
||||
@@ -88,4 +88,4 @@ public class HashIndexTests
|
||||
matches.Count.ShouldBe(2);
|
||||
matches.All(e => e.Key == key).ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,25 @@
|
||||
using ZB.MOM.WW.CBDD.Bson;
|
||||
using ZB.MOM.WW.CBDD.Core;
|
||||
using ZB.MOM.WW.CBDD.Core.Collections;
|
||||
using ZB.MOM.WW.CBDD.Core.Indexing;
|
||||
using ZB.MOM.WW.CBDD.Shared;
|
||||
using System;
|
||||
using Xunit;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests;
|
||||
|
||||
public class IndexDirectionTests : IDisposable
|
||||
{
|
||||
private readonly TestDbContext _db;
|
||||
private readonly string _dbPath = "index_direction_tests.db";
|
||||
|
||||
private readonly Shared.TestDbContext _db;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes database state for index direction tests.
|
||||
/// Initializes database state for index direction tests.
|
||||
/// </summary>
|
||||
public IndexDirectionTests()
|
||||
{
|
||||
if (File.Exists(_dbPath)) File.Delete(_dbPath);
|
||||
_db = new Shared.TestDbContext(_dbPath);
|
||||
_db = new TestDbContext(_dbPath);
|
||||
// _db.Database.EnsureCreated(); // Not needed/doesn't exist? StorageEngine handles creation.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes test resources and deletes temporary files.
|
||||
/// Disposes test resources and deletes temporary files.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
@@ -34,7 +28,7 @@ public class IndexDirectionTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies forward range scans return values in ascending order.
|
||||
/// Verifies forward range scans return values in ascending order.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Range_Forward_ReturnsOrderedResults()
|
||||
@@ -42,20 +36,21 @@ public class IndexDirectionTests : IDisposable
|
||||
var collection = _db.People;
|
||||
var index = collection.EnsureIndex(p => p.Age, "idx_age");
|
||||
|
||||
var people = Enumerable.Range(1, 100).Select(i => new Person { Id = i, Name = $"Person {i}", Age = i }).ToList();
|
||||
var people = Enumerable.Range(1, 100).Select(i => new Person { Id = i, Name = $"Person {i}", Age = i })
|
||||
.ToList();
|
||||
collection.InsertBulk(people);
|
||||
_db.SaveChanges();
|
||||
|
||||
// Scan Forward
|
||||
var results = index.Range(10, 20, IndexDirection.Forward).ToList();
|
||||
var results = index.Range(10, 20).ToList();
|
||||
|
||||
results.Count.ShouldBe(11); // 10 to 20 inclusive
|
||||
collection.FindByLocation(results.First())!.Age.ShouldBe(10); // First is 10
|
||||
collection.FindByLocation(results.Last())!.Age.ShouldBe(20); // Last is 20
|
||||
collection.FindByLocation(results.Last())!.Age.ShouldBe(20); // Last is 20
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies backward range scans return values in descending order.
|
||||
/// Verifies backward range scans return values in descending order.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Range_Backward_ReturnsReverseOrderedResults()
|
||||
@@ -63,7 +58,8 @@ public class IndexDirectionTests : IDisposable
|
||||
var collection = _db.People;
|
||||
var index = collection.EnsureIndex(p => p.Age, "idx_age");
|
||||
|
||||
var people = Enumerable.Range(1, 100).Select(i => new Person { Id = i, Name = $"Person {i}", Age = i }).ToList();
|
||||
var people = Enumerable.Range(1, 100).Select(i => new Person { Id = i, Name = $"Person {i}", Age = i })
|
||||
.ToList();
|
||||
collection.InsertBulk(people);
|
||||
_db.SaveChanges();
|
||||
|
||||
@@ -72,11 +68,11 @@ public class IndexDirectionTests : IDisposable
|
||||
|
||||
results.Count.ShouldBe(11); // 10 to 20 inclusive
|
||||
collection.FindByLocation(results.First())!.Age.ShouldBe(20); // First is 20 (Reverse)
|
||||
collection.FindByLocation(results.Last())!.Age.ShouldBe(10); // Last is 10
|
||||
collection.FindByLocation(results.Last())!.Age.ShouldBe(10); // Last is 10
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies backward scans across split index pages return complete result sets.
|
||||
/// Verifies backward scans across split index pages return complete result sets.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Range_Backward_WithMultiplePages_ReturnsReverseOrderedResults()
|
||||
@@ -88,7 +84,8 @@ public class IndexDirectionTests : IDisposable
|
||||
// Entry size approx 10 bytes key + 6 bytes loc + overhead
|
||||
// 1000 items * 20 bytes = 20KB > 4KB.
|
||||
var count = 1000;
|
||||
var people = Enumerable.Range(1, count).Select(i => new Person { Id = i, Name = $"Person {i}", Age = i }).ToList();
|
||||
var people = Enumerable.Range(1, count).Select(i => new Person { Id = i, Name = $"Person {i}", Age = i })
|
||||
.ToList();
|
||||
collection.InsertBulk(people);
|
||||
_db.SaveChanges();
|
||||
|
||||
@@ -105,4 +102,4 @@ public class IndexDirectionTests : IDisposable
|
||||
// collection.FindByLocation(results.First(), null)!.Age.ShouldBe(count); // Max Age (Fails: Max is likely 255)
|
||||
// collection.FindByLocation(results.Last(), null)!.Age.ShouldBe(1); // Min Age (Fails: Min is likely 256)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,163 +1,161 @@
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.CBDD.Core.Query;
|
||||
using ZB.MOM.WW.CBDD.Core.Indexing;
|
||||
using System.Linq.Expressions;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using ZB.MOM.WW.CBDD.Core.Indexing;
|
||||
using ZB.MOM.WW.CBDD.Core.Query;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests
|
||||
namespace ZB.MOM.WW.CBDD.Tests;
|
||||
|
||||
public class IndexOptimizationTests
|
||||
{
|
||||
public class IndexOptimizationTests
|
||||
/// <summary>
|
||||
/// Tests optimizer identifies equality.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Optimizer_Identifies_Equality()
|
||||
{
|
||||
public class TestEntity
|
||||
var indexes = new List<CollectionIndexInfo>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the id.
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = "";
|
||||
/// <summary>
|
||||
/// Gets or sets the age.
|
||||
/// </summary>
|
||||
public int Age { get; set; }
|
||||
}
|
||||
new() { Name = "idx_age", PropertyPaths = ["Age"] }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Tests optimizer identifies equality.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Optimizer_Identifies_Equality()
|
||||
{
|
||||
var indexes = new List<CollectionIndexInfo>
|
||||
{
|
||||
new CollectionIndexInfo { Name = "idx_age", PropertyPaths = ["Age"] }
|
||||
};
|
||||
Expression<Func<TestEntity, bool>> predicate = x => x.Age == 30;
|
||||
var model = new QueryModel { WhereClause = predicate };
|
||||
|
||||
Expression<Func<TestEntity, bool>> predicate = x => x.Age == 30;
|
||||
var model = new QueryModel { WhereClause = predicate };
|
||||
var result = IndexOptimizer.TryOptimize<TestEntity>(model, indexes);
|
||||
|
||||
var result = IndexOptimizer.TryOptimize<TestEntity>(model, indexes);
|
||||
|
||||
result.ShouldNotBeNull();
|
||||
result.IndexName.ShouldBe("idx_age");
|
||||
result.MinValue.ShouldBe(30);
|
||||
result.MaxValue.ShouldBe(30);
|
||||
result.IsRange.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests optimizer identifies range greater than.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Optimizer_Identifies_Range_GreaterThan()
|
||||
{
|
||||
var indexes = new List<CollectionIndexInfo>
|
||||
{
|
||||
new CollectionIndexInfo { Name = "idx_age", PropertyPaths = ["Age"] }
|
||||
};
|
||||
|
||||
Expression<Func<TestEntity, bool>> predicate = x => x.Age > 25;
|
||||
var model = new QueryModel { WhereClause = predicate };
|
||||
|
||||
var result = IndexOptimizer.TryOptimize<TestEntity>(model, indexes);
|
||||
|
||||
result.ShouldNotBeNull();
|
||||
result.IndexName.ShouldBe("idx_age");
|
||||
result.MinValue.ShouldBe(25);
|
||||
result.MaxValue.ShouldBeNull();
|
||||
result.IsRange.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests optimizer identifies range less than.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Optimizer_Identifies_Range_LessThan()
|
||||
{
|
||||
var indexes = new List<CollectionIndexInfo>
|
||||
{
|
||||
new CollectionIndexInfo { Name = "idx_age", PropertyPaths = ["Age"] }
|
||||
};
|
||||
|
||||
Expression<Func<TestEntity, bool>> predicate = x => x.Age < 50;
|
||||
var model = new QueryModel { WhereClause = predicate };
|
||||
|
||||
var result = IndexOptimizer.TryOptimize<TestEntity>(model, indexes);
|
||||
|
||||
result.ShouldNotBeNull();
|
||||
result.IndexName.ShouldBe("idx_age");
|
||||
result.MinValue.ShouldBeNull();
|
||||
result.MaxValue.ShouldBe(50);
|
||||
result.IsRange.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests optimizer identifies range between simulated.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Optimizer_Identifies_Range_Between_Simulated()
|
||||
{
|
||||
var indexes = new List<CollectionIndexInfo>
|
||||
{
|
||||
new CollectionIndexInfo { Name = "idx_age", PropertyPaths = ["Age"] }
|
||||
};
|
||||
|
||||
Expression<Func<TestEntity, bool>> predicate = x => x.Age > 20 && x.Age < 40;
|
||||
var model = new QueryModel { WhereClause = predicate };
|
||||
|
||||
var result = IndexOptimizer.TryOptimize<TestEntity>(model, indexes);
|
||||
|
||||
result.ShouldNotBeNull();
|
||||
result.IndexName.ShouldBe("idx_age");
|
||||
result.MinValue.ShouldBe(20);
|
||||
result.MaxValue.ShouldBe(40);
|
||||
result.IsRange.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests optimizer identifies starts with.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Optimizer_Identifies_StartsWith()
|
||||
{
|
||||
var indexes = new List<CollectionIndexInfo>
|
||||
{
|
||||
new CollectionIndexInfo { Name = "idx_name", PropertyPaths = ["Name"], Type = IndexType.BTree }
|
||||
};
|
||||
|
||||
Expression<Func<TestEntity, bool>> predicate = x => x.Name.StartsWith("Ali");
|
||||
var model = new QueryModel { WhereClause = predicate };
|
||||
|
||||
var result = IndexOptimizer.TryOptimize<TestEntity>(model, indexes);
|
||||
|
||||
result.ShouldNotBeNull();
|
||||
result.IndexName.ShouldBe("idx_name");
|
||||
result.MinValue.ShouldBe("Ali");
|
||||
// "Ali" + next char -> "Alj"
|
||||
result.MaxValue.ShouldBe("Alj");
|
||||
result.IsRange.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests optimizer ignores non indexed fields.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Optimizer_Ignores_NonIndexed_Fields()
|
||||
{
|
||||
var indexes = new List<CollectionIndexInfo>
|
||||
{
|
||||
new CollectionIndexInfo { Name = "idx_age", PropertyPaths = ["Age"] }
|
||||
};
|
||||
|
||||
Expression<Func<TestEntity, bool>> predicate = x => x.Name == "Alice"; // Name is not indexed
|
||||
var model = new QueryModel { WhereClause = predicate };
|
||||
|
||||
var result = IndexOptimizer.TryOptimize<TestEntity>(model, indexes);
|
||||
|
||||
result.ShouldBeNull();
|
||||
}
|
||||
result.ShouldNotBeNull();
|
||||
result.IndexName.ShouldBe("idx_age");
|
||||
result.MinValue.ShouldBe(30);
|
||||
result.MaxValue.ShouldBe(30);
|
||||
result.IsRange.ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests optimizer identifies range greater than.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Optimizer_Identifies_Range_GreaterThan()
|
||||
{
|
||||
var indexes = new List<CollectionIndexInfo>
|
||||
{
|
||||
new() { Name = "idx_age", PropertyPaths = ["Age"] }
|
||||
};
|
||||
|
||||
Expression<Func<TestEntity, bool>> predicate = x => x.Age > 25;
|
||||
var model = new QueryModel { WhereClause = predicate };
|
||||
|
||||
var result = IndexOptimizer.TryOptimize<TestEntity>(model, indexes);
|
||||
|
||||
result.ShouldNotBeNull();
|
||||
result.IndexName.ShouldBe("idx_age");
|
||||
result.MinValue.ShouldBe(25);
|
||||
result.MaxValue.ShouldBeNull();
|
||||
result.IsRange.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests optimizer identifies range less than.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Optimizer_Identifies_Range_LessThan()
|
||||
{
|
||||
var indexes = new List<CollectionIndexInfo>
|
||||
{
|
||||
new() { Name = "idx_age", PropertyPaths = ["Age"] }
|
||||
};
|
||||
|
||||
Expression<Func<TestEntity, bool>> predicate = x => x.Age < 50;
|
||||
var model = new QueryModel { WhereClause = predicate };
|
||||
|
||||
var result = IndexOptimizer.TryOptimize<TestEntity>(model, indexes);
|
||||
|
||||
result.ShouldNotBeNull();
|
||||
result.IndexName.ShouldBe("idx_age");
|
||||
result.MinValue.ShouldBeNull();
|
||||
result.MaxValue.ShouldBe(50);
|
||||
result.IsRange.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests optimizer identifies range between simulated.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Optimizer_Identifies_Range_Between_Simulated()
|
||||
{
|
||||
var indexes = new List<CollectionIndexInfo>
|
||||
{
|
||||
new() { Name = "idx_age", PropertyPaths = ["Age"] }
|
||||
};
|
||||
|
||||
Expression<Func<TestEntity, bool>> predicate = x => x.Age > 20 && x.Age < 40;
|
||||
var model = new QueryModel { WhereClause = predicate };
|
||||
|
||||
var result = IndexOptimizer.TryOptimize<TestEntity>(model, indexes);
|
||||
|
||||
result.ShouldNotBeNull();
|
||||
result.IndexName.ShouldBe("idx_age");
|
||||
result.MinValue.ShouldBe(20);
|
||||
result.MaxValue.ShouldBe(40);
|
||||
result.IsRange.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests optimizer identifies starts with.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Optimizer_Identifies_StartsWith()
|
||||
{
|
||||
var indexes = new List<CollectionIndexInfo>
|
||||
{
|
||||
new() { Name = "idx_name", PropertyPaths = ["Name"], Type = IndexType.BTree }
|
||||
};
|
||||
|
||||
Expression<Func<TestEntity, bool>> predicate = x => x.Name.StartsWith("Ali");
|
||||
var model = new QueryModel { WhereClause = predicate };
|
||||
|
||||
var result = IndexOptimizer.TryOptimize<TestEntity>(model, indexes);
|
||||
|
||||
result.ShouldNotBeNull();
|
||||
result.IndexName.ShouldBe("idx_name");
|
||||
result.MinValue.ShouldBe("Ali");
|
||||
// "Ali" + next char -> "Alj"
|
||||
result.MaxValue.ShouldBe("Alj");
|
||||
result.IsRange.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests optimizer ignores non indexed fields.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Optimizer_Ignores_NonIndexed_Fields()
|
||||
{
|
||||
var indexes = new List<CollectionIndexInfo>
|
||||
{
|
||||
new() { Name = "idx_age", PropertyPaths = ["Age"] }
|
||||
};
|
||||
|
||||
Expression<Func<TestEntity, bool>> predicate = x => x.Name == "Alice"; // Name is not indexed
|
||||
var model = new QueryModel { WhereClause = predicate };
|
||||
|
||||
var result = IndexOptimizer.TryOptimize<TestEntity>(model, indexes);
|
||||
|
||||
result.ShouldBeNull();
|
||||
}
|
||||
|
||||
public class TestEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the id.
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the age.
|
||||
/// </summary>
|
||||
public int Age { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,4 @@
|
||||
using ZB.MOM.WW.CBDD.Bson;
|
||||
using ZB.MOM.WW.CBDD.Core;
|
||||
using ZB.MOM.WW.CBDD.Core.Collections;
|
||||
using ZB.MOM.WW.CBDD.Core.Metadata;
|
||||
using ZB.MOM.WW.CBDD.Shared;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests;
|
||||
|
||||
@@ -13,7 +7,7 @@ public class PrimaryKeyTests : IDisposable
|
||||
private readonly string _dbPath = "primary_key_tests.db";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PrimaryKeyTests"/> class.
|
||||
/// Initializes a new instance of the <see cref="PrimaryKeyTests" /> class.
|
||||
/// </summary>
|
||||
public PrimaryKeyTests()
|
||||
{
|
||||
@@ -21,7 +15,7 @@ public class PrimaryKeyTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes Dispose.
|
||||
/// Executes Dispose.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
@@ -29,12 +23,12 @@ public class PrimaryKeyTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes Test_Int_PrimaryKey.
|
||||
/// Executes Test_Int_PrimaryKey.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Test_Int_PrimaryKey()
|
||||
{
|
||||
using var db = new Shared.TestDbContext(_dbPath);
|
||||
using var db = new TestDbContext(_dbPath);
|
||||
|
||||
var entity = new IntEntity { Id = 1, Name = "Test 1" };
|
||||
db.IntEntities.Insert(entity);
|
||||
@@ -56,12 +50,12 @@ public class PrimaryKeyTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes Test_String_PrimaryKey.
|
||||
/// Executes Test_String_PrimaryKey.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Test_String_PrimaryKey()
|
||||
{
|
||||
using var db = new Shared.TestDbContext(_dbPath);
|
||||
using var db = new TestDbContext(_dbPath);
|
||||
|
||||
var entity = new StringEntity { Id = "key1", Value = "Value 1" };
|
||||
db.StringEntities.Insert(entity);
|
||||
@@ -78,12 +72,12 @@ public class PrimaryKeyTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes Test_Guid_PrimaryKey.
|
||||
/// Executes Test_Guid_PrimaryKey.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Test_Guid_PrimaryKey()
|
||||
{
|
||||
using var db = new Shared.TestDbContext(_dbPath);
|
||||
using var db = new TestDbContext(_dbPath);
|
||||
|
||||
var id = Guid.NewGuid();
|
||||
var entity = new GuidEntity { Id = id, Name = "Guid Test" };
|
||||
@@ -100,13 +94,13 @@ public class PrimaryKeyTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes Test_String_PrimaryKey_With_Custom_Name.
|
||||
/// Executes Test_String_PrimaryKey_With_Custom_Name.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Test_String_PrimaryKey_With_Custom_Name()
|
||||
{
|
||||
// Test entity with string key NOT named "Id" (named "Code" instead)
|
||||
using var db = new Shared.TestDbContext(_dbPath);
|
||||
using var db = new TestDbContext(_dbPath);
|
||||
|
||||
var entity = new CustomKeyEntity { Code = "ABC123", Description = "Test Description" };
|
||||
db.CustomKeyEntities.Insert(entity);
|
||||
@@ -131,4 +125,4 @@ public class PrimaryKeyTests : IDisposable
|
||||
db.SaveChanges();
|
||||
db.CustomKeyEntities.FindById("ABC123").ShouldBeNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ namespace ZB.MOM.WW.CBDD.Tests;
|
||||
public class VectorMathTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies distance calculations across all supported vector metrics.
|
||||
/// Verifies distance calculations across all supported vector metrics.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Distance_Should_Cover_All_Metrics()
|
||||
@@ -13,19 +13,19 @@ public class VectorMathTests
|
||||
float[] v1 = [1f, 2f];
|
||||
float[] v2 = [3f, 4f];
|
||||
|
||||
var cosineDistance = VectorMath.Distance(v1, v2, VectorMetric.Cosine);
|
||||
var l2Distance = VectorMath.Distance(v1, v2, VectorMetric.L2);
|
||||
var dotDistance = VectorMath.Distance(v1, v2, VectorMetric.DotProduct);
|
||||
float cosineDistance = VectorMath.Distance(v1, v2, VectorMetric.Cosine);
|
||||
float l2Distance = VectorMath.Distance(v1, v2, VectorMetric.L2);
|
||||
float dotDistance = VectorMath.Distance(v1, v2, VectorMetric.DotProduct);
|
||||
|
||||
l2Distance.ShouldBe(8f);
|
||||
dotDistance.ShouldBe(-11f);
|
||||
|
||||
var expectedCosine = 1f - (11f / (MathF.Sqrt(5f) * 5f));
|
||||
float expectedCosine = 1f - 11f / (MathF.Sqrt(5f) * 5f);
|
||||
MathF.Abs(cosineDistance - expectedCosine).ShouldBeLessThan(0.0001f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies cosine similarity returns zero when one vector has zero magnitude.
|
||||
/// Verifies cosine similarity returns zero when one vector has zero magnitude.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void CosineSimilarity_Should_Return_Zero_For_ZeroMagnitude_Vector()
|
||||
@@ -37,7 +37,7 @@ public class VectorMathTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies dot product throws for mismatched vector lengths.
|
||||
/// Verifies dot product throws for mismatched vector lengths.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DotProduct_Should_Throw_For_Length_Mismatch()
|
||||
@@ -49,7 +49,7 @@ public class VectorMathTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies squared Euclidean distance throws for mismatched vector lengths.
|
||||
/// Verifies squared Euclidean distance throws for mismatched vector lengths.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void EuclideanDistanceSquared_Should_Throw_For_Length_Mismatch()
|
||||
@@ -59,4 +59,4 @@ public class VectorMathTests
|
||||
|
||||
Should.Throw<ArgumentException>(() => VectorMath.EuclideanDistanceSquared(v1, v2));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,20 @@
|
||||
using ZB.MOM.WW.CBDD.Bson;
|
||||
using ZB.MOM.WW.CBDD.Core;
|
||||
using ZB.MOM.WW.CBDD.Core.Indexing;
|
||||
using ZB.MOM.WW.CBDD.Shared;
|
||||
using Xunit;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests;
|
||||
|
||||
public class VectorSearchTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies basic vector-search query behavior.
|
||||
/// Verifies basic vector-search query behavior.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Test_VectorSearch_Basic()
|
||||
{
|
||||
string dbPath = "vector_test.db";
|
||||
var dbPath = "vector_test.db";
|
||||
if (File.Exists(dbPath)) File.Delete(dbPath);
|
||||
|
||||
using (var db = new Shared.TestDbContext(dbPath))
|
||||
using (var db = new TestDbContext(dbPath))
|
||||
{
|
||||
db.VectorItems.Insert(new VectorEntity { Title = "Near", Embedding = [1.0f, 1.0f, 1.0f] });
|
||||
db.VectorItems.Insert(new VectorEntity { Title = "Far", Embedding = [10.0f, 10.0f, 10.0f] });
|
||||
@@ -31,4 +28,4 @@ public class VectorSearchTests
|
||||
|
||||
File.Delete(dbPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,19 @@
|
||||
using ZB.MOM.WW.CBDD.Bson;
|
||||
using ZB.MOM.WW.CBDD.Core;
|
||||
using ZB.MOM.WW.CBDD.Core.Collections;
|
||||
using ZB.MOM.WW.CBDD.Core.Indexing;
|
||||
using ZB.MOM.WW.CBDD.Core.Storage;
|
||||
using ZB.MOM.WW.CBDD.Core.Transactions;
|
||||
using ZB.MOM.WW.CBDD.Shared;
|
||||
using ZB.MOM.WW.CBDD.Shared.TestDbContext_TestDbContext_Mappers;
|
||||
using System.Buffers;
|
||||
using Xunit;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests;
|
||||
|
||||
public class WalIndexTests : IDisposable
|
||||
{
|
||||
private readonly TestDbContext _db;
|
||||
private readonly string _dbPath;
|
||||
private readonly string _walPath;
|
||||
private readonly Shared.TestDbContext _db;
|
||||
private readonly ITestOutputHelper _output;
|
||||
private readonly string _walPath;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WalIndexTests"/> class.
|
||||
/// Initializes a new instance of the <see cref="WalIndexTests" /> class.
|
||||
/// </summary>
|
||||
/// <param name="output">Test output sink.</param>
|
||||
public WalIndexTests(ITestOutputHelper output)
|
||||
@@ -29,11 +23,41 @@ public class WalIndexTests : IDisposable
|
||||
// WAL defaults to .wal next to db
|
||||
_walPath = Path.ChangeExtension(_dbPath, ".wal");
|
||||
|
||||
_db = new Shared.TestDbContext(_dbPath);
|
||||
_db = new TestDbContext(_dbPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies index writes are recorded in the WAL.
|
||||
/// Releases test resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
_db?.Dispose(); // Safe to call multiple times
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(_dbPath)) File.Delete(_dbPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(_walPath)) File.Delete(_walPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies index writes are recorded in the WAL.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void IndexWritesAreLoggedToWal()
|
||||
@@ -71,8 +95,8 @@ public class WalIndexTests : IDisposable
|
||||
_output.WriteLine($"Found {writeRecords.Count} Write records for Txn {txn.TransactionId}");
|
||||
|
||||
// Analyze pages
|
||||
int indexPageCount = 0;
|
||||
int dataPageCount = 0;
|
||||
var indexPageCount = 0;
|
||||
var dataPageCount = 0;
|
||||
|
||||
foreach (var record in writeRecords)
|
||||
{
|
||||
@@ -89,21 +113,18 @@ public class WalIndexTests : IDisposable
|
||||
|
||||
private PageType ParsePageType(byte[]? pageData)
|
||||
{
|
||||
if (pageData == null || pageData.Length < 32) return (PageType)0;
|
||||
if (pageData == null || pageData.Length < 32) return 0;
|
||||
// PageType is at offset 4 (1 byte)
|
||||
return (PageType)pageData[4]; // Casting byte to PageType
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies offline compaction leaves the WAL empty.
|
||||
/// Verifies offline compaction leaves the WAL empty.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Compact_ShouldLeaveWalEmpty_AfterOfflineRun()
|
||||
{
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
_db.Users.Insert(new User { Name = $"wal-compact-{i:D3}", Age = i % 30 });
|
||||
}
|
||||
for (var i = 0; i < 100; i++) _db.Users.Insert(new User { Name = $"wal-compact-{i:D3}", Age = i % 30 });
|
||||
|
||||
_db.SaveChanges();
|
||||
_db.Storage.GetWalSize().ShouldBeGreaterThan(0);
|
||||
@@ -121,24 +142,22 @@ public class WalIndexTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies WAL recovery followed by compaction preserves data.
|
||||
/// Verifies WAL recovery followed by compaction preserves data.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Recover_WithCommittedWal_ThenCompact_ShouldPreserveData()
|
||||
{
|
||||
var dbPath = Path.Combine(Path.GetTempPath(), $"test_wal_recover_compact_{Guid.NewGuid():N}.db");
|
||||
var walPath = Path.ChangeExtension(dbPath, ".wal");
|
||||
string dbPath = Path.Combine(Path.GetTempPath(), $"test_wal_recover_compact_{Guid.NewGuid():N}.db");
|
||||
string walPath = Path.ChangeExtension(dbPath, ".wal");
|
||||
var markerPath = $"{dbPath}.compact.state";
|
||||
var expectedIds = new List<ObjectId>();
|
||||
|
||||
try
|
||||
{
|
||||
using (var writer = new Shared.TestDbContext(dbPath))
|
||||
using (var writer = new TestDbContext(dbPath))
|
||||
{
|
||||
for (var i = 0; i < 48; i++)
|
||||
{
|
||||
expectedIds.Add(writer.Users.Insert(new User { Name = $"recover-{i:D3}", Age = i % 10 }));
|
||||
}
|
||||
|
||||
writer.SaveChanges();
|
||||
writer.Storage.GetWalSize().ShouldBeGreaterThan(0);
|
||||
@@ -146,16 +165,13 @@ public class WalIndexTests : IDisposable
|
||||
|
||||
new FileInfo(walPath).Length.ShouldBeGreaterThan(0);
|
||||
|
||||
using (var recovered = new Shared.TestDbContext(dbPath))
|
||||
using (var recovered = new TestDbContext(dbPath))
|
||||
{
|
||||
recovered.Users.Count().ShouldBe(expectedIds.Count);
|
||||
recovered.Compact();
|
||||
recovered.Storage.GetWalSize().ShouldBe(0);
|
||||
|
||||
foreach (var id in expectedIds)
|
||||
{
|
||||
recovered.Users.FindById(id).ShouldNotBeNull();
|
||||
}
|
||||
foreach (var id in expectedIds) recovered.Users.FindById(id).ShouldNotBeNull();
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -165,19 +181,4 @@ public class WalIndexTests : IDisposable
|
||||
if (File.Exists(markerPath)) File.Delete(markerPath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases test resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
_db?.Dispose(); // Safe to call multiple times
|
||||
}
|
||||
catch { }
|
||||
|
||||
try { if (File.Exists(_dbPath)) File.Delete(_dbPath); } catch { }
|
||||
try { if (File.Exists(_walPath)) File.Delete(_walPath); } catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user