using System.Data; using System.Data.OleDb; using System.Runtime.Versioning; namespace Campaign_Tracker.Server.LegacyData.Schema; [SupportedOSPlatform("windows")] public sealed class OleDbLegacySchemaInspector : ILegacySchemaInspector { private readonly string _connectionString; public OleDbLegacySchemaInspector(string connectionString) { if (string.IsNullOrWhiteSpace(connectionString)) { throw new ArgumentException("Legacy database connection string is required.", nameof(connectionString)); } _connectionString = connectionString; } public async Task> GetCurrentSchemaAsync( CancellationToken cancellationToken = default) { await using var connection = new OleDbConnection(_connectionString); await connection.OpenAsync(cancellationToken); var schema = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Columns, null) ?? throw new InvalidOperationException("OleDb provider did not return column schema metadata."); var tables = schema.Rows .Cast() .GroupBy(row => GetRequiredString(row, "TABLE_NAME"), StringComparer.OrdinalIgnoreCase) .OrderBy(group => group.Key, StringComparer.OrdinalIgnoreCase) .Select(group => new LegacyTableDefinition( group.Key, group .OrderBy(row => GetOrdinal(row)) .Select(row => new LegacyColumnDefinition( GetRequiredString(row, "COLUMN_NAME"), GetRequiredInt(row, "DATA_TYPE"), GetNullableInt(row, "CHARACTER_MAXIMUM_LENGTH"), GetNullableBool(row, "IS_NULLABLE") ?? true) { Constraints = GetColumnConstraints(row), }) .ToArray())) .ToArray(); return tables; } private static int GetOrdinal(DataRow row) => GetNullableInt(row, "ORDINAL_POSITION") ?? int.MaxValue; private static IReadOnlyList GetColumnConstraints(DataRow row) { var constraints = new List(); return constraints; } private static string GetRequiredString(DataRow row, string columnName) { var value = row.Table.Columns.Contains(columnName) ? row[columnName] : null; var text = value is null or DBNull ? null : Convert.ToString(value); return string.IsNullOrWhiteSpace(text) ? throw new InvalidOperationException($"OleDb schema row is missing required field {columnName}.") : text; } private static int GetRequiredInt(DataRow row, string columnName) => GetNullableInt(row, columnName) ?? throw new InvalidOperationException($"OleDb schema row is missing required field {columnName}."); private static int? GetNullableInt(DataRow row, string columnName) { if (!row.Table.Columns.Contains(columnName) || row[columnName] is DBNull) { return null; } return Convert.ToInt32(row[columnName]); } private static bool? GetNullableBool(DataRow row, string columnName) { if (!row.Table.Columns.Contains(columnName) || row[columnName] is DBNull) { return null; } var value = row[columnName]; if (value is bool flag) { return flag; } var text = Convert.ToString(value); if (string.Equals(text, "YES", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "TRUE", StringComparison.OrdinalIgnoreCase)) { return true; } if (string.Equals(text, "NO", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "FALSE", StringComparison.OrdinalIgnoreCase)) { return false; } return null; } }