diff --git a/Directory.Packages.props b/Directory.Packages.props
index 6f1923c7..f2acca28 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -9,7 +9,7 @@
-
+
@@ -19,7 +19,7 @@
-
+
@@ -27,7 +27,7 @@
-
+
diff --git a/src/Speckle.Sdk.Dependencies/Pools.cs b/src/Speckle.Sdk.Dependencies/Pools.cs
index 5f3451fb..734b7432 100644
--- a/src/Speckle.Sdk.Dependencies/Pools.cs
+++ b/src/Speckle.Sdk.Dependencies/Pools.cs
@@ -1,4 +1,4 @@
-using System.Collections.Concurrent;
+using System.Collections.Concurrent;
using System.Text;
using Microsoft.Extensions.ObjectPool;
@@ -6,6 +6,8 @@ namespace Speckle.Sdk.Dependencies;
public static class Pools
{
+ public const int DefaultCapacity = 50;
+
public static Pool> ObjectDictionaries { get; } = new(new ObjectDictionaryPolicy());
private sealed class ObjectDictionaryPolicy : IPooledObjectPolicy>
@@ -25,7 +27,7 @@ public static class Pools
private sealed class ObjectDictionaryPolicy : IPooledObjectPolicy>
where TKey : notnull
{
- public Dictionary Create() => new(50);
+ public Dictionary Create() => new(DefaultCapacity);
public bool Return(Dictionary obj)
{
@@ -38,7 +40,7 @@ public static class Pools
: IPooledObjectPolicy>
where TKey : notnull
{
- public ConcurrentDictionary Create() => new(Environment.ProcessorCount, 50);
+ public ConcurrentDictionary Create() => new(Environment.ProcessorCount, DefaultCapacity);
public bool Return(ConcurrentDictionary obj)
{
@@ -49,7 +51,7 @@ public static class Pools
private sealed class ObjectListPolicy : IPooledObjectPolicy>
{
- public List Create() => new(50);
+ public List Create() => new(DefaultCapacity);
public bool Return(List obj)
{
diff --git a/src/Speckle.Sdk.Dependencies/Serialization/Batch.cs b/src/Speckle.Sdk.Dependencies/Serialization/Batch.cs
index 368cfd5c..fb6bb14c 100644
--- a/src/Speckle.Sdk.Dependencies/Serialization/Batch.cs
+++ b/src/Speckle.Sdk.Dependencies/Serialization/Batch.cs
@@ -1,10 +1,14 @@
-namespace Speckle.Sdk.Serialisation.V2.Send;
+using System.Buffers;
+using Speckle.Sdk.Dependencies;
-public class Batch(int capacity) : IHasSize
+namespace Speckle.Sdk.Serialisation.V2.Send;
+
+public sealed class Batch : IHasSize, IMemoryOwner
where T : IHasSize
{
+ private static readonly Pool> _pool = Pools.CreateListPool();
#pragma warning disable IDE0032
- private readonly List _items = new(capacity);
+ private readonly List _items = _pool.Get();
private int _batchSize;
#pragma warning restore IDE0032
@@ -22,4 +26,8 @@ public class Batch(int capacity) : IHasSize
public int Size => _batchSize;
public List Items => _items;
+
+ public void Dispose() => _pool.Return(_items);
+
+ public Memory Memory => new(_items.ToArray());
}
diff --git a/src/Speckle.Sdk.Dependencies/Serialization/ChannelExtensions.cs b/src/Speckle.Sdk.Dependencies/Serialization/ChannelExtensions.cs
index a03a7337..e420a235 100644
--- a/src/Speckle.Sdk.Dependencies/Serialization/ChannelExtensions.cs
+++ b/src/Speckle.Sdk.Dependencies/Serialization/ChannelExtensions.cs
@@ -1,11 +1,12 @@
-using System.Threading.Channels;
+using System.Buffers;
+using System.Threading.Channels;
using Open.ChannelExtensions;
namespace Speckle.Sdk.Serialisation.V2.Send;
public static class ChannelExtensions
{
- public static BatchingChannelReader> BatchBySize(
+ public static BatchingChannelReader> BatchBySize(
this ChannelReader source,
int batchSize,
bool singleReader = false,
diff --git a/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs b/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs
index 97c00f10..99190998 100644
--- a/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs
+++ b/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs
@@ -1,3 +1,4 @@
+using System.Buffers;
using System.Threading.Channels;
using Open.ChannelExtensions;
using Speckle.Sdk.Serialisation.V2.Send;
@@ -13,7 +14,7 @@ public abstract class ChannelSaver
private const int MAX_PARALLELISM_HTTP = 4;
private const int HTTP_CAPACITY = 500;
private const int MAX_CACHE_WRITE_PARALLELISM = 4;
- private const int MAX_CACHE_BATCH = 200;
+ private const int MAX_CACHE_BATCH = 500;
private readonly Channel _checkCacheChannel = Channel.CreateBounded(
new BoundedChannelOptions(SEND_CAPACITY)
@@ -46,7 +47,13 @@ public abstract class ChannelSaver
public ValueTask Save(T item, CancellationToken cancellationToken = default) =>
_checkCacheChannel.Writer.WriteAsync(item, cancellationToken);
- public abstract Task> SendToServer(Batch batch, CancellationToken cancellationToken);
+ public async Task> SendToServer(IMemoryOwner batch, CancellationToken cancellationToken)
+ {
+ await SendToServer((Batch)batch, cancellationToken).ConfigureAwait(false);
+ return batch;
+ }
+
+ public abstract Task SendToServer(Batch batch, CancellationToken cancellationToken);
public Task Done()
{
diff --git a/src/Speckle.Sdk.Dependencies/Serialization/SizeBatchingChannelReader.cs b/src/Speckle.Sdk.Dependencies/Serialization/SizeBatchingChannelReader.cs
index 0f66b49b..e979aba9 100644
--- a/src/Speckle.Sdk.Dependencies/Serialization/SizeBatchingChannelReader.cs
+++ b/src/Speckle.Sdk.Dependencies/Serialization/SizeBatchingChannelReader.cs
@@ -1,3 +1,4 @@
+using System.Buffers;
using System.Threading.Channels;
using Open.ChannelExtensions;
@@ -13,20 +14,20 @@ public class SizeBatchingChannelReader(
int batchSize,
bool singleReader,
bool syncCont = false
-) : BatchingChannelReader>(x => new(x), source, batchSize, singleReader, syncCont)
+) : BatchingChannelReader>(x => new Batch(), source, batchSize, singleReader, syncCont)
where T : IHasSize
{
- protected override Batch CreateBatch(int capacity) => new(capacity);
+ protected override IMemoryOwner CreateBatch(int capacity) => new Batch();
- protected override void TrimBatch(ref Batch batch, bool isVerifiedFull)
+ protected override void TrimBatch(ref IMemoryOwner batch, bool isVerifiedFull)
{
if (!isVerifiedFull)
{
- batch.TrimExcess();
+ ((Batch)batch).TrimExcess();
}
}
- protected override void AddBatchItem(Batch batch, T item) => batch.Add(item);
+ protected override void AddBatchItem(IMemoryOwner batch, T item) => ((Batch)batch).Add(item);
- protected override int GetBatchSize(Batch batch) => batch.Size;
+ protected override int GetBatchSize(IMemoryOwner batch) => ((Batch)batch).Size;
}
diff --git a/src/Speckle.Sdk.Dependencies/packages.lock.json b/src/Speckle.Sdk.Dependencies/packages.lock.json
index 38bc5fac..123ae1af 100644
--- a/src/Speckle.Sdk.Dependencies/packages.lock.json
+++ b/src/Speckle.Sdk.Dependencies/packages.lock.json
@@ -19,9 +19,9 @@
},
"Microsoft.Extensions.ObjectPool": {
"type": "Direct",
- "requested": "[8.0.11, )",
- "resolved": "8.0.11",
- "contentHash": "6ApKcHNJigXBfZa6XlDQ8feJpq7SG1ogZXg6M4FiNzgd6irs3LUAzo0Pfn4F2ZI9liGnH1XIBR/OtSbZmJAV5w=="
+ "requested": "[9.0.0, )",
+ "resolved": "9.0.0",
+ "contentHash": "UbsU/gYe4nv1DeqMXIVzDfNNek7Sk2kKuAOXL/Y+sLcAR0HwFUqzg1EPiU88jeHNe0g81aPvvHbvHarQr3r9IA=="
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
@@ -44,13 +44,13 @@
},
"Open.ChannelExtensions": {
"type": "Direct",
- "requested": "[8.6.0, )",
- "resolved": "8.6.0",
- "contentHash": "g5axz417bA6FXifJaBlB0l62gV7dYmknXx0n8lT/LSA3+7isaGMsOjJp5J+H/yXDRe4r+KZrE+bzQcs4Ets2kA==",
+ "requested": "[9.0.0, )",
+ "resolved": "9.0.0",
+ "contentHash": "DP+l5S6G46wcuY4I4kNXE+RDOmJr0DKuMienOdt0mMBN9z7vmLSC8YQbqCyb9i9LNjXj1tgCx5LyitJiRr/v7g==",
"dependencies": {
- "Microsoft.Bcl.AsyncInterfaces": "8.0.0",
- "System.Collections.Immutable": "8.0.0",
- "System.Threading.Channels": "8.0.0"
+ "Microsoft.Bcl.AsyncInterfaces": "9.0.0",
+ "System.Collections.Immutable": "9.0.0",
+ "System.Threading.Channels": "9.0.0"
}
},
"Polly": {
@@ -88,10 +88,11 @@
},
"System.Threading.Channels": {
"type": "Direct",
- "requested": "[8.0.0, )",
- "resolved": "8.0.0",
- "contentHash": "CMaFr7v+57RW7uZfZkPExsPB6ljwzhjACWW1gfU35Y56rk72B/Wu+sTqxVmGSk4SFUlPc3cjeKND0zktziyjBA==",
+ "requested": "[9.0.0, )",
+ "resolved": "9.0.0",
+ "contentHash": "hzACdIf1C+4Dqos5ijV404b94+LqfIC8nfS3mNpCDFWowb1N3PNfJPopneq32ahWlDeyaPZJqjBk76YFR69Rpg==",
"dependencies": {
+ "Microsoft.Bcl.AsyncInterfaces": "9.0.0",
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
@@ -122,8 +123,8 @@
},
"System.Collections.Immutable": {
"type": "Transitive",
- "resolved": "8.0.0",
- "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==",
+ "resolved": "9.0.0",
+ "contentHash": "QhkXUl2gNrQtvPmtBTQHb0YsUrDiDQ2QS09YbtTTiSjGcf7NBqtYbrG/BE06zcBPCKEwQGzIv13IVdXNOSub2w==",
"dependencies": {
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
@@ -160,8 +161,8 @@
"Microsoft.Bcl.AsyncInterfaces": {
"type": "CentralTransitive",
"requested": "[5.0.0, )",
- "resolved": "8.0.0",
- "contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==",
+ "resolved": "9.0.0",
+ "contentHash": "owmu2Cr3IQ8yQiBleBHlGk8dSQ12oaF2e7TpzwJKEl4m84kkZJjEY1n33L67Y3zM5jPOjmmbdHjbfiL0RqcMRQ==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
@@ -185,9 +186,9 @@
},
"Microsoft.Extensions.ObjectPool": {
"type": "Direct",
- "requested": "[8.0.11, )",
- "resolved": "8.0.11",
- "contentHash": "6ApKcHNJigXBfZa6XlDQ8feJpq7SG1ogZXg6M4FiNzgd6irs3LUAzo0Pfn4F2ZI9liGnH1XIBR/OtSbZmJAV5w=="
+ "requested": "[9.0.0, )",
+ "resolved": "9.0.0",
+ "contentHash": "UbsU/gYe4nv1DeqMXIVzDfNNek7Sk2kKuAOXL/Y+sLcAR0HwFUqzg1EPiU88jeHNe0g81aPvvHbvHarQr3r9IA=="
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
@@ -201,9 +202,9 @@
},
"Open.ChannelExtensions": {
"type": "Direct",
- "requested": "[8.6.0, )",
- "resolved": "8.6.0",
- "contentHash": "g5axz417bA6FXifJaBlB0l62gV7dYmknXx0n8lT/LSA3+7isaGMsOjJp5J+H/yXDRe4r+KZrE+bzQcs4Ets2kA=="
+ "requested": "[9.0.0, )",
+ "resolved": "9.0.0",
+ "contentHash": "DP+l5S6G46wcuY4I4kNXE+RDOmJr0DKuMienOdt0mMBN9z7vmLSC8YQbqCyb9i9LNjXj1tgCx5LyitJiRr/v7g=="
},
"Polly": {
"type": "Direct",
@@ -240,9 +241,9 @@
},
"System.Threading.Channels": {
"type": "Direct",
- "requested": "[8.0.0, )",
- "resolved": "8.0.0",
- "contentHash": "CMaFr7v+57RW7uZfZkPExsPB6ljwzhjACWW1gfU35Y56rk72B/Wu+sTqxVmGSk4SFUlPc3cjeKND0zktziyjBA=="
+ "requested": "[9.0.0, )",
+ "resolved": "9.0.0",
+ "contentHash": "hzACdIf1C+4Dqos5ijV404b94+LqfIC8nfS3mNpCDFWowb1N3PNfJPopneq32ahWlDeyaPZJqjBk76YFR69Rpg=="
},
"ILRepack": {
"type": "Transitive",
diff --git a/src/Speckle.Sdk/Credentials/AccountManager.cs b/src/Speckle.Sdk/Credentials/AccountManager.cs
index e8784ee1..2009ebbf 100644
--- a/src/Speckle.Sdk/Credentials/AccountManager.cs
+++ b/src/Speckle.Sdk/Credentials/AccountManager.cs
@@ -21,11 +21,13 @@ using Stream = System.IO.Stream;
namespace Speckle.Sdk.Credentials;
+public partial interface IAccountManager : IDisposable;
+
///
/// Manage accounts locally for desktop applications.
///
[GenerateAutoInterface]
-public class AccountManager(
+public sealed class AccountManager(
ISpeckleApplication application,
ILogger logger,
ISpeckleHttp speckleHttp,
@@ -40,6 +42,13 @@ public class AccountManager(
"AccountAddFlow"
);
+ [AutoInterfaceIgnore]
+ public void Dispose()
+ {
+ _accountStorage.Dispose();
+ _accountAddLockStorage.Dispose();
+ }
+
///
/// Gets the basic information about a server.
///
@@ -321,7 +330,7 @@ public class AccountManager(
{
static bool IsInvalid(Account ac) => ac.userInfo == null || ac.serverInfo == null;
- var sqlAccounts = _accountStorage.GetAllObjects().Select(x => JsonConvert.DeserializeObject(x));
+ var sqlAccounts = _accountStorage.GetAllObjects().Select(x => JsonConvert.DeserializeObject(x.Json));
var localAccounts = GetLocalAccounts();
foreach (var acc in sqlAccounts)
@@ -642,7 +651,7 @@ public class AccountManager(
}
// this uses the SQLite transport to store locks
- var lockIds = _accountAddLockStorage.GetAllObjects().OrderByDescending(d => d).ToList();
+ var lockIds = _accountAddLockStorage.GetAllObjects().Select(x => x.Id).OrderByDescending(d => d).ToList();
var now = DateTime.Now;
foreach (var l in lockIds)
{
@@ -674,7 +683,7 @@ public class AccountManager(
{
s_isAddingAccount = false;
// make sure all old locks are removed
- foreach (var id in _accountAddLockStorage.GetAllObjects())
+ foreach (var (id, _) in _accountAddLockStorage.GetAllObjects())
{
_accountAddLockStorage.DeleteObject(id);
}
diff --git a/src/Speckle.Sdk/SQLite/CacheDbCommandPool.cs b/src/Speckle.Sdk/SQLite/CacheDbCommandPool.cs
new file mode 100644
index 00000000..500d2230
--- /dev/null
+++ b/src/Speckle.Sdk/SQLite/CacheDbCommandPool.cs
@@ -0,0 +1,96 @@
+using System.Collections.Concurrent;
+using Microsoft.Data.Sqlite;
+
+namespace Speckle.Sdk.SQLite;
+
+//inspired by https://github.com/neosmart/SqliteCache/blob/master/SqliteCache/DbCommandPool.cs
+public sealed class CacheDbCommandPool : IDisposable
+{
+ private readonly ConcurrentBag[] _commands = new ConcurrentBag[CacheDbCommands.Count];
+ private readonly ConcurrentBag _connections = new();
+ private readonly string _connectionString;
+
+ public CacheDbCommandPool(string connectionString, int concurrency)
+ {
+ _connectionString = connectionString;
+ for (int i = 0; i < _commands.Length; ++i)
+ {
+ _commands[i] = new ConcurrentBag();
+ }
+ for (int i = 0; i < concurrency; ++i)
+ {
+ var connection = new SqliteConnection(_connectionString);
+ connection.Open();
+ _connections.Add(connection);
+ }
+ }
+
+ public void Use(CacheOperation type, Action handler) =>
+ Use(
+ type,
+ cmd =>
+ {
+ handler(cmd);
+ return true;
+ }
+ );
+
+ private T Use(Func handler)
+ {
+ if (!_connections.TryTake(out var db))
+ {
+ db = new SqliteConnection(_connectionString);
+ db.Open();
+ }
+
+ try
+ {
+ return handler(db);
+ }
+ finally
+ {
+ _connections.Add(db);
+ }
+ }
+
+ public T Use(CacheOperation type, Func handler) =>
+ Use(conn =>
+ {
+ var pool = _commands[(int)type];
+ if (!pool.TryTake(out var command))
+ {
+#pragma warning disable CA2100
+ command = new SqliteCommand(CacheDbCommands.Commands[(int)type], conn);
+#pragma warning restore CA2100
+ }
+
+ try
+ {
+ command.Connection = conn;
+ return handler(command);
+ }
+ finally
+ {
+ command.Connection = null;
+ command.Parameters.Clear();
+ pool.Add(command);
+ }
+ });
+
+ public void Dispose()
+ {
+ foreach (var pool in _commands)
+ {
+ while (pool.TryTake(out var cmd))
+ {
+ cmd.Dispose();
+ }
+ }
+
+ foreach (var conn in _connections)
+ {
+ conn.Close();
+ conn.Dispose();
+ }
+ }
+}
diff --git a/src/Speckle.Sdk/SQLite/CacheDbCommands.cs b/src/Speckle.Sdk/SQLite/CacheDbCommands.cs
new file mode 100644
index 00000000..33a28771
--- /dev/null
+++ b/src/Speckle.Sdk/SQLite/CacheDbCommands.cs
@@ -0,0 +1,35 @@
+namespace Speckle.Sdk.SQLite;
+
+public enum CacheOperation
+{
+ InsertOrIgnore,
+ InsertOrReplace,
+ Has,
+ Get,
+ Delete,
+ GetAll,
+ BulkInsertOrIgnore,
+}
+
+public static class CacheDbCommands
+{
+ public static readonly string[] Commands;
+ public static readonly int Count = Enum.GetValues(typeof(CacheOperation)).Length;
+
+#pragma warning disable CA1810
+ static CacheDbCommands()
+#pragma warning restore CA1810
+ {
+ Commands = new string[Count];
+
+ Commands[(int)CacheOperation.InsertOrIgnore] =
+ "INSERT OR IGNORE INTO objects(hash, content) VALUES(@hash, @content)";
+ Commands[(int)CacheOperation.InsertOrReplace] = "REPLACE INTO objects(hash, content) VALUES(@hash, @content)";
+ Commands[(int)CacheOperation.Has] = "SELECT 1 FROM objects WHERE hash = @hash LIMIT 1";
+ Commands[(int)CacheOperation.Get] = "SELECT content FROM objects WHERE hash = @hash LIMIT 1";
+ Commands[(int)CacheOperation.Delete] = "DELETE FROM objects WHERE hash = @hash";
+ Commands[(int)CacheOperation.GetAll] = "SELECT hash, content FROM objects";
+
+ Commands[(int)CacheOperation.BulkInsertOrIgnore] = "INSERT OR IGNORE INTO objects (hash, content) VALUES ";
+ }
+}
diff --git a/src/Speckle.Sdk/SQLite/SQLiteJsonCacheManager.cs b/src/Speckle.Sdk/SQLite/SQLiteJsonCacheManager.cs
index 8ffffddd..6a2983f4 100644
--- a/src/Speckle.Sdk/SQLite/SQLiteJsonCacheManager.cs
+++ b/src/Speckle.Sdk/SQLite/SQLiteJsonCacheManager.cs
@@ -1,19 +1,28 @@
+using System.Text;
using Microsoft.Data.Sqlite;
using Speckle.InterfaceGenerator;
+using Speckle.Sdk.Dependencies;
namespace Speckle.Sdk.SQLite;
+public partial interface ISqLiteJsonCacheManager : IDisposable;
+
[GenerateAutoInterface]
-public class SqLiteJsonCacheManager : ISqLiteJsonCacheManager
+public sealed class SqLiteJsonCacheManager : ISqLiteJsonCacheManager
{
private readonly string _connectionString;
+ private readonly CacheDbCommandPool _pool;
- public SqLiteJsonCacheManager(string rootPath)
+ public SqLiteJsonCacheManager(string connectionString, int concurrency)
{
- _connectionString = $"Data Source={rootPath};";
+ _connectionString = connectionString;
Initialize();
+ _pool = new CacheDbCommandPool(_connectionString, concurrency);
}
+ [AutoInterfaceIgnore]
+ public void Dispose() => _pool.Dispose();
+
private void Initialize()
{
// NOTE: used for creating partioned object tables.
@@ -57,100 +66,107 @@ public class SqLiteJsonCacheManager : ISqLiteJsonCacheManager
using SqliteCommand cmd4 = new("PRAGMA page_size = 32768;", c);
cmd4.ExecuteNonQuery();
+ c.Close();
}
- public IEnumerable GetAllObjects()
- {
- using var c = new SqliteConnection(_connectionString);
- c.Open();
- using var command = new SqliteCommand("SELECT * FROM objects", c);
+ public IReadOnlyCollection<(string Id, string Json)> GetAllObjects() =>
+ _pool.Use(
+ CacheOperation.GetAll,
+ command =>
+ {
+ var list = new HashSet<(string, string)>();
+ using var reader = command.ExecuteReader();
+ while (reader.Read())
+ {
+ list.Add((reader.GetString(0), reader.GetString(1)));
+ }
+ return list;
+ }
+ );
- using var reader = command.ExecuteReader();
- while (reader.Read())
- {
- yield return reader.GetString(1);
- }
- }
+ public void DeleteObject(string id) =>
+ _pool.Use(
+ CacheOperation.Delete,
+ command =>
+ {
+ command.Parameters.AddWithValue("@hash", id);
+ command.ExecuteNonQuery();
+ }
+ );
- public void DeleteObject(string id)
- {
- using var c = new SqliteConnection(_connectionString);
- c.Open();
- using var command = new SqliteCommand("DELETE FROM objects WHERE hash = @hash", c);
- command.Parameters.AddWithValue("@hash", id);
- command.ExecuteNonQuery();
- }
-
- public string? GetObject(string id)
- {
- using var c = new SqliteConnection(_connectionString);
- c.Open();
- using var command = new SqliteCommand("SELECT * FROM objects WHERE hash = @hash LIMIT 1 ", c);
- command.Parameters.AddWithValue("@hash", id);
- using var reader = command.ExecuteReader();
- if (reader.Read())
- {
- return reader.GetString(1);
- }
-
- return null; // pass on the duty of null checks to consumers
- }
+ public string? GetObject(string id) =>
+ _pool.Use(
+ CacheOperation.Get,
+ command =>
+ {
+ command.Parameters.AddWithValue("@hash", id);
+ return (string?)command.ExecuteScalar();
+ }
+ );
//This does an insert or ignores if already exists
- public void SaveObject(string id, string json)
- {
- using var c = new SqliteConnection(_connectionString);
- c.Open();
- const string COMMAND_TEXT = "INSERT OR IGNORE INTO objects(hash, content) VALUES(@hash, @content)";
-
- using var command = new SqliteCommand(COMMAND_TEXT, c);
- command.Parameters.AddWithValue("@hash", id);
- command.Parameters.AddWithValue("@content", json);
- command.ExecuteNonQuery();
- }
+ public void SaveObject(string id, string json) =>
+ _pool.Use(
+ CacheOperation.InsertOrIgnore,
+ command =>
+ {
+ command.Parameters.AddWithValue("@hash", id);
+ command.Parameters.AddWithValue("@content", json);
+ command.ExecuteNonQuery();
+ }
+ );
//This does an insert or replaces if already exists
- public void UpdateObject(string id, string json)
- {
- using var c = new SqliteConnection(_connectionString);
- c.Open();
- const string COMMAND_TEXT = "REPLACE INTO objects(hash, content) VALUES(@hash, @content)";
- using var command = new SqliteCommand(COMMAND_TEXT, c);
- command.Parameters.AddWithValue("@hash", id);
- command.Parameters.AddWithValue("@content", json);
- command.ExecuteNonQuery();
- }
+ public void UpdateObject(string id, string json) =>
+ _pool.Use(
+ CacheOperation.InsertOrReplace,
+ command =>
+ {
+ command.Parameters.AddWithValue("@hash", id);
+ command.Parameters.AddWithValue("@content", json);
+ command.ExecuteNonQuery();
+ }
+ );
- public void SaveObjects(IEnumerable<(string id, string json)> items)
- {
- using var c = new SqliteConnection(_connectionString);
- c.Open();
- using var t = c.BeginTransaction();
- const string COMMAND_TEXT = "INSERT OR IGNORE INTO objects(hash, content) VALUES(@hash, @content)";
+ public void SaveObjects(IEnumerable<(string id, string json)> items) =>
+ _pool.Use(
+ CacheOperation.BulkInsertOrIgnore,
+ cmd =>
+ {
+ CreateBulkInsert(cmd, items);
+ return cmd.ExecuteNonQuery();
+ }
+ );
- using var command = new SqliteCommand(COMMAND_TEXT, c);
- command.Transaction = t;
- var idParam = command.Parameters.Add("@hash", SqliteType.Text);
- var jsonParam = command.Parameters.Add("@content", SqliteType.Text);
+ private void CreateBulkInsert(SqliteCommand cmd, IEnumerable<(string id, string json)> items)
+ {
+ StringBuilder sb = Pools.StringBuilders.Get();
+ sb.AppendLine(CacheDbCommands.Commands[(int)CacheOperation.BulkInsertOrIgnore]);
+ int i = 0;
foreach (var (id, json) in items)
{
- idParam.Value = id;
- jsonParam.Value = json;
- command.ExecuteNonQuery();
+ sb.Append($"(@key{i}, @value{i}),");
+ cmd.Parameters.AddWithValue($"@key{i}", id);
+ cmd.Parameters.AddWithValue($"@value{i}", json);
+ i++;
}
- t.Commit();
+ sb.Remove(sb.Length - 1, 1);
+ sb.Append(';');
+#pragma warning disable CA2100
+ cmd.CommandText = sb.ToString();
+#pragma warning restore CA2100
+ Pools.StringBuilders.Return(sb);
}
- public bool HasObject(string objectId)
- {
- using var c = new SqliteConnection(_connectionString);
- c.Open();
- const string COMMAND_TEXT = "SELECT 1 FROM objects WHERE hash = @hash LIMIT 1 ";
- using var command = new SqliteCommand(COMMAND_TEXT, c);
- command.Parameters.AddWithValue("@hash", objectId);
-
- using var reader = command.ExecuteReader();
- bool rowFound = reader.Read();
- return rowFound;
- }
+ public bool HasObject(string objectId) =>
+ _pool.Use(
+ CacheOperation.Has,
+ command =>
+ {
+ command.Parameters.AddWithValue("@hash", objectId);
+ using var reader = command.ExecuteReader();
+ bool rowFound = reader.Read();
+ return rowFound;
+ }
+ );
}
diff --git a/src/Speckle.Sdk/SQLite/SqLiteJsonCacheManagerFactory.cs b/src/Speckle.Sdk/SQLite/SqLiteJsonCacheManagerFactory.cs
index c73a28a7..94eb9339 100644
--- a/src/Speckle.Sdk/SQLite/SqLiteJsonCacheManagerFactory.cs
+++ b/src/Speckle.Sdk/SQLite/SqLiteJsonCacheManagerFactory.cs
@@ -7,10 +7,14 @@ namespace Speckle.Sdk.SQLite;
[GenerateAutoInterface]
public class SqLiteJsonCacheManagerFactory : ISqLiteJsonCacheManagerFactory
{
- private ISqLiteJsonCacheManager Create(string path) => new SqLiteJsonCacheManager(path);
+ public const int INITIAL_CONCURRENCY = 4;
+
+ private ISqLiteJsonCacheManager Create(string path, int concurrency) =>
+ new SqLiteJsonCacheManager($"Data Source={path};", concurrency);
public ISqLiteJsonCacheManager CreateForUser(string scope) =>
- Create(Path.Combine(SpecklePathProvider.UserApplicationDataPath(), "Speckle", $"{scope}.db"));
+ Create(Path.Combine(SpecklePathProvider.UserApplicationDataPath(), "Speckle", $"{scope}.db"), 1);
- public ISqLiteJsonCacheManager CreateFromStream(string streamId) => Create(SqlitePaths.GetDBPath(streamId));
+ public ISqLiteJsonCacheManager CreateFromStream(string streamId) =>
+ Create(SqlitePaths.GetDBPath(streamId), INITIAL_CONCURRENCY);
}
diff --git a/src/Speckle.Sdk/Serialisation/V2/DummySendServerObjectManager.cs b/src/Speckle.Sdk/Serialisation/V2/DummySendServerObjectManager.cs
index 827ed34a..26e50477 100644
--- a/src/Speckle.Sdk/Serialisation/V2/DummySendServerObjectManager.cs
+++ b/src/Speckle.Sdk/Serialisation/V2/DummySendServerObjectManager.cs
@@ -5,9 +5,11 @@ using Speckle.Sdk.Transports;
namespace Speckle.Sdk.Serialisation.V2;
-public class DummySqLiteJsonCacheManager : ISqLiteJsonCacheManager
+public sealed class DummySqLiteJsonCacheManager : ISqLiteJsonCacheManager
{
- public IEnumerable GetAllObjects() => throw new NotImplementedException();
+ public void Dispose() { }
+
+ public IReadOnlyCollection<(string, string)> GetAllObjects() => throw new NotImplementedException();
public void DeleteObject(string id) => throw new NotImplementedException();
diff --git a/src/Speckle.Sdk/Serialisation/V2/Receive/DeserializeProcess.cs b/src/Speckle.Sdk/Serialisation/V2/Receive/DeserializeProcess.cs
index 87aa22a3..5be948fd 100644
--- a/src/Speckle.Sdk/Serialisation/V2/Receive/DeserializeProcess.cs
+++ b/src/Speckle.Sdk/Serialisation/V2/Receive/DeserializeProcess.cs
@@ -13,6 +13,8 @@ public record DeserializeProcessOptions(
bool SkipInvalidConverts = false
);
+public partial interface IDeserializeProcess : IDisposable;
+
[GenerateAutoInterface]
public sealed class DeserializeProcess(
IProgress? progress,
@@ -30,6 +32,9 @@ public sealed class DeserializeProcess(
public IReadOnlyDictionary BaseCache => _baseCache;
public long Total { get; private set; }
+ [AutoInterfaceIgnore]
+ public void Dispose() => objectLoader.Dispose();
+
public async Task Deserialize(string rootId, CancellationToken cancellationToken)
{
var (rootJson, childrenIds) = await objectLoader
diff --git a/src/Speckle.Sdk/Serialisation/V2/Receive/ObjectLoader.cs b/src/Speckle.Sdk/Serialisation/V2/Receive/ObjectLoader.cs
index 7201c4d0..345190c5 100644
--- a/src/Speckle.Sdk/Serialisation/V2/Receive/ObjectLoader.cs
+++ b/src/Speckle.Sdk/Serialisation/V2/Receive/ObjectLoader.cs
@@ -9,6 +9,8 @@ using Speckle.Sdk.Transports;
namespace Speckle.Sdk.Serialisation.V2.Receive;
+public partial interface IObjectLoader : IDisposable;
+
[GenerateAutoInterface]
public sealed class ObjectLoader(
ISqLiteJsonCacheManager sqLiteJsonCacheManager,
@@ -21,6 +23,9 @@ public sealed class ObjectLoader(
private long _cached;
private DeserializeProcessOptions _options = new(false);
+ [AutoInterfaceIgnore]
+ public void Dispose() => sqLiteJsonCacheManager.Dispose();
+
public async Task<(string, IReadOnlyCollection)> GetAndCache(
string rootId,
DeserializeProcessOptions options,
diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/BaseItem.cs b/src/Speckle.Sdk/Serialisation/V2/Send/BaseItem.cs
new file mode 100644
index 00000000..38f2922f
--- /dev/null
+++ b/src/Speckle.Sdk/Serialisation/V2/Send/BaseItem.cs
@@ -0,0 +1,19 @@
+using System.Text;
+
+namespace Speckle.Sdk.Serialisation.V2.Send;
+
+public readonly record struct BaseItem(Id Id, Json Json, bool NeedsStorage, Dictionary? Closures) : IHasSize
+{
+ public int Size { get; } = Encoding.UTF8.GetByteCount(Json.Value);
+
+ public bool Equals(BaseItem? other)
+ {
+ if (other is null)
+ {
+ return false;
+ }
+ return string.Equals(Id.Value, other.Value.Id.Value, StringComparison.OrdinalIgnoreCase);
+ }
+
+ public override int GetHashCode() => Id.GetHashCode();
+}
diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/PriorityScheduler.cs b/src/Speckle.Sdk/Serialisation/V2/Send/PriorityScheduler.cs
index c38a3df3..46eacf4a 100644
--- a/src/Speckle.Sdk/Serialisation/V2/Send/PriorityScheduler.cs
+++ b/src/Speckle.Sdk/Serialisation/V2/Send/PriorityScheduler.cs
@@ -1,4 +1,4 @@
-using System.Collections.Concurrent;
+using System.Collections.Concurrent;
namespace Speckle.Sdk.Serialisation.V2.Send;
diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs b/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs
index 8f08173a..242fc5a0 100644
--- a/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs
+++ b/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs
@@ -1,5 +1,4 @@
using System.Collections.Concurrent;
-using System.Text;
using Speckle.InterfaceGenerator;
using Speckle.Sdk.Common;
using Speckle.Sdk.Dependencies;
@@ -23,22 +22,6 @@ public readonly record struct SerializeProcessResults(
IReadOnlyDictionary ConvertedReferences
);
-public readonly record struct BaseItem(Id Id, Json Json, bool NeedsStorage, Closures? Closures) : IHasSize
-{
- public int Size { get; } = Encoding.UTF8.GetByteCount(Json.Value);
-
- public bool Equals(BaseItem? other)
- {
- if (other is null)
- {
- return false;
- }
- return string.Equals(Id.Value, other.Value.Id.Value, StringComparison.OrdinalIgnoreCase);
- }
-
- public override int GetHashCode() => Id.GetHashCode();
-}
-
public partial interface ISerializeProcess : IDisposable;
[GenerateAutoInterface]
@@ -77,6 +60,7 @@ public sealed class SerializeProcess(
{
_highest.Dispose();
_belowNormal.Dispose();
+ sqLiteJsonCacheManager.Dispose();
}
public async Task Serialize(Base root, CancellationToken cancellationToken)
@@ -223,7 +207,7 @@ public sealed class SerializeProcess(
return new BaseItem(id, json, true, closures);
}
- public override async Task> SendToServer(Batch batch, CancellationToken cancellationToken)
+ public override async Task SendToServer(Batch batch, CancellationToken cancellationToken)
{
if (!_options.SkipServer && batch.Items.Count != 0)
{
@@ -238,9 +222,7 @@ public sealed class SerializeProcess(
Interlocked.Exchange(ref _uploaded, _uploaded + batch.Items.Count);
}
progress?.Report(new(ProgressEvent.UploadedObjects, _uploaded, null));
- return objectBatch;
}
- return batch.Items;
}
public override void SaveToCache(List batch)
diff --git a/src/Speckle.Sdk/Serialisation/V2/SerializeProcessFactory.cs b/src/Speckle.Sdk/Serialisation/V2/SerializeProcessFactory.cs
index 36cc08af..68615ec5 100644
--- a/src/Speckle.Sdk/Serialisation/V2/SerializeProcessFactory.cs
+++ b/src/Speckle.Sdk/Serialisation/V2/SerializeProcessFactory.cs
@@ -1,5 +1,3 @@
-using Speckle.Sdk.Helpers;
-using Speckle.Sdk.Logging;
using Speckle.Sdk.Serialisation.V2.Receive;
using Speckle.Sdk.Serialisation.V2.Send;
using Speckle.Sdk.SQLite;
@@ -23,20 +21,14 @@ public interface ISerializeProcessFactory
IProgress? progress,
DeserializeProcessOptions? options = null
);
-
- public ISerializeProcess CreateSerializeProcess(
- SerializeProcessOptions? options = null,
- IProgress? progress = null
- );
}
public class SerializeProcessFactory(
- ISpeckleHttp speckleHttp,
- ISdkActivityFactory activityFactory,
IBaseChildFinder baseChildFinder,
IObjectSerializerFactory objectSerializerFactory,
IObjectDeserializerFactory objectDeserializerFactory,
- ISqLiteJsonCacheManagerFactory sqLiteJsonCacheManagerFactory
+ ISqLiteJsonCacheManagerFactory sqLiteJsonCacheManagerFactory,
+ IServerObjectManagerFactory serverObjectManagerFactory
) : ISerializeProcessFactory
{
public ISerializeProcess CreateSerializeProcess(
@@ -48,24 +40,7 @@ public class SerializeProcessFactory(
)
{
var sqLiteJsonCacheManager = sqLiteJsonCacheManagerFactory.CreateFromStream(streamId);
- var serverObjectManager = new ServerObjectManager(speckleHttp, activityFactory, url, streamId, authorizationToken);
- return new SerializeProcess(
- progress,
- sqLiteJsonCacheManager,
- serverObjectManager,
- baseChildFinder,
- objectSerializerFactory,
- options
- );
- }
-
- public ISerializeProcess CreateSerializeProcess(
- SerializeProcessOptions? options = null,
- IProgress? progress = null
- )
- {
- var sqLiteJsonCacheManager = new DummySqLiteJsonCacheManager();
- var serverObjectManager = new DummySendServerObjectManager();
+ var serverObjectManager = serverObjectManagerFactory.Create(url, streamId, authorizationToken);
return new SerializeProcess(
progress,
sqLiteJsonCacheManager,
@@ -85,9 +60,12 @@ public class SerializeProcessFactory(
)
{
var sqLiteJsonCacheManager = sqLiteJsonCacheManagerFactory.CreateFromStream(streamId);
- var serverObjectManager = new ServerObjectManager(speckleHttp, activityFactory, url, streamId, authorizationToken);
+ var serverObjectManager = serverObjectManagerFactory.Create(url, streamId, authorizationToken);
+#pragma warning disable CA2000
+ //owned by process, refactor later
var objectLoader = new ObjectLoader(sqLiteJsonCacheManager, serverObjectManager, progress);
+#pragma warning restore CA2000
return new DeserializeProcess(progress, objectLoader, objectDeserializerFactory, options);
}
}
diff --git a/src/Speckle.Sdk/Serialisation/V2/ServerObjectManagerFactory.cs b/src/Speckle.Sdk/Serialisation/V2/ServerObjectManagerFactory.cs
new file mode 100644
index 00000000..071a62b5
--- /dev/null
+++ b/src/Speckle.Sdk/Serialisation/V2/ServerObjectManagerFactory.cs
@@ -0,0 +1,13 @@
+using Speckle.InterfaceGenerator;
+using Speckle.Sdk.Helpers;
+using Speckle.Sdk.Logging;
+
+namespace Speckle.Sdk.Serialisation.V2;
+
+[GenerateAutoInterface]
+public class ServerObjectManagerFactory(ISpeckleHttp speckleHttp, ISdkActivityFactory activityFactory)
+ : IServerObjectManagerFactory
+{
+ public IServerObjectManager Create(Uri url, string streamId, string? authorizationToken, int timeoutSeconds = 120) =>
+ new ServerObjectManager(speckleHttp, activityFactory, url, streamId, authorizationToken, timeoutSeconds);
+}
diff --git a/tests/Speckle.Sdk.Serialization.Testing/Program.cs b/tests/Speckle.Sdk.Serialization.Testing/Program.cs
index b5a324f4..77e81387 100644
--- a/tests/Speckle.Sdk.Serialization.Testing/Program.cs
+++ b/tests/Speckle.Sdk.Serialization.Testing/Program.cs
@@ -3,9 +3,7 @@ using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Sdk;
using Speckle.Sdk.Credentials;
-using Speckle.Sdk.Helpers;
using Speckle.Sdk.Host;
-using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
using Speckle.Sdk.Serialisation.V2;
using Speckle.Sdk.Serialisation.V2.Receive;
@@ -43,12 +41,11 @@ var token = serviceProvider.GetRequiredService().GetDefaultAcco
var progress = new Progress(true);
var factory = new SerializeProcessFactory(
- serviceProvider.GetRequiredService(),
- serviceProvider.GetRequiredService(),
new BaseChildFinder(new BasePropertyGatherer()),
new ObjectSerializerFactory(new BasePropertyGatherer()),
new ObjectDeserializerFactory(),
- serviceProvider.GetRequiredService()
+ serviceProvider.GetRequiredService(),
+ serviceProvider.GetRequiredService()
);
var process = factory.CreateDeserializeProcess(new Uri(url), streamId, token, progress, new(skipCacheReceive));
var @base = await process.Deserialize(rootId, default).ConfigureAwait(false);
diff --git a/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs b/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs
index 94a396e2..0448cb4a 100644
--- a/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs
+++ b/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs
@@ -529,7 +529,9 @@ public class DummyServerObjectManager : IServerObjectManager
public class DummySendCacheManager(Dictionary objects) : ISqLiteJsonCacheManager
{
- public IEnumerable GetAllObjects() => throw new NotImplementedException();
+ public void Dispose() { }
+
+ public IReadOnlyCollection<(string, string)> GetAllObjects() => throw new NotImplementedException();
public void DeleteObject(string id) => throw new NotImplementedException();
diff --git a/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteReceiveManager.cs b/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteReceiveManager.cs
index 42961d6b..797eacd9 100644
--- a/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteReceiveManager.cs
+++ b/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteReceiveManager.cs
@@ -4,7 +4,9 @@ namespace Speckle.Sdk.Serialization.Tests;
public class DummySqLiteReceiveManager(Dictionary savedObjects) : ISqLiteJsonCacheManager
{
- public IEnumerable GetAllObjects() => throw new NotImplementedException();
+ public void Dispose() { }
+
+ public IReadOnlyCollection<(string, string)> GetAllObjects() => throw new NotImplementedException();
public void DeleteObject(string id) => throw new NotImplementedException();
diff --git a/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteSendManager.cs b/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteSendManager.cs
index ce1dae8c..0b93af62 100644
--- a/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteSendManager.cs
+++ b/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteSendManager.cs
@@ -14,7 +14,9 @@ public class DummySqLiteSendManager : ISqLiteJsonCacheManager
public bool HasObject(string objectId) => throw new NotImplementedException();
- public IEnumerable GetAllObjects() => throw new NotImplementedException();
+ public IReadOnlyCollection<(string, string)> GetAllObjects() => throw new NotImplementedException();
public void DeleteObject(string id) => throw new NotImplementedException();
+
+ public void Dispose() { }
}
diff --git a/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs b/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs
index aaf98d30..076730a2 100644
--- a/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs
+++ b/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs
@@ -33,6 +33,8 @@ public class SerializationTests
}
public string? LoadId(string id) => null;
+
+ public void Dispose() { }
}
private readonly Assembly _assembly = Assembly.GetExecutingAssembly();
@@ -103,6 +105,8 @@ public class SerializationTests
}
public string? LoadId(string id) => idToObject.GetValueOrDefault(id);
+
+ public void Dispose() { }
}
[Test]
@@ -154,7 +158,7 @@ public class SerializationTests
var fullName = _assembly.GetManifestResourceNames().Single(x => x.EndsWith(fileName));
var json = await ReadJson(fullName);
var closures = ReadAsObjects(json);
- var process = new DeserializeProcess(null, new TestObjectLoader(closures), new ObjectDeserializerFactory());
+ using var process = new DeserializeProcess(null, new TestObjectLoader(closures), new ObjectDeserializerFactory());
await process.Deserialize("3416d3fe01c9196115514c4a2f41617b", default);
foreach (var (id, objJson) in closures)
{
@@ -251,7 +255,7 @@ public class SerializationTests
new DummyReceiveServerObjectManager(closure),
null
);
- var process = new DeserializeProcess(null, o, new ObjectDeserializerFactory(), new(true));
+ using var process = new DeserializeProcess(null, o, new ObjectDeserializerFactory(), new(true));
var root = await process.Deserialize(rootId, default);
process.BaseCache.Count.ShouldBe(oldCount);
process.Total.ShouldBe(oldCount);
diff --git a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs
index 6ffc63ea..59fc7b33 100644
--- a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs
+++ b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs
@@ -57,7 +57,7 @@ public class GeneralDeserializer : IDisposable
null
);
var o = new ObjectLoader(sqlite, serverObjects, null);
- var process = new DeserializeProcess(null, o, new ObjectDeserializerFactory(), new(skipCache));
+ using var process = new DeserializeProcess(null, o, new ObjectDeserializerFactory(), new(skipCache));
return await process.Deserialize(rootId, default).ConfigureAwait(false);
}
diff --git a/tests/Speckle.Sdk.Tests.Unit/SQLite/SQLiteJsonCacheManagerTests.cs b/tests/Speckle.Sdk.Tests.Unit/SQLite/SQLiteJsonCacheManagerTests.cs
new file mode 100644
index 00000000..961729a5
--- /dev/null
+++ b/tests/Speckle.Sdk.Tests.Unit/SQLite/SQLiteJsonCacheManagerTests.cs
@@ -0,0 +1,91 @@
+using Microsoft.Data.Sqlite;
+using NUnit.Framework;
+using Shouldly;
+using Speckle.Sdk.Common;
+using Speckle.Sdk.SQLite;
+
+namespace Speckle.Sdk.Tests.Unit.SQLite;
+
+[TestFixture]
+public class SQLiteJsonCacheManagerTests
+{
+ private readonly string _basePath = $"{Guid.NewGuid()}.db";
+ private string? _connectionString;
+
+ [SetUp]
+ public void Setup() => _connectionString = $"Data Source={_basePath};";
+
+ [TearDown]
+ public void TearDown()
+ {
+ if (File.Exists(_basePath))
+ {
+ SqliteConnection.ClearAllPools();
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ File.Delete(_basePath);
+ }
+ }
+
+ [Test]
+ public void TestGetAll()
+ {
+ var data = new List<(string id, string json)>() { ("id1", "1"), ("id2", "2") };
+ using var manager = new SqLiteJsonCacheManager(_connectionString.NotNull(), 2);
+ manager.SaveObjects(data);
+ var items = manager.GetAllObjects();
+ items.Count.ShouldBe(data.Count);
+ var i = items.ToDictionary();
+ foreach (var (id, json) in data)
+ {
+ i.TryGetValue(id, out var j).ShouldBeTrue();
+ j.ShouldBe(json);
+ }
+ }
+
+ [Test]
+ public void TestGet()
+ {
+ var data = new List<(string id, string json)>() { ("id1", "1"), ("id2", "2") };
+ using var manager = new SqLiteJsonCacheManager(_connectionString.NotNull(), 2);
+ foreach (var d in data)
+ {
+ manager.SaveObject(d.id, d.json);
+ }
+ foreach (var d in data)
+ {
+ manager.SaveObject(d.id, d.json);
+ }
+ var items = manager.GetAllObjects();
+ items.Count.ShouldBe(data.Count);
+
+ var id1 = data[0].id;
+ var json1 = manager.GetObject(id1);
+ json1.ShouldBe(data[0].json);
+ manager.HasObject(id1).ShouldBeTrue();
+
+ manager.UpdateObject(id1, "3");
+ json1 = manager.GetObject(id1);
+ json1.ShouldBe("3");
+ manager.HasObject(id1).ShouldBeTrue();
+
+ manager.DeleteObject(id1);
+ json1 = manager.GetObject(id1);
+ json1.ShouldBeNull();
+ manager.HasObject(id1).ShouldBeFalse();
+
+ manager.UpdateObject(id1, "3");
+ json1 = manager.GetObject(id1);
+ json1.ShouldBe("3");
+ manager.HasObject(id1).ShouldBeTrue();
+
+ var id2 = data[1].id;
+ var json2 = manager.GetObject(id2);
+ json2.ShouldBe(data[1].json);
+ manager.HasObject(id2).ShouldBeTrue();
+ manager.DeleteObject(id2);
+ json2 = manager.GetObject(id2);
+ json2.ShouldBeNull();
+ manager.HasObject(id2).ShouldBeFalse();
+ }
+}
diff --git a/tests/Speckle.Sdk.Tests.Unit/Serialisation/BatchTests.cs b/tests/Speckle.Sdk.Tests.Unit/Serialisation/BatchTests.cs
index e994288d..a16b1937 100644
--- a/tests/Speckle.Sdk.Tests.Unit/Serialisation/BatchTests.cs
+++ b/tests/Speckle.Sdk.Tests.Unit/Serialisation/BatchTests.cs
@@ -1,5 +1,6 @@
-using NUnit.Framework;
+using NUnit.Framework;
using Shouldly;
+using Speckle.Sdk.Dependencies;
using Speckle.Sdk.Serialisation.V2.Send;
namespace Speckle.Sdk.Tests.Unit.Serialisation;
@@ -20,7 +21,7 @@ public class BatchTests
[Test]
public void TestBatchSize_Calc()
{
- var batch = new Batch(4);
+ using var batch = new Batch();
batch.Add(new BatchItem(1));
batch.Size.ShouldBe(1);
batch.Add(new BatchItem(2));
@@ -30,12 +31,12 @@ public class BatchTests
[Test]
public void TestBatchSize_Trim()
{
- var batch = new Batch(4);
+ using var batch = new Batch();
batch.Add(new BatchItem(1));
batch.Add(new BatchItem(2));
batch.Size.ShouldBe(3);
- batch.Items.Capacity.ShouldBe(4);
+ batch.Items.Capacity.ShouldBe(Pools.DefaultCapacity);
batch.TrimExcess();
batch.Items.Capacity.ShouldBe(2);