From 78a532ae41a281b41c90195d5105d9d4ddbf3343 Mon Sep 17 00:00:00 2001 From: windpacer Date: Thu, 4 Jun 2026 09:43:48 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20hc900=5Fmap=5Fmaster=20UNIQUE=20?= =?UTF-8?q?=EC=9D=B8=EB=8D=B1=EC=8A=A4=20=EB=B3=80=EA=B2=BD=20+=20FF/Strea?= =?UTF-8?q?m=20=EC=BB=AC=EB=9F=BC=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - hc900_map_master: UNIQUE(tagname) → UNIQUE(controller_id, tagname) - 동일 태그명이 여러 컨트롤러에 존재 가능 (peer comms) - ff_column_config: ALTER TABLE ADD COLUMN IF NOT EXISTS 10개 누락 컬럼 (feed_tag, pressure_tag, level_tags, feed_filter_tau_sec 등) - ff_stream_config: stream_key → key RENAME COLUMN + 12개 컬럼 추가 (flow_tag, role, target_coeff, theta_up_sec, theta_dn_sec, tau_sec, sp_min, sp_max, rate_up_per_min, rate_dn_per_min, reflux_from_product, grade) - EF Core HasIndex(tagname).IsUnique() → HasIndex(controller_id, tagname).IsUnique() - GetSubArea/UpdateSubArea/DeletePoint: ToLowerInvariant 제거 → OrdinalIgnoreCase 비교로 대체 --- src/Infrastructure/Database/Hc900DbContext.cs | 66 +++++++++++++++++-- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/src/Infrastructure/Database/Hc900DbContext.cs b/src/Infrastructure/Database/Hc900DbContext.cs index 288ad95..c88f15a 100644 --- a/src/Infrastructure/Database/Hc900DbContext.cs +++ b/src/Infrastructure/Database/Hc900DbContext.cs @@ -44,7 +44,7 @@ public class Hc900DbContext : DbContext modelBuilder.Entity(e => { e.HasKey(x => x.Id); - e.HasIndex(x => x.TagName).IsUnique(); + e.HasIndex(x => new { x.ControllerId, x.TagName }).IsUnique(); e.HasIndex(x => x.Hc900Tag); }); @@ -461,6 +461,30 @@ public class Hc900DbService : IExperionDbService await _ctx.Database.ExecuteSqlRawAsync( "ALTER TABLE hc900_map_master ADD COLUMN IF NOT EXISTS controller_id TEXT NOT NULL DEFAULT 'HC1'"); + // hc900_map_master: tagname is unique PER controller, not globally — the same + // SignalTag name can exist on several controllers (peer comms). Replace the old + // UNIQUE(tagname) with UNIQUE(controller_id, tagname). + await _ctx.Database.ExecuteSqlRawAsync(""" + DO $$ + BEGIN + IF EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conrelid = 'hc900_map_master'::regclass + AND conname = 'hc900_map_master_tagname_key' + ) THEN + ALTER TABLE hc900_map_master DROP CONSTRAINT hc900_map_master_tagname_key; + END IF; + IF NOT EXISTS ( + SELECT 1 FROM pg_indexes + WHERE tablename = 'hc900_map_master' + AND indexname = 'ux_hc900_map_ctrl_tag' + ) THEN + CREATE UNIQUE INDEX ux_hc900_map_ctrl_tag + ON hc900_map_master(controller_id, tagname); + END IF; + END $$; + """); + // realtime_table: UNIQUE(controller_id, tagname) for ON CONFLICT upsert await _ctx.Database.ExecuteSqlRawAsync(""" DO $$ @@ -1155,6 +1179,36 @@ public class Hc900DbService : IExperionDbService ALTER TABLE ff_column_config ADD COLUMN IF NOT EXISTS delta_p_tag TEXT; ALTER TABLE ff_column_config ADD COLUMN IF NOT EXISTS delta_p_flood_limit DOUBLE PRECISION NOT NULL DEFAULT 1e9; ALTER TABLE ff_column_config ADD COLUMN IF NOT EXISTS temp_high_limit DOUBLE PRECISION NOT NULL DEFAULT 1e9; + -- migration: missing columns from the original CREATE TABLE (schema was json-based) + ALTER TABLE ff_column_config ADD COLUMN IF NOT EXISTS feed_tag TEXT NOT NULL DEFAULT ''; + ALTER TABLE ff_column_config ADD COLUMN IF NOT EXISTS pressure_tag TEXT; + ALTER TABLE ff_column_config ADD COLUMN IF NOT EXISTS level_tags TEXT; + ALTER TABLE ff_column_config ADD COLUMN IF NOT EXISTS feed_filter_tau_sec DOUBLE PRECISION NOT NULL DEFAULT 300; + ALTER TABLE ff_column_config ADD COLUMN IF NOT EXISTS feed_move_thr_per_min DOUBLE PRECISION NOT NULL DEFAULT 0; + ALTER TABLE ff_column_config ADD COLUMN IF NOT EXISTS press_filter_tau_sec DOUBLE PRECISION NOT NULL DEFAULT 60; + ALTER TABLE ff_column_config ADD COLUMN IF NOT EXISTS pressure_band DOUBLE PRECISION NOT NULL DEFAULT 1e9; + ALTER TABLE ff_column_config ADD COLUMN IF NOT EXISTS settle_sec DOUBLE PRECISION NOT NULL DEFAULT 0; + ALTER TABLE ff_column_config ADD COLUMN IF NOT EXISTS stale_sec DOUBLE PRECISION NOT NULL DEFAULT 120; + ALTER TABLE ff_column_config ADD COLUMN IF NOT EXISTS product_key TEXT NOT NULL DEFAULT 'P'; + DO $$ + BEGIN + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='hc900' AND table_name='ff_stream_config' AND column_name='stream_key') + AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='hc900' AND table_name='ff_stream_config' AND column_name='key') THEN + ALTER TABLE hc900.ff_stream_config RENAME COLUMN stream_key TO key; + END IF; + END $$; + ALTER TABLE ff_stream_config ADD COLUMN IF NOT EXISTS flow_tag TEXT NOT NULL DEFAULT ''; + ALTER TABLE ff_stream_config ADD COLUMN IF NOT EXISTS role TEXT NOT NULL DEFAULT 'Monitor'; + ALTER TABLE ff_stream_config ADD COLUMN IF NOT EXISTS target_coeff DOUBLE PRECISION NOT NULL DEFAULT 0; + ALTER TABLE ff_stream_config ADD COLUMN IF NOT EXISTS theta_up_sec DOUBLE PRECISION NOT NULL DEFAULT 0; + ALTER TABLE ff_stream_config ADD COLUMN IF NOT EXISTS theta_dn_sec DOUBLE PRECISION NOT NULL DEFAULT 0; + ALTER TABLE ff_stream_config ADD COLUMN IF NOT EXISTS tau_sec DOUBLE PRECISION NOT NULL DEFAULT 0; + ALTER TABLE ff_stream_config ADD COLUMN IF NOT EXISTS sp_min DOUBLE PRECISION NOT NULL DEFAULT 0; + ALTER TABLE ff_stream_config ADD COLUMN IF NOT EXISTS sp_max DOUBLE PRECISION NOT NULL DEFAULT 1e9; + ALTER TABLE ff_stream_config ADD COLUMN IF NOT EXISTS rate_up_per_min DOUBLE PRECISION NOT NULL DEFAULT 1e9; + ALTER TABLE ff_stream_config ADD COLUMN IF NOT EXISTS rate_dn_per_min DOUBLE PRECISION NOT NULL DEFAULT 1e9; + ALTER TABLE ff_stream_config ADD COLUMN IF NOT EXISTS reflux_from_product BOOLEAN NOT NULL DEFAULT FALSE; + ALTER TABLE ff_stream_config ADD COLUMN IF NOT EXISTS grade TEXT NOT NULL DEFAULT 'A'; """); // ── FF operator action audit log ──────────────────────────────── @@ -1414,9 +1468,8 @@ public class Hc900DbService : IExperionDbService var point = await _ctx.RealtimePoints.FindAsync(id); if (point == null) return new PointDeleteResult { Deleted = false }; - var tagName = point.TagName; // 예: "fi-6101.pv" - var baseTag = (tagName.Contains('.') ? tagName[..tagName.IndexOf('.')] : tagName) - .ToLowerInvariant(); // "fi-6101" + var tagName = point.TagName; // 예: "FICQ-6101.pv" + var baseTag = tagName.Contains('.') ? tagName[..tagName.IndexOf('.')] : tagName; _ctx.RealtimePoints.Remove(point); await _ctx.SaveChangesAsync(); @@ -1924,9 +1977,9 @@ public class Hc900DbService : IExperionDbService public async Task GetSubAreaByTagNameAsync(string tagName) { - var baseTag = (tagName.Contains('.') ? tagName[..tagName.LastIndexOf('.')] : tagName).ToLowerInvariant(); + var baseTag = tagName.Contains('.') ? tagName[..tagName.LastIndexOf('.')] : tagName; return await _ctx.TagMetadata - .Where(m => m.BaseTag == baseTag && m.Attribute == "sub_area") + .Where(m => m.BaseTag.Equals(baseTag, StringComparison.OrdinalIgnoreCase) && m.Attribute == "sub_area") .Select(m => m.Value) .FirstOrDefaultAsync(); } @@ -1991,7 +2044,6 @@ public class Hc900DbService : IExperionDbService public async Task UpdateSubAreaAsync(string baseTag, string? subArea) { - baseTag = baseTag.ToLowerInvariant(); if (string.IsNullOrWhiteSpace(subArea)) { var deleted = await _ctx.Database.ExecuteSqlRawAsync(