Files
speckle-sharp-sdk/src/Speckle.Sdk/Serialisation/V2/SQLiteCacheManager.cs
T
Adam Hathcock cca8828565 Using Tasks for Deserialization (#143)
* Use a stack channel for deserialization

* multi-threaded

* add object dictionary pool

* more pooling

* adjust sqlite transport

* format

* Optimize IsPropNameValid

* object loader first pass

* save test

* add cache pre check

* save better deserialize

* mostly works

* uses tasks but slower at end

* rework to make more sense

* add check to avoid multi-deserialize

* modify max parallelism

* async enqueuing of tasks

* switch to more asyncenumerable

* fmt

* fmt

* cleanup sqlite

* make ServerObjectManager

* revert change

* add ability to skip cache check

* cache json to know what is loaded

* testing

* clean up usage

* clean up and added new op

* Fix exception handling

* fixing progress

* remove codejam

* remove stackchannel

* remove console writeline

* add cache check shortcut for root object

* recevie2 benchmark

---------

Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
2024-10-22 14:14:39 +01:00

157 lines
5.0 KiB
C#

using Microsoft.Data.Sqlite;
using Speckle.InterfaceGenerator;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Transports;
namespace Speckle.Sdk.Serialisation.V2;
[GenerateAutoInterface]
public class SQLiteCacheManager : ISQLiteCacheManager
{
private readonly string _rootPath;
private readonly string _connectionString;
private const string APPLICATION_NAME = "Speckle";
private const string DATA_FOLDER = "Projects";
public SQLiteCacheManager(string streamId)
{
var basePath = SpecklePathProvider.UserApplicationDataPath();
try
{
var dir = Path.Combine(basePath, APPLICATION_NAME, DATA_FOLDER);
_rootPath = Path.Combine(dir, $"{streamId}.db");
Directory.CreateDirectory(dir); //ensure dir is there
}
catch (Exception ex)
when (ex is ArgumentException or IOException or UnauthorizedAccessException or NotSupportedException)
{
throw new TransportException($"Path was invalid or could not be created {_rootPath}", ex);
}
_connectionString = $"Data Source={_rootPath};";
Initialize();
}
private void Initialize()
{
// NOTE: used for creating partioned object tables.
//string[] HexChars = new string[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
//var cart = new List<string>();
//foreach (var str in HexChars)
// foreach (var str2 in HexChars)
// cart.Add(str + str2);
using var c = new SqliteConnection(_connectionString);
c.Open();
const string COMMAND_TEXT =
@"
CREATE TABLE IF NOT EXISTS objects(
hash TEXT PRIMARY KEY,
content TEXT
) WITHOUT ROWID;
";
using (var command = new SqliteCommand(COMMAND_TEXT, c))
{
command.ExecuteNonQuery();
}
// Insert Optimisations
using SqliteCommand cmd0 = new("PRAGMA journal_mode='wal';", c);
cmd0.ExecuteNonQuery();
//Note / Hack: This setting has the potential to corrupt the db.
//cmd = new SqliteCommand("PRAGMA synchronous=OFF;", Connection);
//cmd.ExecuteNonQuery();
using SqliteCommand cmd1 = new("PRAGMA count_changes=OFF;", c);
cmd1.ExecuteNonQuery();
using SqliteCommand cmd2 = new("PRAGMA temp_store=MEMORY;", c);
cmd2.ExecuteNonQuery();
}
public IEnumerable<(string, string)> GetObjects(IEnumerable<string> ids, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using var c = new SqliteConnection(_connectionString);
c.Open();
using var command = new SqliteCommand("SELECT content FROM objects WHERE hash = @hash LIMIT 1 ", c);
foreach (var id in ids)
{
command.Parameters.Clear();
command.Parameters.AddWithValue("@hash", id);
using var reader = command.ExecuteReader();
if (reader.Read())
{
yield return (id, reader.GetString(0));
}
}
}
public void SaveObjects(IEnumerable<(string, string)> objects, CancellationToken cancellationToken)
{
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)";
foreach (var (id, content) in objects)
{
using var command = new SqliteCommand(COMMAND_TEXT, c, t);
command.Parameters.AddWithValue("@hash", id);
command.Parameters.AddWithValue("@content", content);
command.ExecuteNonQuery();
cancellationToken.ThrowIfCancellationRequested();
}
t.Commit();
}
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 async IAsyncEnumerable<(string, bool)> HasObjects2(IEnumerable<string> objectIds)
{
await Task.Delay(10).ConfigureAwait(false);
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);
foreach (string objectId in objectIds)
{
command.Parameters.Clear();
command.Parameters.AddWithValue("@hash", objectId);
using var reader = command.ExecuteReader();
bool rowFound = reader.Read();
yield return (objectId, rowFound);
}
}
public void SaveObjectSync(string hash, string serializedObject)
{
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", hash);
command.Parameters.AddWithValue("@content", serializedObject);
command.ExecuteNonQuery();
}
}