Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

147 Zeilen
6.3KB

  1. namespace Campaign_Tracker.Server.LegacyData.Schema;
  2. /// <summary>
  3. /// Default <see cref="ILegacySchemaCompatibilityCheck"/> implementation.
  4. ///
  5. /// Compares the baseline (captured at initialization) against the current
  6. /// schema returned by the inspector. Reports drift entries for:
  7. /// - tables present in baseline but missing in current (TableMissing)
  8. /// - tables present in current but missing in baseline (TableAdded)
  9. /// - columns present in baseline but missing in current (ColumnMissing)
  10. /// - columns present in current but missing in baseline (ColumnAdded)
  11. /// - columns present in both with different type/size/nullability
  12. /// Comparison is case-insensitive on table and column names to match Access
  13. /// behavior.
  14. /// </summary>
  15. public sealed class LegacySchemaCompatibilityCheck : ILegacySchemaCompatibilityCheck
  16. {
  17. private readonly LegacySchemaBaseline _baseline;
  18. private readonly ILegacySchemaInspector _inspector;
  19. private readonly TimeProvider _timeProvider;
  20. public LegacySchemaCompatibilityCheck(
  21. LegacySchemaBaseline baseline,
  22. ILegacySchemaInspector inspector,
  23. TimeProvider? timeProvider = null)
  24. {
  25. _baseline = baseline ?? throw new ArgumentNullException(nameof(baseline));
  26. _inspector = inspector ?? throw new ArgumentNullException(nameof(inspector));
  27. _timeProvider = timeProvider ?? TimeProvider.System;
  28. }
  29. public async Task<LegacySchemaCheckResult> RunAsync(CancellationToken cancellationToken = default)
  30. {
  31. var live = await _inspector.GetCurrentSchemaAsync(cancellationToken).ConfigureAwait(false);
  32. var drifts = new List<LegacySchemaDrift>();
  33. var baselineByName = _baseline.Tables
  34. .ToDictionary(t => t.Name, StringComparer.OrdinalIgnoreCase);
  35. var liveByName = live
  36. .ToDictionary(t => t.Name, StringComparer.OrdinalIgnoreCase);
  37. foreach (var (name, baselineTable) in baselineByName)
  38. {
  39. if (!liveByName.TryGetValue(name, out var liveTable))
  40. {
  41. drifts.Add(new LegacySchemaDrift(
  42. name, null, LegacySchemaChangeType.TableMissing,
  43. $"Table '{name}' is in the approved baseline but missing from the live database."));
  44. continue;
  45. }
  46. CompareColumns(name, baselineTable.Columns, liveTable.Columns, drifts);
  47. }
  48. foreach (var (name, _) in liveByName)
  49. {
  50. if (!baselineByName.ContainsKey(name))
  51. {
  52. drifts.Add(new LegacySchemaDrift(
  53. name, null, LegacySchemaChangeType.TableAdded,
  54. $"Table '{name}' exists in the live database but is not part of the approved baseline."));
  55. }
  56. }
  57. return new LegacySchemaCheckResult(
  58. Passed: drifts.Count == 0,
  59. TablesVerified: baselineByName.Count,
  60. DriftCount: drifts.Count,
  61. CheckedAt: _timeProvider.GetUtcNow(),
  62. Drifts: drifts,
  63. BaselineSource: _baseline.Source);
  64. }
  65. private static void CompareColumns(
  66. string tableName,
  67. IReadOnlyList<LegacyColumnDefinition> baseline,
  68. IReadOnlyList<LegacyColumnDefinition> live,
  69. List<LegacySchemaDrift> sink)
  70. {
  71. var baselineByName = baseline.ToDictionary(c => c.Name, StringComparer.OrdinalIgnoreCase);
  72. var liveByName = live.ToDictionary(c => c.Name, StringComparer.OrdinalIgnoreCase);
  73. foreach (var (name, baseCol) in baselineByName)
  74. {
  75. if (!liveByName.TryGetValue(name, out var liveCol))
  76. {
  77. sink.Add(new LegacySchemaDrift(
  78. tableName, name, LegacySchemaChangeType.ColumnMissing,
  79. $"Column '{tableName}.{name}' is in the approved baseline but missing from the live database."));
  80. continue;
  81. }
  82. if (baseCol.TypeCode != liveCol.TypeCode)
  83. {
  84. sink.Add(new LegacySchemaDrift(
  85. tableName, name, LegacySchemaChangeType.ColumnTypeChanged,
  86. $"Column '{tableName}.{name}' type changed: baseline={baseCol.TypeCode}, live={liveCol.TypeCode}."));
  87. }
  88. if (baseCol.Size != liveCol.Size)
  89. {
  90. sink.Add(new LegacySchemaDrift(
  91. tableName, name, LegacySchemaChangeType.ColumnSizeChanged,
  92. $"Column '{tableName}.{name}' size changed: baseline={Format(baseCol.Size)}, live={Format(liveCol.Size)}."));
  93. }
  94. if (baseCol.Nullable != liveCol.Nullable)
  95. {
  96. sink.Add(new LegacySchemaDrift(
  97. tableName, name, LegacySchemaChangeType.ColumnNullabilityChanged,
  98. $"Column '{tableName}.{name}' nullability changed: baseline={baseCol.Nullable}, live={liveCol.Nullable}."));
  99. }
  100. var baselineConstraints = NormalizeConstraints(baseCol.Constraints);
  101. var liveConstraints = NormalizeConstraints(liveCol.Constraints);
  102. if (!baselineConstraints.SequenceEqual(liveConstraints, StringComparer.OrdinalIgnoreCase))
  103. {
  104. sink.Add(new LegacySchemaDrift(
  105. tableName, name, LegacySchemaChangeType.ColumnConstraintsChanged,
  106. $"Column '{tableName}.{name}' constraints changed: baseline={FormatConstraints(baselineConstraints)}, live={FormatConstraints(liveConstraints)}."));
  107. }
  108. }
  109. foreach (var (name, _) in liveByName)
  110. {
  111. if (!baselineByName.ContainsKey(name))
  112. {
  113. sink.Add(new LegacySchemaDrift(
  114. tableName, name, LegacySchemaChangeType.ColumnAdded,
  115. $"Column '{tableName}.{name}' exists in the live database but is not part of the approved baseline."));
  116. }
  117. }
  118. }
  119. private static string Format(int? value) => value?.ToString() ?? "(none)";
  120. private static IReadOnlyList<string> NormalizeConstraints(IReadOnlyList<string> constraints) =>
  121. constraints
  122. .Where(c => !string.IsNullOrWhiteSpace(c))
  123. .Select(c => c.Trim())
  124. .Order(StringComparer.OrdinalIgnoreCase)
  125. .ToArray();
  126. private static string FormatConstraints(IReadOnlyList<string> constraints) =>
  127. constraints.Count == 0 ? "(none)" : string.Join(",", constraints);
  128. }

Powered by TurnKey Linux.