using HanaToolbox.Config; using HanaToolbox.Logging; using HanaToolbox.Services.Interfaces; namespace HanaToolbox.Services; /// /// Performs the Aurora schema refresh: /// Drop old _AURORA schema → Export source → Import-rename → Update company name → Grant privileges. /// public sealed class AuroraService( IUserSwitcher switcher, IHdbClientLocator locator, INotificationService ntfy, AppLogger logger) : IAuroraService { public async Task RunAsync( AuroraConfig config, HanaConfig hana, string sid, CancellationToken ct = default) { if (string.IsNullOrWhiteSpace(config.SourceSchema)) { logger.Error("Aurora: SourceSchema is not configured."); return; } var hdbsql = locator.LocateHdbsql(hana.HdbsqlPath, sid, hana.InstanceNumber); var threads = config.Threads > 0 ? config.Threads : Math.Max(1, Environment.ProcessorCount / 2); var aurora = $"{config.SourceSchema}_AURORA"; var tmpDir = Path.Combine(config.BackupBasePath, $"{aurora}_TEMP_{DateTime.Now:yyyyMMdd_HHmmss}"); logger.Step($"Starting Aurora refresh: '{config.SourceSchema}' → '{aurora}'"); // 1. Drop old Aurora schema (ignore errors if it doesn't exist) logger.Step($"Dropping old schema '{aurora}' (if exists)..."); await RunSql(hdbsql, config.AdminUserKey, $"DROP SCHEMA \"{aurora}\" CASCADE;", sid, ct); // 2. Prepare temp export directory await RunAs(sid, $"mkdir -p \"{tmpDir}\"", ct); // 3. Export source schema logger.Step($"Exporting '{config.SourceSchema}' to temp dir..."); var exportResult = await RunSql(hdbsql, config.AdminUserKey, $"EXPORT \"{config.SourceSchema}\".\"*\" AS BINARY INTO '{tmpDir}' WITH REPLACE THREADS {threads} NO DEPENDENCIES;", sid, ct); if (!exportResult.Success) { logger.Error($"Aurora export failed: {exportResult.StdErr}"); await ntfy.SendAsync("Aurora Failed", $"Export of '{config.SourceSchema}' FAILED.", ct); await Cleanup(tmpDir, sid, ct); return; } // 4. Import → rename logger.Step($"Importing as '{aurora}'..."); var importResult = await RunSql(hdbsql, config.AdminUserKey, $"IMPORT \"{config.SourceSchema}\".\"*\" AS BINARY FROM '{tmpDir}' WITH REPLACE RENAME SCHEMA \"{config.SourceSchema}\" TO \"{aurora}\" THREADS {threads};", sid, ct); if (!importResult.Success) { logger.Error($"Aurora import failed: {importResult.StdErr}"); await ntfy.SendAsync("Aurora Failed", $"Import-rename to '{aurora}' FAILED.", ct); await Cleanup(tmpDir, sid, ct); return; } // 5. Get original company name from CINF logger.Step("Fetching company name from source schema..."); var nameResult = await RunSqlScalar(hdbsql, config.AdminUserKey, $"SELECT \"CompnyName\" FROM \"{config.SourceSchema}\".\"CINF\";", sid, ct); var companyName = string.IsNullOrWhiteSpace(nameResult) ? config.SourceSchema : nameResult; var newName = $"AURORA - {companyName} - {DateTime.Now:yyyy-MM-dd}"; // 6. Update company name in CINF and OADM logger.Step($"Setting company name to '{newName}'..."); await RunSql(hdbsql, config.AdminUserKey, $"UPDATE \"{aurora}\".\"CINF\" SET \"CompnyName\" = '{newName}';", sid, ct); await RunSql(hdbsql, config.AdminUserKey, $"UPDATE \"{aurora}\".\"OADM\" SET \"CompnyName\" = '{newName}', \"PrintHeadr\" = '{newName}';", sid, ct); // 7. Grant privileges if (!string.IsNullOrWhiteSpace(config.AuroraUser)) { logger.Step($"Granting ALL PRIVILEGES on '{aurora}' to '{config.AuroraUser}'..."); await RunSql(hdbsql, config.AdminUserKey, $"GRANT ALL PRIVILEGES ON SCHEMA \"{aurora}\" TO \"{config.AuroraUser}\";", sid, ct); } // 8. Cleanup temp directory await Cleanup(tmpDir, sid, ct); logger.Success($"Aurora refresh of '{aurora}' completed!"); await ntfy.SendAsync("Aurora Complete", $"Aurora refresh of '{aurora}' completed successfully.", ct); } private async Task RunSql( string hdbsql, string userKey, string sql, string sid, CancellationToken ct) { var tmpFile = Path.Combine("/tmp", $"ht_{Guid.NewGuid():N}.sql"); await File.WriteAllTextAsync(tmpFile, sql, ct); var result = await switcher.RunAsAsync(sid, $"\"{hdbsql}\" -U {userKey} -I \"{tmpFile}\" 2>&1", ct); File.Delete(tmpFile); return result; } private async Task RunSqlScalar( string hdbsql, string userKey, string sql, string sid, CancellationToken ct) { var tmpFile = Path.Combine("/tmp", $"ht_{Guid.NewGuid():N}.sql"); await File.WriteAllTextAsync(tmpFile, sql, ct); var result = await switcher.RunAsAsync(sid, $"\"{hdbsql}\" -U {userKey} -a -x -I \"{tmpFile}\" 2>&1", ct); File.Delete(tmpFile); return result.StdOut.Replace("\"", "").Trim(); } private Task RunAs(string sid, string cmd, CancellationToken ct) => switcher.RunAsAsync(sid, cmd, ct); private async Task Cleanup(string dir, string sid, CancellationToken ct) { logger.Step($"Cleaning up temp dir '{dir}'..."); await RunAs(sid, $"rm -rf \"{dir}\"", ct); } }