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);
}
}