using System.Data; using System.Data.Odbc; namespace Omika.HfSqlProbe; public static class Program { public static int Main(string[] args) { if (args.Length == 0) { PrintUsage(); return 2; } string command = ProbeArgumentParser.NormalizeCommand(args); if (!ProbeArgumentParser.IsValidCommand(command)) { Console.Error.WriteLine($"ERROR: unknown command '{command}'."); PrintUsage(); return 2; } if (command == "--help") { PrintUsage(); return 0; } var options = ProbeArgumentParser.Parse(args.AsSpan(1)); if (options.ConnectionString is null) { Console.Error.WriteLine("ERROR: --connection-string is required."); return 2; } if (command == "capabilities") return RunCapabilities(options); if (command == "audit") return RunAudit(options); if (command == "table-exists") return RunTableExists(options); if (command == "protected-access") return RunProtectedAccess(options); if (ProbeArgumentParser.IsWriteCommand(command)) { if (!options.AllowWriteProbe) { Console.Error.WriteLine("ERROR: This probe performs temporary writes."); Console.Error.WriteLine(" It must be run ONLY against a clone/test database."); Console.Error.WriteLine(" Add --allow-write-probe to confirm you understand this."); return 2; } Console.WriteLine("==================================="); Console.WriteLine(" HFSQL ODBC WRITE PROBE"); Console.WriteLine("==================================="); Console.WriteLine(); Console.WriteLine("\u26a0 This probe performs TEMPORARY writes."); Console.WriteLine(" It MUST run on a clone/test database ONLY."); Console.WriteLine(); } if (command == "transaction") return RunTransactionProbe(options); if (command == "returning") return RunReturningProbe(options); if (command == "client-insert") return RunClientInsert(options); if (command == "client-graph") return RunClientGraph(options); if (command == "omika-guid") return RunOmikaGuidProbe(options); return 0; } static int RunCapabilities(ProbeOptions options) { Console.WriteLine("HFSQL ODBC CAPABILITY PROBE"); Console.WriteLine("==========================="); Console.WriteLine(); try { using var connection = new OdbcConnection(options.ConnectionString); connection.Open(); Console.WriteLine("Connection: OPEN"); Console.WriteLine($" Driver: {MaskConnectionProperty(connection.Driver)}"); Console.WriteLine($" DataSource: {MaskConnectionProperty(connection.DataSource)}"); Console.WriteLine($" ServerVersion: {connection.ServerVersion}"); Console.WriteLine($" Database property: {(string.IsNullOrEmpty(connection.Database) ? "(empty / not exposed by driver)" : MaskConnectionProperty(connection.Database))}"); Console.WriteLine(); Console.WriteLine("CONNECTION: PASS"); Console.WriteLine(); Console.WriteLine("── ODBC Metadata ──"); try { var tables = connection.GetSchema("Tables"); Console.WriteLine($" ODBC metadata: SUPPORTED"); Console.WriteLine($" Tables: {tables.Rows.Count}"); } catch (Exception ex) { Console.WriteLine($" ODBC metadata: UNSUPPORTED ({ex.GetType().Name})"); } Console.WriteLine(); Console.WriteLine("── Driver info ──"); using (var tx = connection.BeginTransaction()) { tx.Rollback(); Console.WriteLine(" BeginTransaction + Rollback: OK (API level)"); } return 0; } catch (OdbcException ex) { Console.Error.WriteLine($"CONNECTION FAILED: {ConnectionStringRedactor.Redact(ex.Message)}"); return 3; } catch (Exception ex) { Console.Error.WriteLine($"CONNECTION FAILED: {ConnectionStringRedactor.Redact(ex.Message)}"); return 3; } } static int RunTransactionProbe(ProbeOptions options) { if (options.Table is null) { Console.Error.WriteLine("ERROR: --table is required for transaction probe."); return 2; } int code = ValidateIdentifiers(options.Table, options.IdColumn ?? "PKID", options.MarkerColumn ?? "Marker"); if (code != 0) return code; string marker = $"PROBE_TX_{DateTime.UtcNow:yyyyMMddHHmmss}_{Guid.NewGuid():N}"; string table = options.Table, markerCol = options.MarkerColumn ?? "Marker"; Console.WriteLine($"Table: {table}"); Console.WriteLine($"Marker: {marker}"); Console.WriteLine(); try { using var connection = new OdbcConnection(options.ConnectionString); connection.Open(); using var tx = connection.BeginTransaction(); Console.WriteLine("BEGIN TRANSACTION: OK"); using (var insertCmd = connection.CreateCommand()) { insertCmd.Transaction = tx; insertCmd.CommandText = ProbeSqlBuilder.BuildInsertMarker(table, markerCol); insertCmd.Parameters.Add(new OdbcParameter { Value = marker }); Console.WriteLine($"INSERT: OK ({insertCmd.ExecuteNonQuery()} row(s))"); } using (var selCmd = connection.CreateCommand()) { selCmd.Transaction = tx; selCmd.CommandText = ProbeSqlBuilder.BuildCountByMarker(table, markerCol); selCmd.Parameters.Add(new OdbcParameter { Value = marker }); Console.WriteLine($"VISIBLE BEFORE ROLLBACK: {(Convert.ToInt32(selCmd.ExecuteScalar()) > 0 ? "YES" : "NO")}"); } tx.Rollback(); Console.WriteLine("ROLLBACK: OK"); using (var selCmd2 = connection.CreateCommand()) { selCmd2.CommandText = ProbeSqlBuilder.BuildCountByMarker(table, markerCol); selCmd2.Parameters.Add(new OdbcParameter { Value = marker }); Console.WriteLine($"ABSENT AFTER ROLLBACK: {(Convert.ToInt32(selCmd2.ExecuteScalar()) == 0 ? "YES" : "NO")}"); } return 0; } catch (OdbcException ex) { Console.Error.WriteLine($"FAILED: {ConnectionStringRedactor.Redact(ex.Message)}"); return 1; } } static int RunReturningProbe(ProbeOptions options) { if (options.Table is null) { Console.Error.WriteLine("ERROR: --table is required for returning probe."); return 2; } int code = ValidateIdentifiers(options.Table, options.IdColumn ?? "PKID", options.MarkerColumn ?? "Marker"); if (code != 0) return code; string marker = $"PROBE_RET_{DateTime.UtcNow:yyyyMMddHHmmss}_{Guid.NewGuid():N}"; string table = options.Table, idCol = options.IdColumn ?? "PKID", markerCol = options.MarkerColumn ?? "Marker"; Console.WriteLine($"Table: {table}"); Console.WriteLine($"ID col: {idCol}"); Console.WriteLine($"Marker: {marker}"); Console.WriteLine(); try { using var connection = new OdbcConnection(options.ConnectionString); connection.Open(); Console.WriteLine("-- METHOD A: ExecuteScalar --"); try { using var txA = connection.BeginTransaction(); using var cmdA = connection.CreateCommand(); cmdA.Transaction = txA; cmdA.CommandText = ProbeSqlBuilder.BuildInsertReturning(table, idCol, markerCol); cmdA.Parameters.Add(new OdbcParameter { Value = marker }); object? scalar = cmdA.ExecuteScalar(); Console.WriteLine($" Accepted: YES"); Console.WriteLine($" CLR type: {scalar?.GetType().Name ?? "null"}"); Console.WriteLine($" Raw value: {scalar}"); if (scalar is long l) Console.WriteLine($" Int64: {l} ({(l > 0 ? "VALID" : "ZERO/INVALID")})"); else if (scalar is not null) { try { Console.WriteLine($" Convert.ToInt64: {Convert.ToInt64(scalar)}"); } catch { Console.WriteLine(" Convert.ToInt64: FAILED"); } } txA.Rollback(); } catch (OdbcException ex) { Console.WriteLine($" ERROR: {ConnectionStringRedactor.Redact(ex.Message)}"); } Console.WriteLine(); Console.WriteLine("-- METHOD B: ExecuteReader --"); try { using var txB = connection.BeginTransaction(); using var cmdB = connection.CreateCommand(); cmdB.Transaction = txB; cmdB.CommandText = ProbeSqlBuilder.BuildInsertReturning(table, idCol, markerCol); cmdB.Parameters.Add(new OdbcParameter { Value = marker }); using var reader = cmdB.ExecuteReader(); Console.WriteLine($" HasRows: {reader.HasRows}"); Console.WriteLine($" FieldCount: {reader.FieldCount}"); if (reader.Read()) { for (int f = 0; f < reader.FieldCount; f++) Console.WriteLine($" [{f}] {reader.GetName(f)} ({reader.GetDataTypeName(f)}): {reader.GetValue(f)}"); } reader.Close(); txB.Rollback(); } catch (OdbcException ex) { Console.WriteLine($" ERROR: {ConnectionStringRedactor.Redact(ex.Message)}"); } Console.WriteLine(); Console.WriteLine("-- VERIFY ROLLBACK --"); using (var verifyCmd = connection.CreateCommand()) { verifyCmd.CommandText = ProbeSqlBuilder.BuildCountByMarker(table, markerCol); verifyCmd.Parameters.Add(new OdbcParameter { Value = marker }); Console.WriteLine($" Rows with marker: {Convert.ToInt32(verifyCmd.ExecuteScalar())}"); } return 0; } catch (OdbcException ex) { Console.Error.WriteLine($"FAILED: {ConnectionStringRedactor.Redact(ex.Message)}"); return 1; } } static string DeriveProtectionStdPassword(string databaseName) { byte[] hash = System.Security.Cryptography.MD5.HashData( System.Text.Encoding.UTF8.GetBytes(databaseName)); return Convert.ToHexString(hash).ToLowerInvariant(); } static int RunProtectedAccess(ProbeOptions options) { if (options.Table is null) { Console.Error.WriteLine("ERROR: --table is required."); return 2; } string table = SqlIdentifierValidator.Validate(options.Table); if (string.IsNullOrEmpty(options.DatabaseName)) { Console.Error.WriteLine("ERROR: --database-name is required."); return 2; } string dbName = options.DatabaseName; Console.WriteLine($"Table: {table}"); Console.WriteLine($"Database: {dbName}"); Console.WriteLine(); try { using var connection = new OdbcConnection(options.ConnectionString); connection.Open(); Console.WriteLine("Connection: OPEN"); // Step A: try normal access Console.WriteLine("-- Normal access --"); bool directPassed = false; try { using var cmd = connection.CreateCommand(); cmd.CommandText = $"SELECT COUNT(*) FROM {table}"; int count = Convert.ToInt32(cmd.ExecuteScalar()); Console.WriteLine($" ACCESS = UNPROTECTED (rows: {count})"); return 0; } catch (OdbcException ex) { string msg = ex.Message; Console.WriteLine($" ERROR: {ConnectionStringRedactor.Redact(msg)}"); Console.WriteLine($" SQLState: {ex.Errors[0]?.SQLState}, NativeError: {ex.Errors[0]?.NativeError}"); Console.WriteLine(" Classification: DIRECT ACCESS FAILED — attempting OPEN FILE fallback"); } // Step B: password fallback — PC SOFT official ODBC syntax Console.WriteLine("-- ProtectionStd fallback (OPEN FILE USING) --"); string derived = DeriveProtectionStdPassword(dbName); // Validate derived password is 32-char hex before any use if (!System.Text.RegularExpressions.Regex.IsMatch(derived, @"^[0-9a-f]{32}$")) { Console.Error.WriteLine(" FATAL: Derived password failed validation."); return 1; } Console.WriteLine($" Password derived: YES (32 hex chars)"); Console.WriteLine($" Password value: [NEVER DISPLAYED]"); // Execute OPEN FILE USING on the same connection — Client/Server form try { using var openCmd = connection.CreateCommand(); // Extract connection properties for Client/Server OPEN FILE string csDb = connection.Database; if (string.IsNullOrEmpty(csDb)) csDb = dbName; string csServer = connection.DataSource; // Build Client/Server explicit OPEN FILE (sanitized form for reporting) string openCmdText = $"OPEN FILE {table} SERVER '{csServer}' DATABASE '{csDb}' USER '' USING ''"; Console.WriteLine($" {openCmdText}"); openCmd.CommandText = $"OPEN FILE {table} SERVER '{csServer}' DATABASE '{csDb}' USER 'susana' USING '{derived}'"; openCmd.ExecuteNonQuery(); Console.WriteLine(" OPEN FILE (C/S): ACCEPTED"); // Retry SELECT using var retryCmd = connection.CreateCommand(); retryCmd.CommandText = $"SELECT COUNT(*) FROM {table}"; int retryCount = Convert.ToInt32(retryCmd.ExecuteScalar()); Console.WriteLine($" SELECT after OPEN: PASS (rows: {retryCount})"); Console.WriteLine($" ACCESS = PROTECTED — ProtectionStd VERIFIED"); return 0; } catch (OdbcException ex2) { Console.WriteLine($" OPEN FILE (C/S) error: {ConnectionStringRedactor.Redact(ex2.Message)}"); Console.WriteLine($" SQLState: {ex2.Errors[0]?.SQLState}, NativeError: {ex2.Errors[0]?.NativeError}"); Console.WriteLine(" Classification: OPEN FILE USING FAILED"); return 1; } } catch (OdbcException ex) { Console.Error.WriteLine($"FAILED: {ConnectionStringRedactor.Redact(ex.Message)}"); return 1; } } static int RunTableExists(ProbeOptions options) { if (options.Table is null) { Console.Error.WriteLine("ERROR: --table is required."); return 2; } string table = SqlIdentifierValidator.Validate(options.Table); Console.WriteLine($"Checking: {table}"); try { using var connection = new OdbcConnection(options.ConnectionString); connection.Open(); using var cmd = connection.CreateCommand(); cmd.CommandText = $"SELECT COUNT(*) FROM {table}"; int count = Convert.ToInt32(cmd.ExecuteScalar()); Console.WriteLine($" EXISTS: YES (rows: {count})"); // Also try GetSchema try { var cols = connection.GetSchema("Columns", [null, null, table, null]); Console.WriteLine($" GetSchema Columns: {cols.Rows.Count} columns"); foreach (System.Data.DataRow row in cols.Rows) { string col = row["COLUMN_NAME"]?.ToString() ?? "?"; string type = row["TYPE_NAME"]?.ToString() ?? "?"; int size = row["COLUMN_SIZE"] is int s ? s : 0; bool nullable = row["IS_NULLABLE"]?.ToString() == "YES"; Console.WriteLine($" {col,-25} {type,-10} size={size,4} nullable={nullable}"); } } catch { Console.WriteLine(" GetSchema Columns: FAILED"); } return 0; } catch (OdbcException ex) { Console.Error.WriteLine($" DOES NOT EXIST or error: {ConnectionStringRedactor.Redact(ex.Message)}"); return 1; } } static int RunAudit(ProbeOptions options) { Console.WriteLine("HFSQL ODBC PHYSICAL AUDIT"); Console.WriteLine("========================"); Console.WriteLine(); try { using var connection = new OdbcConnection(options.ConnectionString); connection.Open(); Console.WriteLine("Connected."); Console.WriteLine(); // ── MetaDataCollections ── Console.WriteLine("── ODBC MetaDataCollections ──"); try { var md = connection.GetSchema("MetaDataCollections"); foreach (System.Data.DataRow row in md.Rows) Console.WriteLine($" {row["CollectionName"]}: {row["NumberOfRestrictions"]} restrictions"); } catch (Exception ex) { Console.WriteLine($" ERROR: {ex.Message}"); } Console.WriteLine(); // ── Tables ── Console.WriteLine("── Tables ──"); var tables = connection.GetSchema("Tables"); var tableNames = new List(); foreach (System.Data.DataRow row in tables.Rows) { string? name = row["TABLE_NAME"]?.ToString(); if (!string.IsNullOrEmpty(name)) tableNames.Add(name); } tableNames.Sort(); int i = 0; foreach (var t in tableNames) Console.WriteLine($" [{++i:D2}] {t}"); Console.WriteLine($" Total: {tableNames.Count}"); Console.WriteLine(); // ── Columns for key tables ── string[] keyTables = ["Client", "Adresse", "ParametreMetier", "Contact", "Site", "Client_Adresse", "ClientAdresse", "Client_Lieu", "ClientLieu", "Lieu", "Personne", "Lieu_Personne", "LieuPersonne", "Compteur"]; foreach (var tbl in keyTables) { if (!tableNames.Contains(tbl, StringComparer.OrdinalIgnoreCase)) continue; Console.WriteLine($"── Columns: {tbl} ──"); try { var cols = connection.GetSchema("Columns", [null, null, tbl, null]); foreach (System.Data.DataRow row in cols.Rows) { string colName = row["COLUMN_NAME"]?.ToString() ?? "?"; string typeName = row["TYPE_NAME"]?.ToString() ?? "?"; int colSize = row["COLUMN_SIZE"] is int s ? s : 0; bool nullable = row["IS_NULLABLE"]?.ToString() == "YES"; Console.WriteLine($" {colName,-30} {typeName,-15} size={colSize,5} nullable={nullable}"); } } catch (Exception ex) { Console.WriteLine($" ERROR: {ex.Message}"); } Console.WriteLine(); } return 0; } catch (OdbcException ex) { Console.Error.WriteLine($"FAILED: {ConnectionStringRedactor.Redact(ex.Message)}"); return 1; } } static int RunClientInsert(ProbeOptions options) { if (!options.UseClientTable) { Console.Error.WriteLine("ERROR: --use-client-table is required for client-insert."); Console.Error.WriteLine(" This probe writes to the Client table on a CLONE/TEST DB ONLY."); return 2; } bool isOmika = string.Equals(options.Schema, "omika", StringComparison.OrdinalIgnoreCase); string marker = $"ODBC_PROBE_{DateTime.UtcNow:yyyyMMddHHmmss}"; string markerGuid = isOmika ? Guid.NewGuid().ToString("D").ToLowerInvariant()[..32] : ""; Console.WriteLine($"Target: {(isOmika ? "OMIKA" : "BATIK")} Client"); Console.WriteLine($"Marker: {marker}"); try { using var connection = new OdbcConnection(options.ConnectionString); connection.Open(); using var tx = connection.BeginTransaction(); Console.WriteLine("BEGIN: OK"); string sql; var parameters = new List(); if (isOmika) { sql = @"INSERT INTO Client ( CL_NOM, CL_CODECLIENT, CL_INFOSUP, CL_PAYS, CL_CODETVA, CL_SIREN, CL_SIRET, GUID ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ? ) RETURNING CL_PKID"; parameters.Add(new OdbcParameter { Value = $"ODBC PROBE {marker}" }); parameters.Add(new OdbcParameter { Value = marker }); parameters.Add(new OdbcParameter { Value = "" }); parameters.Add(new OdbcParameter { Value = "FR" }); parameters.Add(new OdbcParameter { Value = "" }); parameters.Add(new OdbcParameter { Value = "" }); parameters.Add(new OdbcParameter { Value = "" }); parameters.Add(new OdbcParameter { Value = markerGuid }); Console.WriteLine($"GUID candidate: {markerGuid.Length} chars, pattern: {(markerGuid.Count(c => c == '-') == 4 ? "8-4-4-4-8" : "UNEXPECTED")}"); } else { // Minimal BATIK Client INSERT — only columns documented as required sql = @"INSERT INTO Client ( CL_NOM, CL_CODECLIENT ) VALUES ( ?, ? ) RETURNING CL_PKID"; parameters.Add(new OdbcParameter { Value = $"ODBC PROBE {marker}" }); parameters.Add(new OdbcParameter { Value = marker }); } using var cmd = connection.CreateCommand(); cmd.Transaction = tx; cmd.CommandText = sql; cmd.Parameters.AddRange([.. parameters]); Console.WriteLine("-- ExecuteScalar --"); object? scalar = cmd.ExecuteScalar(); Console.WriteLine($" Accepted: YES"); Console.WriteLine($" CLR type: {scalar?.GetType().Name ?? "null"}"); Console.WriteLine($" Raw value: {scalar}"); long pkid = 0; if (scalar is not null) { try { pkid = Convert.ToInt64(scalar); Console.WriteLine($" Convert.ToInt64: {pkid} ({(pkid > 0 ? "VALID" : "ZERO/INVALID")})"); } catch (Exception ex) { Console.WriteLine($" Convert.ToInt64: FAILED ({ex.Message})"); } } // Rollback tx.Rollback(); Console.WriteLine("ROLLBACK: OK"); Console.WriteLine("-- Verify rollback --"); using var verifyCmd = connection.CreateCommand(); verifyCmd.CommandText = "SELECT COUNT(*) FROM Client WHERE CL_CODECLIENT = ?"; verifyCmd.Parameters.Add(new OdbcParameter { Value = marker }); int count = Convert.ToInt32(verifyCmd.ExecuteScalar()); Console.WriteLine($" Rows with marker: {count}"); Console.WriteLine(count == 0 ? " Rollback: VERIFIED" : " Rollback: UNEXPECTED"); Console.WriteLine(); Console.WriteLine(pkid > 0 && count == 0 ? "VERDICT: CLIENT INSERT + RETURNING + ROLLBACK = VERIFIED" : "VERDICT: UNEXPECTED — check output above."); return (pkid > 0 && count == 0) ? 0 : 1; } catch (OdbcException ex) { Console.Error.WriteLine($"FAILED: {ConnectionStringRedactor.Redact(ex.Message)}"); return 1; } } static int RunClientGraph(ProbeOptions options) { if (!options.UseClientTable) { Console.Error.WriteLine("ERROR: --use-client-table is required for client-graph."); Console.Error.WriteLine(" This probe writes to Client + related tables on a CLONE/TEST DB ONLY."); return 2; } bool isOmika = string.Equals(options.Schema, "omika", StringComparison.OrdinalIgnoreCase); string marker = $"GRAPH_PROBE_{DateTime.UtcNow:yyyyMMddHHmmss}"; Console.WriteLine($"Target: {(isOmika ? "OMIKA" : "BATIK")} Client + Adresse graph"); Console.WriteLine($"Marker: {marker}"); try { using var connection = new OdbcConnection(options.ConnectionString); connection.Open(); using var tx = connection.BeginTransaction(); Console.WriteLine("BEGIN: OK"); // === Step 1: INSERT Adresse === long adressePkId = 0; { using var cmdAdr = connection.CreateCommand(); cmdAdr.Transaction = tx; cmdAdr.CommandText = "INSERT INTO Adresse (AD_LIGNE_1, AD_CODEPOSTAL, AD_VILLE) VALUES (?, ?, ?) RETURNING AD_PKID"; cmdAdr.Parameters.Add(new OdbcParameter { Value = $"Probe {marker}" }); cmdAdr.Parameters.Add(new OdbcParameter { Value = "99999" }); cmdAdr.Parameters.Add(new OdbcParameter { Value = "PROBE CITY" }); object? adrId = cmdAdr.ExecuteScalar(); adressePkId = Convert.ToInt64(adrId!); Console.WriteLine($"INSERT Adresse: OK, AD_PKID={adressePkId}, CLR={adrId?.GetType().Name}"); } if (adressePkId <= 0) { Console.Error.WriteLine("FAILED: Adresse PKID invalid."); return 1; } // === Step 2: INSERT Client with FK to Adresse === long clientPkId = 0; { using var cmdCli = connection.CreateCommand(); cmdCli.Transaction = tx; cmdCli.CommandText = "INSERT INTO Client (CL_NOM, CL_CODECLIENT, CL_IDADRESSEPRINCIPALE) VALUES (?, ?, ?) RETURNING CL_PKID"; cmdCli.Parameters.Add(new OdbcParameter { Value = $"ODBC GRAPH {marker}" }); cmdCli.Parameters.Add(new OdbcParameter { Value = marker }); cmdCli.Parameters.Add(new OdbcParameter { Value = adressePkId }); object? cliId = cmdCli.ExecuteScalar(); clientPkId = Convert.ToInt64(cliId!); Console.WriteLine($"INSERT Client: OK, CL_PKID={clientPkId}, FK_ADRESSE={adressePkId}, CLR={cliId?.GetType().Name}"); } if (clientPkId <= 0) { Console.Error.WriteLine("FAILED: Client PKID invalid."); return 1; } Console.WriteLine($"FK chain verified: AD_PKID {adressePkId} -> CL_IDADRESSEPRINCIPALE {adressePkId}"); // === Rollback global === tx.Rollback(); Console.WriteLine("ROLLBACK: OK (global)"); // === Verify all absent === using (var cmdCheck = connection.CreateCommand()) { cmdCheck.CommandText = "SELECT COUNT(*) FROM Client WHERE CL_CODECLIENT = ?"; cmdCheck.Parameters.Add(new OdbcParameter { Value = marker }); int c = Convert.ToInt32(cmdCheck.ExecuteScalar()); Console.WriteLine($"Client rows with marker: {c}"); } using (var cmdCheckAdr = connection.CreateCommand()) { cmdCheckAdr.CommandText = "SELECT COUNT(*) FROM Adresse WHERE AD_VILLE = 'PROBE CITY' AND AD_LIGNE_1 LIKE 'Probe GRAPH%'"; int a = Convert.ToInt32(cmdCheckAdr.ExecuteScalar()); Console.WriteLine($"Adresse rows with marker: {a}"); } Console.WriteLine(); Console.WriteLine("VERDICT: MULTI-INSERT + FK CHAINING + GLOBAL ROLLBACK = VERIFIED"); return 0; } catch (OdbcException ex) { Console.Error.WriteLine($"FAILED: {ConnectionStringRedactor.Redact(ex.Message)}"); return 1; } } static int RunOmikaGuidProbe(ProbeOptions options) { Console.WriteLine("OMIKA GUID PROBE"); Console.WriteLine("================"); Console.WriteLine(); try { using var connection = new OdbcConnection(options.ConnectionString); connection.Open(); Console.WriteLine("Connected. Querying Client table for GUID statistics..."); Console.WriteLine(); using var cmd = connection.CreateCommand(); cmd.CommandText = ProbeSqlBuilder.BuildSelectGuids(200); int count = 0, empty = 0, allHex = 0, hasDash = 0, hasBrace = 0, hasNonPrint = 0; int parseN = 0, parseD = 0, parseAny = 0; int exactly4Dashes = 0, standardDashPos = 0, looksLikeTruncatedD = 0; int allUpper = 0, allLower = 0, mixedCase = 0; var nonHexSeen = new Dictionary(); var lenDist = new Dictionary(); using var reader = cmd.ExecuteReader(); while (reader.Read()) { count++; string? raw = reader.IsDBNull(0) ? null : reader.GetString(0); if (string.IsNullOrEmpty(raw)) { empty++; continue; } string guid = raw.TrimEnd('\0', ' '); int len = guid.Length; lenDist.TryGetValue(len, out int lc); lenDist[len] = lc + 1; bool isAllHex = guid.Where(c => c != '-').All(c => (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')); if (isAllHex) allHex++; if (guid.Contains('-')) hasDash++; if (guid.Contains('{') || guid.Contains('}')) hasBrace++; if (guid.Any(c => char.IsControl(c) && c != '\0')) hasNonPrint++; foreach (char c in guid) { bool isHex = (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); if (!isHex && c != '-') { nonHexSeen.TryGetValue(c, out int nh); nonHexSeen[c] = nh + 1; } } if (Guid.TryParseExact(guid, "N", out _)) parseN++; if (Guid.TryParseExact(guid, "D", out _)) parseD++; if (Guid.TryParse(guid, out _)) parseAny++; // Dash position analysis (0-based) int dashCount = guid.Count(c => c == '-'); if (dashCount == 4) exactly4Dashes++; bool hasStandardPositions = dashCount == 4 && guid.Length == 32 && guid[8] == '-' && guid[13] == '-' && guid[18] == '-' && guid[23] == '-'; if (hasStandardPositions) standardDashPos++; if (hasStandardPositions && isAllHex) { looksLikeTruncatedD++; } // Casing var hexOnly = guid.Replace("-", ""); if (hexOnly.Length > 0) { if (hexOnly.All(c => c >= 'A' && c <= 'F' || c >= '0' && c <= '9')) allUpper++; else if (hexOnly.All(c => c >= 'a' && c <= 'f' || c >= '0' && c <= '9')) allLower++; else mixedCase++; } } Console.WriteLine($" Sample: {count}"); Console.WriteLine($" Empty/null: {empty}"); Console.WriteLine(); Console.WriteLine("── Length distribution ──"); foreach (var kv in lenDist.OrderBy(x => x.Key)) Console.WriteLine($" Len={kv.Key}: {kv.Value}"); Console.WriteLine(); Console.WriteLine("── Content analysis ──"); Console.WriteLine($" All-hex: {allHex}/{count}"); Console.WriteLine($" Contains '-': {hasDash}/{count}"); Console.WriteLine($" Contains brace: {hasBrace}/{count}"); Console.WriteLine($" Non-printable: {hasNonPrint}/{count}"); Console.WriteLine(); Console.WriteLine("── Dash position analysis ──"); Console.WriteLine($" Exactly 4 dashes: {exactly4Dashes}/{count}"); Console.WriteLine($" Standard positions: {standardDashPos}/{count}"); Console.WriteLine($" LooksLikeTruncatedD:{looksLikeTruncatedD}/{count}"); Console.WriteLine(); Console.WriteLine("── Casing (hex chars only) ──"); Console.WriteLine($" All uppercase: {allUpper}/{count}"); Console.WriteLine($" All lowercase: {allLower}/{count}"); Console.WriteLine($" Mixed case: {mixedCase}/{count}"); Console.WriteLine(); Console.WriteLine("── Guid.Parse ──"); Console.WriteLine($" ParseExact N: {parseN}/{count}"); Console.WriteLine($" ParseExact D: {parseD}/{count}"); Console.WriteLine($" ParseAny: {parseAny}/{count}"); if (nonHexSeen.Count > 0) { Console.WriteLine(); Console.WriteLine("── Non-hex chars (codepoint: count) ──"); foreach (var kv in nonHexSeen.OrderByDescending(x => x.Value).Take(20)) Console.WriteLine($" U+{(int)kv.Key:X4} ('{kv.Key}'): {kv.Value}"); } Console.WriteLine(); Console.WriteLine("VERDICT: DETAILED ANALYSIS ABOVE."); return 0; } catch (OdbcException ex) { Console.Error.WriteLine($"FAILED: {ConnectionStringRedactor.Redact(ex.Message)}"); return 1; } } static int ValidateIdentifiers(string table, string idColumn, string markerColumn) { try { SqlIdentifierValidator.Validate(table); SqlIdentifierValidator.Validate(idColumn); SqlIdentifierValidator.Validate(markerColumn); return 0; } catch (ArgumentException ex) { Console.Error.WriteLine($"ERROR: {ex.Message}"); return 2; } } static string MaskConnectionProperty(string? value) { if (string.IsNullOrEmpty(value)) return "(empty)"; if (value.Contains("PWD=", StringComparison.OrdinalIgnoreCase) || value.Contains("Password=", StringComparison.OrdinalIgnoreCase) || value.Contains("UID=", StringComparison.OrdinalIgnoreCase)) return "(masked)"; return value; } static void PrintUsage() { Console.WriteLine("HFSQL ODBC CAPABILITY PROBE"); Console.WriteLine(); Console.WriteLine("COMMANDS:"); Console.WriteLine(" capabilities Display driver info + connection capabilities (READ-ONLY)"); Console.WriteLine(" audit List all tables and key columns metadata (READ-ONLY)"); Console.WriteLine(" transaction Test BEGIN/INSERT/ROLLBACK cycle (WRITE — needs --allow-write-probe)"); Console.WriteLine(" returning Test INSERT ... RETURNING via ODBC (WRITE — needs --allow-write-probe)"); Console.WriteLine(" omika-guid Read GUID statistics from Client table (READ-ONLY)"); Console.WriteLine(); Console.WriteLine("OPTIONS:"); Console.WriteLine(" --connection-string HFSQL ODBC connection string (DSN=...)"); Console.WriteLine(" --allow-write-probe Confirm working on clone/test DB (required for writes)"); Console.WriteLine(" --table Table name for write probes"); Console.WriteLine(" --id-column Auto-ID column name (default: PKID)"); Console.WriteLine(" --marker-column Column to write a marker value (default: Marker)"); Console.WriteLine(" --schema batik | omika (for client-specific probes)"); Console.WriteLine(" --use-client-table Write to Client table (clone DB only!)"); Console.WriteLine(); Console.WriteLine("\u26a0 NEVER run write probes against a production database."); } }