diff --git a/DD.Persistence.Database.Postgres.Test/DD.Persistence.Database.Postgres.Test.csproj b/DD.Persistence.Database.Postgres.Test/DD.Persistence.Database.Postgres.Test.csproj new file mode 100644 index 0000000..859acb5 --- /dev/null +++ b/DD.Persistence.Database.Postgres.Test/DD.Persistence.Database.Postgres.Test.csproj @@ -0,0 +1,27 @@ + + + + net9.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + + diff --git a/DD.Persistence.Database.Postgres.Test/DbFixture.cs b/DD.Persistence.Database.Postgres.Test/DbFixture.cs new file mode 100644 index 0000000..c3a0c64 --- /dev/null +++ b/DD.Persistence.Database.Postgres.Test/DbFixture.cs @@ -0,0 +1,40 @@ +using DD.Persistence.Database.Model; +using DD.Persistence.Database.Postgres.Extensions; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace DD.Persistence.Database.Postgres.Test; +public class DbFixture : IDisposable +{ + private string connectionString { get; } + public ServiceProvider serviceProvider { get; private set; } + + public DbFixture() + { + connectionString = $"Host=localhost;Port=5462;Username=postgres;Password=postgres;Database={Guid.CreateVersion7()}"; + + var serviceCollection = new ServiceCollection(); + serviceCollection + .AddDbContext(options => options.UseNpgsql(connectionString), + ServiceLifetime.Transient); + + serviceProvider = serviceCollection.BuildServiceProvider(); + + var context = serviceProvider.GetRequiredService(); + context.Database.EnsureCreated(); + context.Database.AddPartitioning(); + context.SaveChanges(); + } + + public void Dispose() + { + var dbContext = new PersistencePostgresContext( + new DbContextOptionsBuilder() + .UseNpgsql(connectionString) + .Options); + + dbContext.Database.EnsureDeleted(); + + GC.SuppressFinalize(this); + } +} diff --git a/DD.Persistence.Database.Postgres.Test/UnitTestCheckHyperTables.cs b/DD.Persistence.Database.Postgres.Test/UnitTestCheckHyperTables.cs new file mode 100644 index 0000000..6f58046 --- /dev/null +++ b/DD.Persistence.Database.Postgres.Test/UnitTestCheckHyperTables.cs @@ -0,0 +1,65 @@ +using DD.Persistence.Database.Entity; +using Mapster; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Npgsql; + +namespace DD.Persistence.Database.Postgres.Test; + +public class UnitTestCheckHyperTables : IClassFixture +{ + private ServiceProvider _serviceProvider; + + public UnitTestCheckHyperTables(DbFixture fixture) + { + _serviceProvider = fixture.serviceProvider; + } + + [Fact] + public void CreateHyperTable_For_ParameterData_Return_Success() + { + var chunksCount = 0; + + var entity = new ParameterData() + { + DiscriminatorId = Guid.NewGuid(), + ParameterId = 1, + Timestamp = DateTime.UtcNow, + Value = "123" + }; + + using (var context = _serviceProvider.GetService()!) + { + context.ParameterData.Add(entity); + + var entity2 = entity.Adapt(); + entity2.ParameterId = 2; + context.ParameterData.Add(entity2); + + var entity3 = entity2.Adapt(); + entity3.ParameterId = 3; + context.ParameterData.Add(entity3); + + var entity4 = entity3.Adapt(); + entity4.ParameterId = 4; + context.ParameterData.Add(entity4); + + var entity5 = entity3.Adapt(); + entity5.Timestamp = DateTime.UtcNow.AddDays(1).AddHours(1); + context.ParameterData.Add(entity5); + + var entity6 = entity3.Adapt(); + entity6.DiscriminatorId = Guid.CreateVersion7(); + context.ParameterData.Add(entity6); + + context.SaveChanges(); + + string sql = "select count(*) from (select show_chunks('parameter_data'));"; + var queryRow = context.Database.SqlQueryRaw(sql); + + chunksCount = queryRow.AsEnumerable().FirstOrDefault(); + } + + Assert.Equal(5, chunksCount); + } +} diff --git a/DD.Persistence.Database.Postgres/Extensions/EFExtensionsPartitioning.cs b/DD.Persistence.Database.Postgres/Extensions/EFExtensionsPartitioning.cs index 4ee2890..0860dc3 100644 --- a/DD.Persistence.Database.Postgres/Extensions/EFExtensionsPartitioning.cs +++ b/DD.Persistence.Database.Postgres/Extensions/EFExtensionsPartitioning.cs @@ -25,6 +25,8 @@ public static class EFExtensionsPartitioning /// private static void AddParameterDataPartitioning(this DatabaseFacade db) { + var dayCount = 1; + var sectionCount = 128; var type = typeof(ParameterData); var tableAttribute = type.GetCustomAttribute(); if (tableAttribute is null) @@ -32,13 +34,16 @@ public static class EFExtensionsPartitioning return; } - const int sectionsNumber = 2; - const int chunkTimeInterval = 5; - var sqlString = $"SELECT create_hypertable('{tableAttribute.Name}'," + - $"'{nameof(ParameterData.Timestamp)}'," + - $"'{nameof(ParameterData.ParameterId)}'," + - $"{sectionsNumber}," + - $"chunk_time_interval => INTERVAL '{chunkTimeInterval} day');"; - db.ExecuteSqlRaw(sqlString); + var sqlCreateHypertableString = $"SELECT create_hypertable('{tableAttribute.Name}'," + + $"by_range('{nameof(ParameterData.Timestamp)}', INTERVAL '{dayCount} day'), if_not_exists => {true});"; + db.ExecuteSqlRaw(sqlCreateHypertableString); + + var sqlCreateDimensionParameterId = $"SELECT add_dimension('{tableAttribute.Name}'," + + $"by_hash('{nameof(ParameterData.ParameterId)}', {sectionCount}), if_not_exists => {true});"; + db.ExecuteSqlRaw(sqlCreateDimensionParameterId); + + var sqlCreateDimensionDiscriminatorId = $"SELECT add_dimension('{tableAttribute.Name}'," + + $"by_hash('{nameof(ParameterData.DiscriminatorId)}', {sectionCount}), if_not_exists => {true});"; + db.ExecuteSqlRaw(sqlCreateDimensionDiscriminatorId); } } diff --git a/DD.Persistence.Database.Postgres/Migrations/20250122120353_Init.Designer.cs b/DD.Persistence.Database.Postgres/Migrations/20250203061429_Init.Designer.cs similarity index 91% rename from DD.Persistence.Database.Postgres/Migrations/20250122120353_Init.Designer.cs rename to DD.Persistence.Database.Postgres/Migrations/20250203061429_Init.Designer.cs index 81695fb..bdeaf87 100644 --- a/DD.Persistence.Database.Postgres/Migrations/20250122120353_Init.Designer.cs +++ b/DD.Persistence.Database.Postgres/Migrations/20250203061429_Init.Designer.cs @@ -13,7 +13,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace DD.Persistence.Database.Postgres.Migrations { [DbContext(typeof(PersistencePostgresContext))] - [Migration("20250122120353_Init")] + [Migration("20250203061429_Init")] partial class Init { /// @@ -26,6 +26,47 @@ namespace DD.Persistence.Database.Postgres.Migrations NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("DD.Persistence.Database.Entity.ChangeLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasComment("Ключ записи"); + + b.Property("Creation") + .HasColumnType("timestamp with time zone") + .HasComment("Дата создания записи"); + + b.Property("IdAuthor") + .HasColumnType("uuid") + .HasComment("Автор изменения"); + + b.Property("IdDiscriminator") + .HasColumnType("uuid") + .HasComment("Дискриминатор таблицы"); + + b.Property("IdEditor") + .HasColumnType("uuid") + .HasComment("Редактор"); + + b.Property("IdNext") + .HasColumnType("uuid") + .HasComment("Id заменяющей записи"); + + b.Property("Obsolete") + .HasColumnType("timestamp with time zone") + .HasComment("Дата устаревания (например при удалении)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("jsonb") + .HasComment("Значение"); + + b.HasKey("Id"); + + b.ToTable("change_log"); + }); + modelBuilder.Entity("DD.Persistence.Database.Entity.DataScheme", b => { b.Property("DiscriminatorId") @@ -88,6 +129,29 @@ namespace DD.Persistence.Database.Postgres.Migrations b.ToTable("parameter_data"); }); + modelBuilder.Entity("DD.Persistence.Database.Entity.Setpoint", b => + { + b.Property("Key") + .HasColumnType("uuid") + .HasComment("Ключ"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone") + .HasComment("Дата создания уставки"); + + b.Property("IdUser") + .HasColumnType("uuid") + .HasComment("Id автора последнего изменения"); + + b.Property("Value") + .HasColumnType("jsonb") + .HasComment("Значение уставки"); + + b.HasKey("Key", "Timestamp"); + + b.ToTable("setpoint"); + }); + modelBuilder.Entity("DD.Persistence.Database.Entity.TechMessage", b => { b.Property("EventId") @@ -143,82 +207,6 @@ namespace DD.Persistence.Database.Postgres.Migrations b.ToTable("timestamped_values"); }); - modelBuilder.Entity("DD.Persistence.Database.Model.ChangeLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasComment("Ключ записи"); - - b.Property("Creation") - .HasColumnType("timestamp with time zone") - .HasComment("Дата создания записи"); - - b.Property("DepthEnd") - .HasColumnType("double precision") - .HasComment("Глубина забоя на дату окончания интервала"); - - b.Property("DepthStart") - .HasColumnType("double precision") - .HasComment("Глубина забоя на дату начала интервала"); - - b.Property("IdAuthor") - .HasColumnType("uuid") - .HasComment("Автор изменения"); - - b.Property("IdDiscriminator") - .HasColumnType("uuid") - .HasComment("Дискриминатор таблицы"); - - b.Property("IdEditor") - .HasColumnType("uuid") - .HasComment("Редактор"); - - b.Property("IdNext") - .HasColumnType("uuid") - .HasComment("Id заменяющей записи"); - - b.Property("IdSection") - .HasColumnType("uuid") - .HasComment("Ключ секции"); - - b.Property("Obsolete") - .HasColumnType("timestamp with time zone") - .HasComment("Дата устаревания (например при удалении)"); - - b.Property("Value") - .IsRequired() - .HasColumnType("jsonb") - .HasComment("Значение"); - - b.HasKey("Id"); - - b.ToTable("change_log"); - }); - - modelBuilder.Entity("DD.Persistence.Database.Model.Setpoint", b => - { - b.Property("Key") - .HasColumnType("uuid") - .HasComment("Ключ"); - - b.Property("Timestamp") - .HasColumnType("timestamp with time zone") - .HasComment("Дата создания уставки"); - - b.Property("IdUser") - .HasColumnType("uuid") - .HasComment("Id автора последнего изменения"); - - b.Property("Value") - .HasColumnType("jsonb") - .HasComment("Значение уставки"); - - b.HasKey("Key", "Timestamp"); - - b.ToTable("setpoint"); - }); - modelBuilder.Entity("DD.Persistence.Database.Entity.TechMessage", b => { b.HasOne("DD.Persistence.Database.Entity.DataSourceSystem", "System") diff --git a/DD.Persistence.Database.Postgres/Migrations/20250122120353_Init.cs b/DD.Persistence.Database.Postgres/Migrations/20250203061429_Init.cs similarity index 94% rename from DD.Persistence.Database.Postgres/Migrations/20250122120353_Init.cs rename to DD.Persistence.Database.Postgres/Migrations/20250203061429_Init.cs index 6f56873..df996bc 100644 --- a/DD.Persistence.Database.Postgres/Migrations/20250122120353_Init.cs +++ b/DD.Persistence.Database.Postgres/Migrations/20250203061429_Init.cs @@ -23,9 +23,6 @@ namespace DD.Persistence.Database.Postgres.Migrations Creation = table.Column(type: "timestamp with time zone", nullable: false, comment: "Дата создания записи"), Obsolete = table.Column(type: "timestamp with time zone", nullable: true, comment: "Дата устаревания (например при удалении)"), IdNext = table.Column(type: "uuid", nullable: true, comment: "Id заменяющей записи"), - DepthStart = table.Column(type: "double precision", nullable: false, comment: "Глубина забоя на дату начала интервала"), - DepthEnd = table.Column(type: "double precision", nullable: false, comment: "Глубина забоя на дату окончания интервала"), - IdSection = table.Column(type: "uuid", nullable: false, comment: "Ключ секции"), Value = table.Column(type: "jsonb", nullable: false, comment: "Значение") }, constraints: table => diff --git a/DD.Persistence.Database.Postgres/Migrations/PersistencePostgresContextModelSnapshot.cs b/DD.Persistence.Database.Postgres/Migrations/PersistencePostgresContextModelSnapshot.cs index 3db9dda..5c5ad2e 100644 --- a/DD.Persistence.Database.Postgres/Migrations/PersistencePostgresContextModelSnapshot.cs +++ b/DD.Persistence.Database.Postgres/Migrations/PersistencePostgresContextModelSnapshot.cs @@ -23,6 +23,47 @@ namespace DD.Persistence.Database.Postgres.Migrations NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("DD.Persistence.Database.Entity.ChangeLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasComment("Ключ записи"); + + b.Property("Creation") + .HasColumnType("timestamp with time zone") + .HasComment("Дата создания записи"); + + b.Property("IdAuthor") + .HasColumnType("uuid") + .HasComment("Автор изменения"); + + b.Property("IdDiscriminator") + .HasColumnType("uuid") + .HasComment("Дискриминатор таблицы"); + + b.Property("IdEditor") + .HasColumnType("uuid") + .HasComment("Редактор"); + + b.Property("IdNext") + .HasColumnType("uuid") + .HasComment("Id заменяющей записи"); + + b.Property("Obsolete") + .HasColumnType("timestamp with time zone") + .HasComment("Дата устаревания (например при удалении)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("jsonb") + .HasComment("Значение"); + + b.HasKey("Id"); + + b.ToTable("change_log"); + }); + modelBuilder.Entity("DD.Persistence.Database.Entity.DataScheme", b => { b.Property("DiscriminatorId") @@ -85,6 +126,29 @@ namespace DD.Persistence.Database.Postgres.Migrations b.ToTable("parameter_data"); }); + modelBuilder.Entity("DD.Persistence.Database.Entity.Setpoint", b => + { + b.Property("Key") + .HasColumnType("uuid") + .HasComment("Ключ"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone") + .HasComment("Дата создания уставки"); + + b.Property("IdUser") + .HasColumnType("uuid") + .HasComment("Id автора последнего изменения"); + + b.Property("Value") + .HasColumnType("jsonb") + .HasComment("Значение уставки"); + + b.HasKey("Key", "Timestamp"); + + b.ToTable("setpoint"); + }); + modelBuilder.Entity("DD.Persistence.Database.Entity.TechMessage", b => { b.Property("EventId") @@ -140,82 +204,6 @@ namespace DD.Persistence.Database.Postgres.Migrations b.ToTable("timestamped_values"); }); - modelBuilder.Entity("DD.Persistence.Database.Model.ChangeLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasComment("Ключ записи"); - - b.Property("Creation") - .HasColumnType("timestamp with time zone") - .HasComment("Дата создания записи"); - - b.Property("DepthEnd") - .HasColumnType("double precision") - .HasComment("Глубина забоя на дату окончания интервала"); - - b.Property("DepthStart") - .HasColumnType("double precision") - .HasComment("Глубина забоя на дату начала интервала"); - - b.Property("IdAuthor") - .HasColumnType("uuid") - .HasComment("Автор изменения"); - - b.Property("IdDiscriminator") - .HasColumnType("uuid") - .HasComment("Дискриминатор таблицы"); - - b.Property("IdEditor") - .HasColumnType("uuid") - .HasComment("Редактор"); - - b.Property("IdNext") - .HasColumnType("uuid") - .HasComment("Id заменяющей записи"); - - b.Property("IdSection") - .HasColumnType("uuid") - .HasComment("Ключ секции"); - - b.Property("Obsolete") - .HasColumnType("timestamp with time zone") - .HasComment("Дата устаревания (например при удалении)"); - - b.Property("Value") - .IsRequired() - .HasColumnType("jsonb") - .HasComment("Значение"); - - b.HasKey("Id"); - - b.ToTable("change_log"); - }); - - modelBuilder.Entity("DD.Persistence.Database.Model.Setpoint", b => - { - b.Property("Key") - .HasColumnType("uuid") - .HasComment("Ключ"); - - b.Property("Timestamp") - .HasColumnType("timestamp with time zone") - .HasComment("Дата создания уставки"); - - b.Property("IdUser") - .HasColumnType("uuid") - .HasComment("Id автора последнего изменения"); - - b.Property("Value") - .HasColumnType("jsonb") - .HasComment("Значение уставки"); - - b.HasKey("Key", "Timestamp"); - - b.ToTable("setpoint"); - }); - modelBuilder.Entity("DD.Persistence.Database.Entity.TechMessage", b => { b.HasOne("DD.Persistence.Database.Entity.DataSourceSystem", "System") diff --git a/DD.Persistence.Database.Postgres/PersistencePostgresContext.cs b/DD.Persistence.Database.Postgres/PersistencePostgresContext.cs index 6cd8955..3da502f 100644 --- a/DD.Persistence.Database.Postgres/PersistencePostgresContext.cs +++ b/DD.Persistence.Database.Postgres/PersistencePostgresContext.cs @@ -1,9 +1,9 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; namespace DD.Persistence.Database.Model; /// -/// EF Postgres +/// EF контекст для базы данных Postgres /// public partial class PersistencePostgresContext : PersistenceDbContext { diff --git a/DD.Persistence.sln b/DD.Persistence.sln index 926e688..ca91e47 100644 --- a/DD.Persistence.sln +++ b/DD.Persistence.sln @@ -35,6 +35,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Элементы решен .gitea\workflows\integrationTests.yaml = .gitea\workflows\integrationTests.yaml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DD.Persistence.Database.Postgres.Test", "DD.Persistence.Database.Postgres.Test\DD.Persistence.Database.Postgres.Test.csproj", "{47142566-9EAB-4FB5-92EC-9DCB02CAC890}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -85,6 +87,10 @@ Global {B8C774E6-6B75-41AC-B3CF-10BD3623B2FA}.Debug|Any CPU.Build.0 = Debug|Any CPU {B8C774E6-6B75-41AC-B3CF-10BD3623B2FA}.Release|Any CPU.ActiveCfg = Release|Any CPU {B8C774E6-6B75-41AC-B3CF-10BD3623B2FA}.Release|Any CPU.Build.0 = Release|Any CPU + {47142566-9EAB-4FB5-92EC-9DCB02CAC890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47142566-9EAB-4FB5-92EC-9DCB02CAC890}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47142566-9EAB-4FB5-92EC-9DCB02CAC890}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47142566-9EAB-4FB5-92EC-9DCB02CAC890}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE