From daefaa274d7f563fbf9b25110877f074650010ca Mon Sep 17 00:00:00 2001 From: Michael Stonis Date: Wed, 1 Sep 2021 10:52:21 -0500 Subject: [PATCH] updating insertion, transactions, and unit tests --- SqliteJson/MainPage.xaml.cs | 42 ++-- SqliteJson/SqliteJson.csproj | 2 + Tycho.Benchmarks/Benchmarks/Insertion.cs | 162 +++++++++---- Tycho.Benchmarks/TestModels.cs | 8 +- Tycho.UnitTests/Tycho.UnitTests.csproj | 9 +- Tycho.UnitTests/TychoDbTests.cs | 6 +- Tycho/Queries.cs | 5 + Tycho/RegisteredTypeInformation.cs | 38 +-- Tycho/TychoDb.cs | 282 ++++++++++++----------- 9 files changed, 318 insertions(+), 236 deletions(-) diff --git a/SqliteJson/MainPage.xaml.cs b/SqliteJson/MainPage.xaml.cs index 5d2441d..e3760a7 100644 --- a/SqliteJson/MainPage.xaml.cs +++ b/SqliteJson/MainPage.xaml.cs @@ -1,28 +1,28 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; using Tycho; using Xamarin.Essentials; -using Xamarin.Forms; - -namespace SqliteJson -{ - public partial class MainPage : ContentPage - { - public MainPage () - { - InitializeComponent (); - } - +using Xamarin.Forms; + +namespace SqliteJson +{ + public partial class MainPage : ContentPage + { + public MainPage () + { + InitializeComponent (); + } + protected override async void OnAppearing () { base.OnAppearing (); using var db = - await new TychoDb (FileSystem.AppDataDirectory) + await new TychoDb (FileSystem.AppDataDirectory, new SystemTextJsonSerializer()) .ConnectAsync(); var testObj = @@ -38,7 +38,7 @@ protected override async void OnAppearing () var readResult = await db.ReadObjectAsync (testObj.StringProperty); System.Diagnostics.Debug.WriteLine ($"{readResult}"); - } + } } class TestClassA @@ -48,5 +48,5 @@ class TestClassA public int IntProperty { get; set; } public long TimestampMillis { get; set; } - } -} + } +} diff --git a/SqliteJson/SqliteJson.csproj b/SqliteJson/SqliteJson.csproj index d523cf6..39b5b29 100644 --- a/SqliteJson/SqliteJson.csproj +++ b/SqliteJson/SqliteJson.csproj @@ -15,6 +15,8 @@ + + \ No newline at end of file diff --git a/Tycho.Benchmarks/Benchmarks/Insertion.cs b/Tycho.Benchmarks/Benchmarks/Insertion.cs index eacded3..cde7fa1 100644 --- a/Tycho.Benchmarks/Benchmarks/Insertion.cs +++ b/Tycho.Benchmarks/Benchmarks/Insertion.cs @@ -1,32 +1,118 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; +using Microsoft.Diagnostics.Tracing.Parsers.ApplicationServer; -namespace Tycho.Benchmarks.Benchmarks -{ - [MemoryDiagnoser] - [SimpleJob (launchCount: 1, warmupCount: 1, targetCount: 10)] - public class Insertion - { - [ParamsSource (nameof (JsonSerializers))] - public IJsonSerializer JsonSerializer { get; set; } - - public static IEnumerable JsonSerializers () - => new IJsonSerializer[] { new SystemTextJsonSerializer (), new NewtonsoftJsonSerializer() }; - - [Benchmark] - public async Task InsertManyAsync () - { +namespace Tycho.Benchmarks.Benchmarks +{ + [MemoryDiagnoser] + [SimpleJob (launchCount: 1, warmupCount: 1, targetCount: 10)] + public class Insertion + { + [ParamsSource (nameof (JsonSerializers))] + public IJsonSerializer JsonSerializer { get; set; } + + public static IEnumerable JsonSerializers () + => new IJsonSerializer[] { new SystemTextJsonSerializer (), new NewtonsoftJsonSerializer() }; + + private static TestClassE _largeTestObject = + new TestClassE() + { + TestClassId = Guid.NewGuid(), + Values = + new [] + { + new TestClassD() + { + DoubleProperty = 12d, + FloatProperty = 15f, + ValueC = + new TestClassC() + { + DoubleProperty = 14d, + IntProperty = 15, + } + }, + new TestClassD() + { + DoubleProperty = 12d, + FloatProperty = 15f, + ValueC = + new TestClassC() + { + DoubleProperty = 14d, + IntProperty = 15, + } + }, + new TestClassD() + { + DoubleProperty = 12d, + FloatProperty = 15f, + ValueC = + new TestClassC() + { + DoubleProperty = 14d, + IntProperty = 15, + } + }, + new TestClassD() + { + DoubleProperty = 12d, + FloatProperty = 15f, + ValueC = + new TestClassC() + { + DoubleProperty = 14d, + IntProperty = 15, + } + } + } + + + }; + + internal static TestClassE LargeTestObject => _largeTestObject; + + [Benchmark] + public async Task InsertSingularAsync() + { + using var db = + new TychoDb(Path.GetTempPath(), JsonSerializer, rebuildCache: true) + .Connect(); + + var testObj = + new TestClassA + { + StringProperty = $"Test String", + IntProperty = 100, + TimestampMillis = 123451234, + }; + + + await db.WriteObjectAsync(testObj, x => x.StringProperty).ConfigureAwait(false); + } + + [Benchmark] + public async Task InsertSingularLargeObjectAsync () + { + using var db = + new TychoDb(Path.GetTempPath(), JsonSerializer, rebuildCache: true) + .Connect(); + + await db.WriteObjectAsync (LargeTestObject, x => x.TestClassId).ConfigureAwait (false); + } + + [Benchmark] + public async Task InsertManyAsync () + { using var db = new TychoDb (Path.GetTempPath (), JsonSerializer, rebuildCache: true) .Connect (); - var successWrites = 0; - for (int i = 100; i < 1100; i++) { var testObj = @@ -38,26 +124,17 @@ public async Task InsertManyAsync () }; - var resultWrite = await db.WriteObjectAsync (testObj, x => x.StringProperty).ConfigureAwait (false); + await db.WriteObjectAsync (testObj, x => x.StringProperty).ConfigureAwait (false); + } + } - if (resultWrite) - { - Interlocked.Increment (ref successWrites); - } - } - - return successWrites; - } - - [Benchmark] - public async Task InsertManyConcurrentAsync () - { + [Benchmark] + public async Task InsertManyConcurrentAsync () + { using var db = new TychoDb (Path.GetTempPath (), JsonSerializer, rebuildCache: true) .Connect (); - - var successWrites = 0; - + var tasks = Enumerable .Range (100, 1000) @@ -73,18 +150,11 @@ public async Task InsertManyConcurrentAsync () }; - var resultWrite = await db.WriteObjectAsync (testObj, x => x.StringProperty).ConfigureAwait (false); - - if (resultWrite) - { - Interlocked.Increment (ref successWrites); - } + await db.WriteObjectAsync (testObj, x => x.StringProperty).ConfigureAwait (false); }) .ToList (); - await Task.WhenAll (tasks).ConfigureAwait (false); - - return successWrites; - } - } -} + await Task.WhenAll (tasks).ConfigureAwait (false); + } + } +} diff --git a/Tycho.Benchmarks/TestModels.cs b/Tycho.Benchmarks/TestModels.cs index 52cfb0b..106b599 100644 --- a/Tycho.Benchmarks/TestModels.cs +++ b/Tycho.Benchmarks/TestModels.cs @@ -1,8 +1,8 @@ using System; -using System.Collections.Generic; - +using System.Collections.Generic; + namespace Tycho.Benchmarks -{ +{ class TestClassA { public string StringProperty { get; set; } @@ -24,7 +24,7 @@ class TestClassC { public int IntProperty { get; set; } - public string DoubleProperty { get; set; } + public double DoubleProperty { get; set; } } class TestClassD diff --git a/Tycho.UnitTests/Tycho.UnitTests.csproj b/Tycho.UnitTests/Tycho.UnitTests.csproj index 14af98c..6a14348 100644 --- a/Tycho.UnitTests/Tycho.UnitTests.csproj +++ b/Tycho.UnitTests/Tycho.UnitTests.csproj @@ -10,11 +10,12 @@ - runtime; build; native; contentfiles; analyzers; buildtransitive -all - + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + - + diff --git a/Tycho.UnitTests/TychoDbTests.cs b/Tycho.UnitTests/TychoDbTests.cs index 79fbfc7..d25304a 100644 --- a/Tycho.UnitTests/TychoDbTests.cs +++ b/Tycho.UnitTests/TychoDbTests.cs @@ -818,7 +818,9 @@ public class Patient : ModelBase } public abstract class ModelBase : INotifyPropertyChanged - { - public event PropertyChangedEventHandler PropertyChanged; + { +#pragma warning disable CS0067 + public event PropertyChangedEventHandler PropertyChanged; +#pragma warning restore CS0067 } } diff --git a/Tycho/Queries.cs b/Tycho/Queries.cs index d3cf478..9abf10f 100644 --- a/Tycho/Queries.cs +++ b/Tycho/Queries.cs @@ -49,6 +49,11 @@ FROM JsonValue AND FullTypeName = $fullTypeName"; + public const string SelectPartitions = +@" +SELECT DISTINCT Partition +From JsonValue"; + public const string SelectDataFromJsonValueWithFullTypeName = @" SELECT Data diff --git a/Tycho/RegisteredTypeInformation.cs b/Tycho/RegisteredTypeInformation.cs index d48fbc5..4a5bb87 100644 --- a/Tycho/RegisteredTypeInformation.cs +++ b/Tycho/RegisteredTypeInformation.cs @@ -12,6 +12,10 @@ internal struct RegisteredTypeInformation public string IdProperty { get; set; } + public string IdPropertyPath { get; set; } + + public bool IsNumeric { get; set; } + public string TypeFullName { get; set; } public string TypeName { get; set; } @@ -31,7 +35,9 @@ public static RegisteredTypeInformation Create (Expression> new RegisteredTypeInformation { FuncIdSelector = compiledExpression, - IdProperty = GetMemberInfo(idProperty).Member.Name, + IdProperty = GetExpressionMemberName (idProperty), + IdPropertyPath = QueryPropertyPath.BuildPath(idProperty), + IsNumeric = QueryPropertyPath.IsNumeric(idProperty), TypeFullName = type.FullName, TypeName = type.Name, TypeNamespace = type.Namespace, @@ -49,28 +55,22 @@ public Func GetId () return (Func)FuncIdSelector; } - private static MemberExpression GetMemberInfo (Expression method) + private static string GetExpressionMemberName (Expression method) { - LambdaExpression lambda = method as LambdaExpression; - if (lambda == null) - throw new ArgumentNullException ("method"); - - MemberExpression memberExpr = null; - - if (lambda.Body.NodeType == ExpressionType.Convert) + if(method is LambdaExpression lex) { - memberExpr = - ((UnaryExpression)lambda.Body).Operand as MemberExpression; - } - else if (lambda.Body.NodeType == ExpressionType.MemberAccess) - { - memberExpr = lambda.Body as MemberExpression; - } + if (lex.Body.NodeType == ExpressionType.Convert) + { + return (((UnaryExpression)lex.Body).Operand as MemberExpression).Member.Name; + } - if (memberExpr == null) - throw new ArgumentException ("method"); + if (lex.Body.NodeType == ExpressionType.MemberAccess) + { + return (lex.Body as MemberExpression).Member.Name; + } + } - return memberExpr; + throw new TychoDbException ("The provided expression is not valid member expression"); } } } diff --git a/Tycho/TychoDb.cs b/Tycho/TychoDb.cs index 74f6b1b..41f2439 100644 --- a/Tycho/TychoDb.cs +++ b/Tycho/TychoDb.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Data; using System.IO; @@ -9,22 +9,22 @@ using System.Threading.Tasks; using Microsoft.Data.Sqlite; -namespace Tycho -{ - public class TychoDb : IDisposable - { - private const string - ParameterFullTypeName = "$fullTypeName", - ParameterPartition = "$partition", - ParameterKey = "$key", - ParameterJson = "$json"; - +namespace Tycho +{ + public class TychoDb : IDisposable + { + private const string + ParameterFullTypeName = "$fullTypeName", + ParameterPartition = "$partition", + ParameterKey = "$key", + ParameterJson = "$json"; + private readonly object _connectionLock = new object (); - private readonly string _dbConnectionString; - - private readonly IJsonSerializer _jsonSerializer; - + private readonly string _dbConnectionString; + + private readonly IJsonSerializer _jsonSerializer; + private readonly Dictionary _registeredTypeInformation = new Dictionary(); private readonly ProcessingQueue _processingQueue = new ProcessingQueue(); @@ -33,19 +33,19 @@ private const string private bool _isDisposed; - public TychoDb (string dbPath, IJsonSerializer jsonSerializer, string dbName = "tycho_cache.db", string password = null, bool rebuildCache = false) - { - SQLitePCL.Batteries_V2.Init (); - - _jsonSerializer = jsonSerializer; - - var databasePath = Path.Join (dbPath, dbName); - + public TychoDb (string dbPath, IJsonSerializer jsonSerializer, string dbName = "tycho_cache.db", string password = null, bool rebuildCache = false) + { + SQLitePCL.Batteries_V2.Init (); + + _jsonSerializer = jsonSerializer; + + var databasePath = Path.Join (dbPath, dbName); + if(rebuildCache && File.Exists(databasePath)) { File.Delete (databasePath); - } - + } + var connectionStringBuilder = new SqliteConnectionStringBuilder { @@ -53,56 +53,68 @@ public TychoDb (string dbPath, IJsonSerializer jsonSerializer, string dbName = " Cache = SqliteCacheMode.Default, Mode = SqliteOpenMode.ReadWriteCreate, - }; - + }; + if(password != null) { connectionStringBuilder.Password = password; - } - - _dbConnectionString = connectionStringBuilder.ToString (); - } - - public TychoDb AddTypeRegistration (Expression> idSelector) - where T : class - { + } + + _dbConnectionString = connectionStringBuilder.ToString (); + } + + public TychoDb AddTypeRegistration (Expression> idSelector) + where T : class + { var rti = RegisteredTypeInformation.Create (idSelector); - _registeredTypeInformation[rti.ObjectType] = rti; - - return this; - } - + _registeredTypeInformation[rti.ObjectType] = rti; + + return this; + } + public TychoDb Connect () - { - + { + if(_connection == null) { - _connection = BuildConnection (); - } - - return this; - } - + _connection = BuildConnection (); + + foreach (var registeredType in _registeredTypeInformation) + { + var value = registeredType.Value; + CreateIndex(value.IdPropertyPath, value.IsNumeric, value.TypeName, $"ID_{value.TypeName}_{value.IdProperty}"); + } + } + + return this; + } + public async ValueTask ConnectAsync () - { - + { + if (_connection == null) { - _connection = await BuildConnectionAsync ().ConfigureAwait(false); - } - - return this; - } - + _connection = await BuildConnectionAsync ().ConfigureAwait(false); + + foreach (var registeredType in _registeredTypeInformation) + { + var value = registeredType.Value; + await CreateIndexAsync(value.IdPropertyPath, value.IsNumeric, value.TypeName, $"ID_{value.TypeName}_{value.IdProperty}").ConfigureAwait(false); + } + } + + return this; + } + public void Disconnect () - { + { lock(_connectionLock) { _connection?.Dispose (); - } - } - + } + } + public ValueTask DisconnectAsync () { if(_connection == null) @@ -110,14 +122,14 @@ public ValueTask DisconnectAsync () return new ValueTask (Task.CompletedTask); } - return _connection.DisposeAsync (); - } - + return _connection.DisposeAsync (); + } + public ValueTask WriteObjectAsync (T obj, string partition = null, CancellationToken cancellationToken = default) { return WriteObjectsAsync (new[] { obj }, GetIdFor(), partition, cancellationToken); - } - + } + public ValueTask WriteObjectAsync (T obj, Func keySelector, string partition = null, CancellationToken cancellationToken = default) { return WriteObjectsAsync (new[] { obj }, keySelector, partition, cancellationToken); @@ -138,7 +150,7 @@ public ValueTask WriteObjectsAsync(IEnumerable objs, Func var writeCount = 0; var totalCount = objs.Count (); - var transaction = await conn.BeginTransactionAsync (IsolationLevel.Serializable, cancellationToken).ConfigureAwait(false); + using var transaction = await conn.BeginTransactionAsync (IsolationLevel.Serializable, cancellationToken).ConfigureAwait(false); try { @@ -170,8 +182,8 @@ public ValueTask WriteObjectsAsync(IEnumerable objs, Func return writeCount == totalCount; }); - } - + } + public ValueTask ReadObjectAsync (object key, string partition = null, CancellationToken cancellationToken = default) { return _connection @@ -179,7 +191,7 @@ public ValueTask ReadObjectAsync (object key, string partition = null, Can _processingQueue, async conn => { - var transaction = await conn.BeginTransactionAsync (IsolationLevel.RepeatableRead, cancellationToken).ConfigureAwait (false); + using var transaction = await conn.BeginTransactionAsync (IsolationLevel.RepeatableRead, cancellationToken).ConfigureAwait (false); try { @@ -206,23 +218,22 @@ public ValueTask ReadObjectAsync (object key, string partition = null, Can using var reader = await selectCommand.ExecuteReaderAsync (cancellationToken).ConfigureAwait (false); + T returnValue = default(T); while (await reader.ReadAsync (cancellationToken).ConfigureAwait (false)) { using var stream = reader.GetStream (0); - return await _jsonSerializer.DeserializeAsync(stream, cancellationToken).ConfigureAwait (false); + returnValue = await _jsonSerializer.DeserializeAsync(stream, cancellationToken).ConfigureAwait (false); } + + await transaction.CommitAsync (cancellationToken).ConfigureAwait (false); + + return returnValue; } catch (Exception ex) { await transaction.RollbackAsync (cancellationToken).ConfigureAwait (false); throw new TychoDbException ($"Failed Reading Object with key \"{key}\"", ex); } - finally - { - await transaction.CommitAsync ().ConfigureAwait (false); - } - - return default; }); } @@ -236,8 +247,8 @@ public async ValueTask ReadObjectAsync (FilterBuilder filter, string pa } return result.FirstOrDefault (); - } - + } + public ValueTask> ReadObjectsAsync (string partition = null, FilterBuilder filter = null, CancellationToken cancellationToken = default) { return _connection @@ -245,7 +256,7 @@ public ValueTask> ReadObjectsAsync (string partition = null, F _processingQueue, async conn => { - var transaction = await conn.BeginTransactionAsync (IsolationLevel.RepeatableRead, cancellationToken).ConfigureAwait (false); + using var transaction = await conn.BeginTransactionAsync (IsolationLevel.RepeatableRead, cancellationToken).ConfigureAwait (false); try { @@ -284,6 +295,8 @@ public ValueTask> ReadObjectsAsync (string partition = null, F objects.Add(await _jsonSerializer.DeserializeAsync (stream, cancellationToken).ConfigureAwait (false)); } + await transaction.CommitAsync (cancellationToken).ConfigureAwait (false); + return objects; } catch (Exception ex) @@ -291,13 +304,9 @@ public ValueTask> ReadObjectsAsync (string partition = null, F await transaction.RollbackAsync (cancellationToken).ConfigureAwait (false); throw new TychoDbException ($"Failed Reading Objects", ex); } - finally - { - await transaction.CommitAsync ().ConfigureAwait (false); - } }); - } - + } + public ValueTask> ReadObjectsAsync (Expression> innerObjectSelection, string partition = null, FilterBuilder filter = null, CancellationToken cancellationToken = default) { return _connection @@ -305,7 +314,7 @@ public ValueTask> ReadObjectsAsync (Expression { - var transaction = await conn.BeginTransactionAsync (IsolationLevel.RepeatableRead, cancellationToken).ConfigureAwait (false); + using var transaction = await conn.BeginTransactionAsync (IsolationLevel.RepeatableRead, cancellationToken).ConfigureAwait (false); var objects = new List (); @@ -346,16 +355,13 @@ public ValueTask> ReadObjectsAsync (Expression (stream, cancellationToken).ConfigureAwait (false)); } + await transaction.CommitAsync (cancellationToken).ConfigureAwait (false); } catch (Exception ex) { await transaction.RollbackAsync (cancellationToken).ConfigureAwait (false); throw new TychoDbException ("Failed Reading Objects", ex); } - finally - { - await transaction.CommitAsync ().ConfigureAwait (false); - } return objects; }); @@ -368,7 +374,7 @@ public ValueTask DeleteObjectAsync (object key, string partition = null _processingQueue, async conn => { - var transaction = await conn.BeginTransactionAsync (IsolationLevel.Serializable, cancellationToken).ConfigureAwait (false); + using var transaction = await conn.BeginTransactionAsync (IsolationLevel.Serializable, cancellationToken).ConfigureAwait (false); try { @@ -395,6 +401,8 @@ public ValueTask DeleteObjectAsync (object key, string partition = null var deletionCount = await selectCommand.ExecuteNonQueryAsync (cancellationToken).ConfigureAwait (false); + await transaction.CommitAsync ().ConfigureAwait (false); + return deletionCount == 1; } catch (Exception ex) @@ -402,10 +410,6 @@ public ValueTask DeleteObjectAsync (object key, string partition = null await transaction.RollbackAsync (cancellationToken).ConfigureAwait (false); throw new TychoDbException ($"Failed to delete object with key \"{key}\"", ex); } - finally - { - await transaction.CommitAsync ().ConfigureAwait (false); - } }); } @@ -416,7 +420,7 @@ public ValueTask DeleteObjectAsync (object key, string partition = null _processingQueue, async conn => { - var transaction = await conn.BeginTransactionAsync (IsolationLevel.Serializable, cancellationToken).ConfigureAwait (false); + using var transaction = await conn.BeginTransactionAsync (IsolationLevel.Serializable, cancellationToken).ConfigureAwait (false); try { @@ -447,60 +451,58 @@ public ValueTask DeleteObjectAsync (object key, string partition = null var deletionCount = await selectCommand.ExecuteNonQueryAsync (cancellationToken).ConfigureAwait (false); + await transaction.CommitAsync ().ConfigureAwait (false); + return (deletionCount > 0, deletionCount); } catch (Exception ex) { await transaction.RollbackAsync (cancellationToken).ConfigureAwait (false); - throw new TychoDbException ("Failed to delete objects", ex); } - finally - { - await transaction.CommitAsync ().ConfigureAwait (false); - } }); - } + } public TychoDb CreateIndex (Expression> propertyPath, string indexName) + { + return CreateIndex(QueryPropertyPath.BuildPath(propertyPath), QueryPropertyPath.IsNumeric(propertyPath), typeof(TObj).Name, indexName); + } + + public TychoDb CreateIndex(string propertyPathString, bool isNumeric, string objectTypeName, string indexName) { lock (_connectionLock) { try { - _connection.Open (); - - var transaction = _connection.BeginTransaction (IsolationLevel.Serializable); + _connection.Open(); - var propertyPathString = QueryPropertyPath.BuildPath (propertyPath); + var transaction = _connection.BeginTransaction(IsolationLevel.Serializable); - var isNumeric = QueryPropertyPath.IsNumeric (propertyPath); - - var fullIndexName = $"idx_{indexName}_{typeof (TObj).Name}"; + var fullIndexName = $"idx_{indexName}_{objectTypeName}"; try { - using var createIndexCommand = _connection.CreateCommand (); + using var createIndexCommand = _connection.CreateCommand(); if (isNumeric) { - createIndexCommand.CommandText = Queries.CreateIndexForJsonValueAsNumeric (fullIndexName, propertyPathString); + createIndexCommand.CommandText = Queries.CreateIndexForJsonValueAsNumeric(fullIndexName, propertyPathString); } else { - createIndexCommand.CommandText = Queries.CreateIndexForJsonValue (fullIndexName, propertyPathString); + createIndexCommand.CommandText = Queries.CreateIndexForJsonValue(fullIndexName, propertyPathString); } - createIndexCommand.ExecuteNonQuery (); + createIndexCommand.ExecuteNonQuery(); } catch (Exception ex) { transaction.Rollback(); - throw new TychoDbException ($"Failed to Create Index: {fullIndexName}", ex); + throw new TychoDbException($"Failed to Create Index: {fullIndexName}", ex); } finally { - transaction.Commit (); + transaction.Commit(); } } @@ -514,53 +516,52 @@ public TychoDb CreateIndex (Expression> propertyPath, s } public ValueTask CreateIndexAsync(Expression> propertyPath, string indexName, CancellationToken cancellationToken = default) + { + return CreateIndexAsync(QueryPropertyPath.BuildPath(propertyPath), QueryPropertyPath.IsNumeric(propertyPath), typeof(TObj).Name, indexName); + } + + public ValueTask CreateIndexAsync(string propertyPathString, bool isNumeric, string objectTypeName, string indexName, CancellationToken cancellationToken = default) { return _connection - .WithConnectionBlock ( + .WithConnectionBlock( _processingQueue, async conn => { - var transaction = await conn.BeginTransactionAsync (IsolationLevel.Serializable, cancellationToken).ConfigureAwait (false); + using var transaction = await conn.BeginTransactionAsync(IsolationLevel.Serializable, cancellationToken).ConfigureAwait(false); var result = false; - var propertyPathString = QueryPropertyPath.BuildPath (propertyPath); - - var fullIndexName = $"idx_{indexName}_{typeof(TObj).Name}"; - - var isNumeric = QueryPropertyPath.IsNumeric (propertyPath); + var fullIndexName = $"idx_{indexName}_{objectTypeName}"; try { - using var createIndexCommand = conn.CreateCommand (); + using var createIndexCommand = conn.CreateCommand(); if (isNumeric) { - createIndexCommand.CommandText = Queries.CreateIndexForJsonValueAsNumeric (fullIndexName, propertyPathString); + createIndexCommand.CommandText = Queries.CreateIndexForJsonValueAsNumeric(fullIndexName, propertyPathString); } else { - createIndexCommand.CommandText = Queries.CreateIndexForJsonValue (fullIndexName, propertyPathString); + createIndexCommand.CommandText = Queries.CreateIndexForJsonValue(fullIndexName, propertyPathString); } - await createIndexCommand.ExecuteNonQueryAsync ().ConfigureAwait(false); + await createIndexCommand.ExecuteNonQueryAsync().ConfigureAwait(false); + + await transaction.CommitAsync().ConfigureAwait(false); result = true; } catch (Exception ex) { - await transaction.RollbackAsync (cancellationToken).ConfigureAwait (false); - throw new TychoDbException ($"Failed to Create Index: {fullIndexName}", ex); - } - finally - { - await transaction.CommitAsync ().ConfigureAwait (false); + await transaction.RollbackAsync(cancellationToken).ConfigureAwait(false); + throw new TychoDbException($"Failed to Create Index: {fullIndexName}", ex); } return result; }); - } - + } + protected virtual void Dispose (bool disposing) { if (!_isDisposed) @@ -579,8 +580,8 @@ public void Dispose () // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose (disposing: true); GC.SuppressFinalize (this); - } - + } + private SqliteConnection BuildConnection () { lock(_connectionLock) @@ -646,6 +647,7 @@ private ValueTask BuildConnectionAsync (CancellationToken canc // Enable write-ahead logging using var hasJsonCommand = _connection.CreateCommand (); hasJsonCommand.CommandText = Queries.PragmaCompileOptions; + using var reader = await hasJsonCommand.ExecuteReaderAsync (cancellationToken).ConfigureAwait (false); while (await reader.ReadAsync (cancellationToken).ConfigureAwait(false)) @@ -737,11 +739,11 @@ public static ValueTask WithConnectionBlock (this SqliteConnection connect } }); } - + public static object AsValueOrDbNull(this T value) where T : class { return value ?? (object)DBNull.Value; - } - } -} + } + } +}