diff --git a/Gangs.sln b/Gangs.sln index 0b1507f..23c3740 100644 --- a/Gangs.sln +++ b/Gangs.sln @@ -10,6 +10,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GangsImpl.Mock", "GangsImpl EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GangsImpl.SQL", "GangsImpl.SQL\GangsImpl.SQL.csproj", "{7F519D8B-63B5-4628-9806-5E9FE79F9797}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GangsImpl.SQLite", "GangsImpl.SQLite\GangsImpl.SQLite.csproj", "{F87CD126-3D2D-47B7-A11E-93987772D1B0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -36,5 +38,9 @@ Global {7F519D8B-63B5-4628-9806-5E9FE79F9797}.Debug|Any CPU.Build.0 = Debug|Any CPU {7F519D8B-63B5-4628-9806-5E9FE79F9797}.Release|Any CPU.ActiveCfg = Release|Any CPU {7F519D8B-63B5-4628-9806-5E9FE79F9797}.Release|Any CPU.Build.0 = Release|Any CPU + {F87CD126-3D2D-47B7-A11E-93987772D1B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F87CD126-3D2D-47B7-A11E-93987772D1B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F87CD126-3D2D-47B7-A11E-93987772D1B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F87CD126-3D2D-47B7-A11E-93987772D1B0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/GangsImpl.SQL/SQLStatManager.cs b/GangsImpl.SQL/SQLStatManager.cs index 6fdabba..7fc0d61 100644 --- a/GangsImpl.SQL/SQLStatManager.cs +++ b/GangsImpl.SQL/SQLStatManager.cs @@ -1,5 +1,4 @@ -using System.Data; -using Dapper; +using Dapper; using GangsAPI.Data.Stat; using GangsAPI.Services; using MySqlConnector; @@ -10,25 +9,35 @@ public class SQLStatManager(string connectionString, string table = "gang_stats") : IStatManager { private readonly HashSet stats = []; private MySqlConnection connection = null!; + private MySqlTransaction transaction = null!; public void Start() { connection = new MySqlConnection(connectionString); connection.Open(); - var command = connection.CreateCommand(); - command.CommandText = - $"CREATE TABLE IF NOT EXISTS {table} (StatId VARCHAR(255) PRIMARY KEY, Name VARCHAR(255), Description TEXT)"; - command.ExecuteNonQuery(); + transaction = connection.BeginTransaction(); + + try { + var command = connection.CreateCommand(); - connection.Query($"SELECT * FROM {table}") - .ToList() - .ForEach(stat => stats.Add(stat)); + command.Transaction = transaction; + command.CommandText = + $"CREATE TEMPORARY TABLE IF NOT EXISTS {table} (StatId VARCHAR(255) PRIMARY KEY, Name VARCHAR(255), Description TEXT)"; + command.ExecuteNonQuery(); + + connection + .Query($"SELECT * FROM {table}", transaction: transaction) + .ToList() + .ForEach(stat => stats.Add(stat)); + } catch (Exception e) { + transaction.Rollback(); + throw new InvalidOperationException("Failed initializing the database", + e); + } } public void Dispose() { - var command = connection.CreateCommand(); - command.CommandText = $"DROP TABLE IF EXISTS {table}"; - command.ExecuteNonQuery(); + transaction.Rollback(); connection.Close(); connection.Dispose(); } @@ -50,8 +59,10 @@ public async Task> GetStats() { } public async Task RegisterStat(IStat stat) { + if (stats.Contains(stat)) return false; var sqlStat = (SQLStat)stat; var command = connection.CreateCommand(); + command.Transaction = transaction; command.CommandText = $"INSERT INTO {table} (StatId, Name, Description) VALUES (@StatId, @Name, @Description)"; command.Parameters.AddWithValue("@StatId", sqlStat.StatId); @@ -66,6 +77,7 @@ public async Task UnregisterStat(string id) { foreach (var stat in matches) stats.Remove(stat); var command = connection.CreateCommand(); + command.Transaction = transaction; command.CommandText = $"DELETE FROM {table} WHERE StatId = @StatId"; command.Parameters.AddWithValue("@StatId", id); await command.ExecuteNonQueryAsync(); diff --git a/GangsImpl.SQLite/GangsImpl.SQLite.csproj b/GangsImpl.SQLite/GangsImpl.SQLite.csproj new file mode 100644 index 0000000..81046c9 --- /dev/null +++ b/GangsImpl.SQLite/GangsImpl.SQLite.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + GangsImpl.SQLLite + + + + + + + + + + + + + diff --git a/GangsImpl.SQLite/SQLiteStatManager.cs b/GangsImpl.SQLite/SQLiteStatManager.cs new file mode 100644 index 0000000..eddfba2 --- /dev/null +++ b/GangsImpl.SQLite/SQLiteStatManager.cs @@ -0,0 +1,88 @@ +using Dapper; +using GangsAPI.Data.Stat; +using GangsAPI.Services; +using GangsImpl.SQL; +using Microsoft.Data.Sqlite; + +namespace GangsImpl.SQLLite; + +public class SQLiteStatManager(string connectionString, + string table = "gang_stats") : IStatManager { + private readonly HashSet stats = []; + private SqliteConnection connection = null!; + private SqliteTransaction transaction = null!; + + public void Start() { + connection = new SqliteConnection(connectionString); + + connection.Open(); + transaction = connection.BeginTransaction(); + + try { + var command = connection.CreateCommand(); + + command.Transaction = transaction; + command.CommandText = + $"CREATE TEMPORARY TABLE IF NOT EXISTS {table} (StatId VARCHAR(255) PRIMARY KEY, Name VARCHAR(255), Description TEXT)"; + command.ExecuteNonQuery(); + + connection + .Query($"SELECT * FROM {table}", transaction: transaction) + .ToList() + .ForEach(stat => stats.Add(stat)); + } catch (Exception e) { + transaction.Rollback(); + throw new InvalidOperationException("Failed initializing the database", + e); + } + } + + public void Dispose() { + transaction.Rollback(); + connection.Close(); + connection.Dispose(); + } + + public async Task> GetStats() { + return await Task.FromResult>(stats); + } + + public Task GetStat(string id) { + return Task.FromResult(stats.FirstOrDefault(stat => stat.StatId == id)); + } + + public async Task CreateStat(string id, string name, + string? description = null) { + var stat = await GetStat(id); + if (stat != null) return stat; + stat = new SQLStat { StatId = id, Name = name, Description = description }; + return stat; + } + + public async Task RegisterStat(IStat stat) { + if (stats.Contains(stat)) return false; + var sqlStat = (SQLStat)stat; + var command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = + $"INSERT INTO {table} (StatId, Name, Description) VALUES (@StatId, @Name, @Description)"; + command.Parameters.AddWithValue("@StatId", sqlStat.StatId); + command.Parameters.AddWithValue("@Name", sqlStat.Name); + command.Parameters.AddWithValue("@Description", sqlStat.Description); + await command.ExecuteNonQueryAsync(); + return stats.Add(stat); + } + + public async Task UnregisterStat(string id) { + var matches = stats.Where(stat => stat.StatId == id).ToList(); + foreach (var stat in matches) stats.Remove(stat); + + var command = connection.CreateCommand(); + command.Transaction = transaction; + command.CommandText = $"DELETE FROM {table} WHERE StatId = @StatId"; + command.Parameters.AddWithValue("@StatId", id); + await command.ExecuteNonQueryAsync(); + + return await Task.FromResult(matches.Count > 0); + } +} \ No newline at end of file diff --git a/GangsTest/GangsTest.csproj b/GangsTest/GangsTest.csproj index ebb4894..eb7f25c 100644 --- a/GangsTest/GangsTest.csproj +++ b/GangsTest/GangsTest.csproj @@ -30,6 +30,7 @@ + diff --git a/GangsTest/Startup.cs b/GangsTest/Startup.cs index c668b2a..2c01ff4 100644 --- a/GangsTest/Startup.cs +++ b/GangsTest/Startup.cs @@ -6,10 +6,10 @@ namespace GangsTest; public class Startup { public void ConfigureServices(IServiceCollection services) { - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); } } \ No newline at end of file diff --git a/GangsTest/StatTests/StatManagerData.cs b/GangsTest/StatTests/StatManagerData.cs index d83d0f7..af8f290 100644 --- a/GangsTest/StatTests/StatManagerData.cs +++ b/GangsTest/StatTests/StatManagerData.cs @@ -2,13 +2,16 @@ using GangsAPI; using GangsImpl.Memory; using GangsImpl.SQL; +using GangsImpl.SQLLite; namespace GangsTest.StatTests; public class StatManagerData : IEnumerable { private readonly IBehavior[] behaviors = [ new MockStatManager(), - new SQLStatManager("Server=localhost;User=root;Database=gang", "gang_unit_test") + new SQLStatManager("Server=localhost;User=root;Database=gang", + "gang_unit_test"), + new SQLiteStatManager("Data Source=:memory:", "gang_unit_test") ]; public StatManagerData() {