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(