Premiere commit - Omika

This commit is contained in:
2026-10-23 18:46:50 +02:00
commit 8ea76c1b24
2155 changed files with 984301 additions and 0 deletions
+784
View File
@@ -0,0 +1,784 @@
using System.Data.OleDb;
using System.Reflection;
using System.Text.Json;
using System.Text.RegularExpressions;
using Omika.Data.HfSql.Protection;
namespace HfSqlOleDbAudit;
internal static class Program
{
private static int Main(string[] args)
{
// Mode "connection-test" (lecture seule, OLE DB PCSOFT.HFSQL). Dispatch
// ajouté sans modifier le comportement historique ci-dessous : tout autre
// premier argument emprunte exactement le chemin d'origine.
if (args.Length > 0
&& StringComparer.OrdinalIgnoreCase.Equals(args[0], ConnectionTest.CommandName))
{
return ConnectionTest.Run(args);
}
if (args.Length < 2)
{
Console.WriteLine("Usage: HfSqlOleDbAudit <configPath> <Batik|Omika>");
return 1;
}
string configPath = args[0];
string schema = args[1];
// ================================================================
// CONFIG LOADING
// ================================================================
TestConfig? config;
try
{
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
config = JsonSerializer.Deserialize<TestConfig>(File.ReadAllText(configPath), options);
}
catch
{
config = null;
}
if (config is null)
{
Console.WriteLine("Config JSON: INVALID");
return 1;
}
Console.WriteLine("Config JSON: VALID");
if (string.IsNullOrWhiteSpace(config.ProtectionPlusSecret))
{
Console.WriteLine("ProtectionPlusSecret: MISSING");
return 1;
}
Console.WriteLine("ProtectionPlusSecret: AVAILABLE");
// ================================================================
// DATABASE CHOICE
// ================================================================
DatabaseConfig db = schema.Equals("Batik", StringComparison.OrdinalIgnoreCase)
? config.Batik
: config.Omika;
string connectionDatabase = db.Database;
string passwordDatabaseName = db.OriginalDatabaseName ?? db.Database;
Console.WriteLine("PasswordDatabaseNameSource: " +
(db.OriginalDatabaseName is null ? "CURRENT" : "ORIGINAL"));
// ================================================================
// EXPLICIT LOCAL CONFIG — file passwords come from the local config,
// NOT derived. No derivation, no wildcard.
// ================================================================
string protectionStd = config.FilePasswords.ProtectionStd;
string protectionPlus = config.FilePasswords.ProtectionPlus;
Console.WriteLine("PasswordSource: EXPLICIT_LOCAL_CONFIG");
Console.WriteLine("PasswordDerivationUsed: NO");
Console.WriteLine("ProtectionPlusSecretUsedForConnection: NO");
// ================================================================
// LOAD TABLES
// ================================================================
List<TableInfo> tables = LoadTables(schema);
var results = new Dictionary<string, TableResult>(StringComparer.OrdinalIgnoreCase);
foreach (var t in tables)
{
results[t.LogicalName] = new TableResult { Table = t };
}
// ================================================================
// PHASE 1 — DIRECT
// ================================================================
using (var cn = new OleDbConnection(BuildConnectionString(config, db, null)))
{
cn.Open();
foreach (var t in tables)
{
var r = results[t.LogicalName];
QueryResult qr = TrySelect(cn, t.LogicalName);
if (qr.Success)
{
r.DirectAccess = true;
r.FinalAccess = FinalAccess.Direct;
r.FieldCount = qr.FieldCount;
r.HasRows = qr.HasRows;
}
else
{
r.DirectAccess = false;
r.FinalAccess = FinalAccess.Unknown;
r.HfSqlErrorCode = qr.HfSqlErrorCode;
}
}
}
// ================================================================
// PHASE 2 — FAILURES-ONLY EXPLICIT MAP
// ================================================================
string strategy = "FAILURES_ONLY_EXPLICIT_MAP";
List<string>? workingEntries = null;
var failedProtected = tables
.Where(t => !results[t.LogicalName].DirectAccess
&& (t.ExpectedGroup == ExpectedGroup.ProtectionStd
|| t.ExpectedGroup == ExpectedGroup.ProtectionPlus))
.ToList();
if (failedProtected.Count > 0)
{
var entries = new List<string>();
foreach (var t in failedProtected)
{
if (t.ExpectedGroup == ExpectedGroup.ProtectionStd)
entries.Add(BuildFilePasswordEntry(t.LogicalName, protectionStd));
else
entries.Add(BuildFilePasswordEntry(t.LogicalName, protectionPlus));
}
string ext = string.Join(";", entries);
using (var cn = new OleDbConnection(BuildConnectionString(config, db, ext)))
{
cn.Open();
foreach (var t in failedProtected)
{
var r = results[t.LogicalName];
QueryResult qr = TrySelect(cn, t.LogicalName);
if (qr.Success)
{
r.ProtectedAccess = true;
r.FinalAccess = t.ExpectedGroup == ExpectedGroup.ProtectionStd
? FinalAccess.ProtectionStd
: FinalAccess.ProtectionPlus;
r.FieldCount = qr.FieldCount;
r.HasRows = qr.HasRows;
}
else
{
r.HfSqlErrorCode = qr.HfSqlErrorCode;
}
}
}
workingEntries = entries;
}
// ================================================================
// CANARY CHECK — if ParametreMetier passes, STD is verified.
// ================================================================
bool stdVerified = false;
bool plusVerified = false;
if (results.TryGetValue("ParametreMetier", out var pmFinal)
&& !pmFinal.DirectAccess && pmFinal.ProtectedAccess)
{
stdVerified = true;
}
if ((results.TryGetValue("ParametreMetierEntete", out var pmeFinal)
&& !pmeFinal.DirectAccess && pmeFinal.ProtectedAccess)
|| (results.TryGetValue("ParametreSysteme", out var psFinal)
&& !psFinal.DirectAccess && psFinal.ProtectedAccess))
{
plusVerified = true;
}
// ================================================================
// STRATEGY 2 — FULL EXPLICIT MAP (if ParametreMetier still failed)
// ================================================================
bool pmStillFailed = results.TryGetValue("ParametreMetier", out var pmCheck)
&& pmCheck.FinalAccess != FinalAccess.Direct
&& pmCheck.FinalAccess != FinalAccess.ProtectionStd;
if (pmStillFailed)
{
strategy = "FULL_EXPLICIT_MAP";
var entries = new List<string>();
foreach (var t in tables)
{
if (t.ExpectedGroup == ExpectedGroup.ProtectionStd)
entries.Add(BuildFilePasswordEntry(t.LogicalName, protectionStd));
else if (t.ExpectedGroup == ExpectedGroup.ProtectionPlus)
entries.Add(BuildFilePasswordEntry(t.LogicalName, protectionPlus));
}
string ext = string.Join(";", entries);
using (var cn = new OleDbConnection(BuildConnectionString(config, db, ext)))
{
cn.Open();
foreach (var name in new[] { "Client", "ParametreMetier", "ParametreMetierEntete", "ParametreSysteme" })
{
if (results.TryGetValue(name, out var r) && !r.DirectAccess)
{
QueryResult qr = TrySelect(cn, name);
if (qr.Success)
{
r.ProtectedAccess = true;
r.FinalAccess = r.Table.ExpectedGroup == ExpectedGroup.ProtectionStd
? FinalAccess.ProtectionStd
: FinalAccess.ProtectionPlus;
r.FieldCount = qr.FieldCount;
r.HasRows = qr.HasRows;
}
else
{
r.HfSqlErrorCode = qr.HfSqlErrorCode;
}
}
}
foreach (var t in tables.Where(t => !results[t.LogicalName].DirectAccess
&& results[t.LogicalName].FinalAccess == FinalAccess.Unknown))
{
var r = results[t.LogicalName];
QueryResult qr = TrySelect(cn, t.LogicalName);
if (qr.Success)
{
r.ProtectedAccess = true;
r.FinalAccess = t.ExpectedGroup == ExpectedGroup.ProtectionStd
? FinalAccess.ProtectionStd
: FinalAccess.ProtectionPlus;
r.FieldCount = qr.FieldCount;
r.HasRows = qr.HasRows;
}
else
{
r.HfSqlErrorCode = qr.HfSqlErrorCode;
}
}
}
workingEntries = entries;
// Re-evaluate canaries after strategy 2
if (results.TryGetValue("ParametreMetier", out var pm2)
&& !pm2.DirectAccess && pm2.ProtectedAccess)
{
stdVerified = true;
}
if ((results.TryGetValue("ParametreMetierEntete", out var pme2)
&& !pme2.DirectAccess && pme2.ProtectedAccess)
|| (results.TryGetValue("ParametreSysteme", out var ps2)
&& !ps2.DirectAccess && ps2.ProtectedAccess))
{
plusVerified = true;
}
}
// ================================================================
// STRATEGY 3 — WILDCARD_PLUS_OVERRIDES (if ParametreMetier still failed)
// ================================================================
bool pmStillFailed2 = results.TryGetValue("ParametreMetier", out var pmCheck2)
&& pmCheck2.FinalAccess != FinalAccess.Direct
&& pmCheck2.FinalAccess != FinalAccess.ProtectionStd;
if (pmStillFailed2)
{
strategy = "WILDCARD_PLUS_OVERRIDES";
var entries = new List<string>();
// Wildcard first: applies ProtectionStd to all files.
entries.Add(BuildFilePasswordEntry("*", protectionStd));
// Then explicit overrides for each ProtectionPlus file.
foreach (var t in tables.Where(t => t.ExpectedGroup == ExpectedGroup.ProtectionPlus))
{
entries.Add(BuildFilePasswordEntry(t.LogicalName, protectionPlus));
}
string ext = string.Join(";", entries);
using (var cn = new OleDbConnection(BuildConnectionString(config, db, ext)))
{
cn.Open();
foreach (var name in new[] { "Client", "ParametreMetier", "ParametreMetierEntete", "ParametreSysteme" })
{
if (results.TryGetValue(name, out var r) && !r.DirectAccess)
{
QueryResult qr = TrySelect(cn, name);
if (qr.Success)
{
r.ProtectedAccess = true;
r.FinalAccess = r.Table.ExpectedGroup == ExpectedGroup.ProtectionStd
? FinalAccess.ProtectionStd
: FinalAccess.ProtectionPlus;
r.FieldCount = qr.FieldCount;
r.HasRows = qr.HasRows;
}
else
{
r.HfSqlErrorCode = qr.HfSqlErrorCode;
}
}
}
foreach (var t in tables.Where(t => !results[t.LogicalName].DirectAccess
&& results[t.LogicalName].FinalAccess == FinalAccess.Unknown))
{
var r = results[t.LogicalName];
QueryResult qr = TrySelect(cn, t.LogicalName);
if (qr.Success)
{
r.ProtectedAccess = true;
r.FinalAccess = t.ExpectedGroup == ExpectedGroup.ProtectionStd
? FinalAccess.ProtectionStd
: FinalAccess.ProtectionPlus;
r.FieldCount = qr.FieldCount;
r.HasRows = qr.HasRows;
}
else
{
r.HfSqlErrorCode = qr.HfSqlErrorCode;
}
}
}
workingEntries = entries;
// Re-evaluate canaries after strategy 3
if (results.TryGetValue("ParametreMetier", out var pm3)
&& !pm3.DirectAccess && pm3.ProtectedAccess)
{
stdVerified = true;
}
if ((results.TryGetValue("ParametreMetierEntete", out var pme3)
&& !pme3.DirectAccess && pme3.ProtectedAccess)
|| (results.TryGetValue("ParametreSysteme", out var ps3)
&& !ps3.DirectAccess && ps3.ProtectedAccess))
{
plusVerified = true;
}
}
// ================================================================
// NEGATIVE CONTROL
// ================================================================
bool negativeControlRun = false;
bool negativeControlPass = false;
var protectedPassed = tables.FirstOrDefault(t => results[t.LogicalName].ProtectedAccess);
if (protectedPassed is not null && workingEntries is not null)
{
negativeControlRun = true;
string fakePassword = "00000000000000000000000000000000";
string chosenName = protectedPassed.LogicalName;
var fakeEntries = new List<string>(workingEntries);
string escapedChosen = EscapeExtendedPropertyValue(chosenName);
fakeEntries.RemoveAll(e => e.StartsWith("Password=" + escapedChosen + ":"));
fakeEntries.Add(BuildFilePasswordEntry(chosenName, fakePassword));
string fakeExt = string.Join(";", fakeEntries);
using (var cn = new OleDbConnection(BuildConnectionString(config, db, fakeExt)))
{
cn.Open();
QueryResult qr = TrySelect(cn, chosenName);
negativeControlPass = !qr.Success;
}
}
// ================================================================
// FINALIZE — mark any table still unresolved as Failed
// ================================================================
foreach (var t in tables)
{
var r = results[t.LogicalName];
if (r.FinalAccess == FinalAccess.Unknown)
{
r.FinalAccess = FinalAccess.Failed;
}
}
// ================================================================
// CONSOLE OUTPUT
// ================================================================
Console.WriteLine();
Console.WriteLine($"=== {schema} ===");
Console.WriteLine();
foreach (var t in tables)
{
var r = results[t.LogicalName];
Console.WriteLine("LogicalFile: " + t.LogicalName);
Console.WriteLine("ExpectedGroup: " + t.ExpectedGroup);
Console.WriteLine("DirectAccess: " + r.DirectAccess);
Console.WriteLine("ProtectedAccess: " + r.ProtectedAccess);
Console.WriteLine("FinalAccess: " + GetFinalAccessLabel(r.FinalAccess));
Console.WriteLine("FieldCount: " + r.FieldCount);
Console.WriteLine("HasRows: " + r.HasRows);
Console.WriteLine("HfSqlErrorCode: " + (r.HfSqlErrorCode ?? "NONE"));
Console.WriteLine();
}
int directCount = tables.Count(t => results[t.LogicalName].DirectAccess);
int stdCount = tables.Count(t => results[t.LogicalName].FinalAccess == FinalAccess.ProtectionStd);
int plusCount = tables.Count(t => results[t.LogicalName].FinalAccess == FinalAccess.ProtectionPlus);
int failedCount = tables.Count(t => results[t.LogicalName].FinalAccess == FinalAccess.Failed);
Console.WriteLine("=== SUMMARY ===");
Console.WriteLine($"Tables: {tables.Count}");
Console.WriteLine($"Direct: {directCount}");
Console.WriteLine($"ProtectionStd: {stdCount}");
Console.WriteLine($"ProtectionPlus: {plusCount}");
Console.WriteLine($"Failed: {failedCount}");
Console.WriteLine();
Console.WriteLine($"ParametreMetier: {GetCanaryLabel(results, "ParametreMetier")}");
Console.WriteLine($"ParametreMetierEntete: {GetCanaryLabel(results, "ParametreMetierEntete")}");
Console.WriteLine($"ParametreSysteme: {GetCanaryLabel(results, "ParametreSysteme")}");
Console.WriteLine();
Console.WriteLine($"Strategy: {strategy}");
Console.WriteLine($"ProtectionStd OLE DB: {(stdVerified ? "VERIFIED_FROM_DATABASE" : "UNRESOLVED")}");
Console.WriteLine($"ProtectionPlus OLE DB: {(plusVerified ? "VERIFIED_FROM_DATABASE" : "UNRESOLVED")}");
Console.WriteLine($"NegativeControl: {(negativeControlRun ? (negativeControlPass ? "PASS" : "FAIL") : "NOT_RUN")}");
// ================================================================
// CSV REPORT
// ================================================================
string csvPath = Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "..", "docs", "diagnostics", "hfsql-oledb-open-all.csv");
csvPath = Path.GetFullPath(csvPath);
Directory.CreateDirectory(Path.GetDirectoryName(csvPath)!);
bool csvExists = File.Exists(csvPath);
var csvLines = new List<string>();
if (!csvExists)
{
csvLines.Add("Schema,Table,DiskName,ExpectedGroup,DirectAccess,ProtectedAccess,FinalAccess,FieldCount,HasRows,HfSqlErrorCode");
}
foreach (var t in tables)
{
var r = results[t.LogicalName];
csvLines.Add(string.Join(",",
Csv(schema), Csv(t.LogicalName), Csv(t.DiskName), Csv(t.ExpectedGroup.ToString()),
Csv(r.DirectAccess.ToString()), Csv(r.ProtectedAccess.ToString()),
Csv(GetFinalAccessLabel(r.FinalAccess)),
Csv(r.FieldCount.ToString()), Csv(r.HasRows.ToString()),
Csv(r.HfSqlErrorCode ?? "")));
}
File.AppendAllLines(csvPath, csvLines);
Console.WriteLine($"CSV: CREATED ({csvPath})");
// ================================================================
// MARKDOWN REPORT
// ================================================================
string mdPath = Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "..", "docs", "diagnostics", "hfsql-oledb-open-all.md");
mdPath = Path.GetFullPath(mdPath);
Directory.CreateDirectory(Path.GetDirectoryName(mdPath)!);
var md = new System.Text.StringBuilder();
bool mdExists = File.Exists(mdPath);
if (!mdExists)
{
md.AppendLine("# HFSQL OLE DB Open All — Diagnostic");
md.AppendLine();
}
md.AppendLine($"## {schema}");
md.AppendLine();
md.AppendLine("### Résumé");
md.AppendLine();
md.AppendLine($"- Tables: {tables.Count}");
md.AppendLine($"- Direct: {directCount}");
md.AppendLine($"- ProtectionStd: {stdCount}");
md.AppendLine($"- ProtectionPlus: {plusCount}");
md.AppendLine($"- Failed: {failedCount}");
md.AppendLine();
md.AppendLine($"- ParametreMetier: {GetCanaryLabel(results, "ParametreMetier")}");
md.AppendLine($"- ParametreMetierEntete: {GetCanaryLabel(results, "ParametreMetierEntete")}");
md.AppendLine($"- ParametreSysteme: {GetCanaryLabel(results, "ParametreSysteme")}");
md.AppendLine();
md.AppendLine($"- Strategy: {strategy}");
md.AppendLine($"- ProtectionStd OLE DB: {(stdVerified ? "VERIFIED_FROM_DATABASE" : "UNRESOLVED")}");
md.AppendLine($"- ProtectionPlus OLE DB: {(plusVerified ? "VERIFIED_FROM_DATABASE" : "UNRESOLVED")}");
md.AppendLine($"- NegativeControl: {(negativeControlRun ? (negativeControlPass ? "PASS" : "FAIL") : "NOT_RUN")}");
md.AppendLine();
md.AppendLine("### Canaris");
md.AppendLine();
md.AppendLine($"- ParametreMetier: {GetCanaryLabel(results, "ParametreMetier")}");
md.AppendLine($"- ParametreMetierEntete: {GetCanaryLabel(results, "ParametreMetierEntete")}");
md.AppendLine($"- ParametreSysteme: {GetCanaryLabel(results, "ParametreSysteme")}");
md.AppendLine();
md.AppendLine("### Échecs");
md.AppendLine();
foreach (var t in tables.Where(t => results[t.LogicalName].FinalAccess == FinalAccess.Failed))
{
var r = results[t.LogicalName];
md.AppendLine($"- {t.LogicalName} (Expected={t.ExpectedGroup}, HfSql={r.HfSqlErrorCode ?? "NONE"})");
}
md.AppendLine();
File.AppendAllText(mdPath, md.ToString());
Console.WriteLine($"MD: CREATED ({mdPath})");
return 0;
}
// ================================================================
// HELPERS
// ================================================================
private static string GetCanaryLabel(Dictionary<string, TableResult> results, string name)
{
if (!results.TryGetValue(name, out var r)) return "FAILED";
return r.FinalAccess switch
{
FinalAccess.Direct => "DIRECT",
FinalAccess.ProtectionStd => "PROTECTION_STD",
FinalAccess.ProtectionPlus => "PROTECTION_PLUS",
FinalAccess.Failed => "FAILED",
_ => "FAILED"
};
}
private static string GetFinalAccessLabel(FinalAccess access)
{
return access switch
{
FinalAccess.Direct => "DIRECT",
FinalAccess.ProtectionStd => "PROTECTION_STD",
FinalAccess.ProtectionPlus => "PROTECTION_PLUS",
FinalAccess.Failed => "FAILED",
_ => "UNKNOWN"
};
}
private static string Csv(string? value)
{
if (string.IsNullOrEmpty(value)) return "";
if (value.Contains(',') || value.Contains('"') || value.Contains('\n'))
return "\"" + value.Replace("\"", "\"\"") + "\"";
return value;
}
// ================================================================
// MODEL LOADING (reflection)
// ================================================================
private static List<TableInfo> LoadTables(string schema)
{
var assembly = typeof(HfsqlFilePasswordDeriver).Assembly;
string ns = schema.Equals("Batik", StringComparison.OrdinalIgnoreCase)
? "Omika.Data.HfSql.Models.Batik"
: "Omika.Data.HfSql.Models.Omika";
var tables = new List<TableInfo>();
foreach (var type in assembly.GetTypes())
{
if (type.Namespace != ns) continue;
var attr = type.GetCustomAttributes()
.FirstOrDefault(a => a.GetType().Name == "HfSqlTableAttribute");
if (attr is null) continue;
string tableName = GetAttrProperty(attr, "TableName") ?? type.Name;
string diskName = GetAttrProperty(attr, "DiskName") ?? tableName + ".fic";
string[] groups = GetAttrGroups(attr);
bool hasStd = groups.Any(g => g.Equals("ProtectionStd", StringComparison.OrdinalIgnoreCase));
bool hasPlus = groups.Any(g => g.Equals("ProtectionPlus", StringComparison.OrdinalIgnoreCase));
ExpectedGroup expected;
if (hasStd && hasPlus) expected = ExpectedGroup.Conflict;
else if (hasPlus) expected = ExpectedGroup.ProtectionPlus;
else if (hasStd) expected = ExpectedGroup.ProtectionStd;
else expected = ExpectedGroup.None;
tables.Add(new TableInfo
{
LogicalName = tableName,
DiskName = diskName,
ExpectedGroup = expected
});
}
return tables.OrderBy(t => t.LogicalName).ToList();
}
private static string? GetAttrProperty(object attr, string propName)
{
var prop = attr.GetType().GetProperty(propName);
if (prop is null) return null;
var val = prop.GetValue(attr);
return val?.ToString();
}
private static string[] GetAttrGroups(object attr)
{
var prop = attr.GetType().GetProperty("Groups");
if (prop is null) return [];
var val = prop.GetValue(attr);
if (val is string[] arr) return arr;
if (val is IEnumerable<string> en) return en.ToArray();
return [];
}
// ================================================================
// CONNECTION STRING / EXTENDED PROPERTIES
// ================================================================
private static string EscapeExtendedPropertyValue(string value)
{
return value
.Replace("\\", "\\\\")
.Replace("=", "\\=")
.Replace(";", "\\;")
.Replace(":", "\\:");
}
private static string BuildFilePasswordEntry(string logicalFileName, string password)
{
return "Password=" +
EscapeExtendedPropertyValue(logicalFileName) +
":" +
EscapeExtendedPropertyValue(password);
}
private static string BuildConnectionString(
TestConfig root,
DatabaseConfig database,
string? extendedProperties)
{
var builder = new OleDbConnectionStringBuilder();
builder.Provider = "PCSOFT.HFSQL";
builder.DataSource = root.Server + ":" + root.Port;
builder["Initial Catalog"] = database.Database;
builder["User ID"] = database.User;
builder["Password"] = database.Credential;
if (!string.IsNullOrWhiteSpace(extendedProperties))
{
builder["Extended Properties"] = extendedProperties;
}
return builder.ConnectionString;
}
// ================================================================
// QUERY
// ================================================================
private static QueryResult TrySelect(OleDbConnection connection, string logicalName)
{
if (!IsValidIdentifier(logicalName))
{
return new QueryResult(false, 0, false, null);
}
try
{
using var cmd = connection.CreateCommand();
cmd.CommandText = $"SELECT TOP 1 * FROM {logicalName}";
using var reader = cmd.ExecuteReader();
int fieldCount = reader.FieldCount;
bool hasRows = reader.Read();
return new QueryResult(true, fieldCount, hasRows, null);
}
catch (OleDbException ex)
{
return new QueryResult(false, 0, false, ExtractHfSqlErrorCode(ex));
}
}
private static bool IsValidIdentifier(string name)
{
if (string.IsNullOrEmpty(name)) return false;
foreach (char c in name)
{
bool ok = (c >= 'A' && c <= 'Z')
|| (c >= 'a' && c <= 'z')
|| (c >= '0' && c <= '9')
|| c == '_';
if (!ok) return false;
}
return true;
}
private static string? ExtractHfSqlErrorCode(OleDbException ex)
{
string msg = ex.Message;
var m = Regex.Match(msg, @"Code\s+erreur\s*:\s*(\d+)");
if (!m.Success)
m = Regex.Match(msg, @"Error\s+code\s*:\s*(\d+)");
return m.Success ? m.Groups[1].Value : null;
}
// ================================================================
// STRUCTURES
// ================================================================
private sealed record QueryResult(
bool Success,
int FieldCount,
bool HasRows,
string? HfSqlErrorCode);
private sealed class TestConfig
{
public int Port { get; set; }
public string Server { get; set; } = "";
public string ProtectionPlusSecret { get; set; } = "";
public FilePasswordsConfig FilePasswords { get; set; } = new();
public DatabaseConfig Batik { get; set; } = new();
public DatabaseConfig Omika { get; set; } = new();
}
private sealed class FilePasswordsConfig
{
public string ProtectionStd { get; set; } = "";
public string ProtectionPlus { get; set; } = "";
}
private sealed class DatabaseConfig
{
public string Credential { get; set; } = "";
public string Database { get; set; } = "";
public string User { get; set; } = "";
public string? OriginalDatabaseName { get; set; }
}
private enum ExpectedGroup
{
None,
ProtectionStd,
ProtectionPlus,
Conflict
}
private enum FinalAccess
{
Unknown,
Direct,
ProtectionStd,
ProtectionPlus,
Failed
}
private sealed class TableInfo
{
public string LogicalName { get; init; } = "";
public string DiskName { get; init; } = "";
public ExpectedGroup ExpectedGroup { get; init; }
}
private sealed class TableResult
{
public required TableInfo Table { get; init; }
public bool DirectAccess { get; set; }
public bool ProtectedAccess { get; set; }
public FinalAccess FinalAccess { get; set; }
public int FieldCount { get; set; }
public bool HasRows { get; set; }
public string? HfSqlErrorCode { get; set; }
}
}