using System.Globalization;
using System.Text.Json;
using Dapper;
using Microsoft.Data.SqlClient;
using SQLInsight.Core.Data;
namespace SQLInsight.Repository;
/// A long operation as stored; is sensitive (local UI only).
public sealed record StoredLongOperation(int SessionId, DateTime StartedAt, string Command, string? DatabaseName, string? LoginName,
double PercentComplete, DateTime? EstimatedEnd, DateTime LastSeenAt)
{
/// Still running at the latest sample (seen within the last two minutes).
public bool IsRunning(DateTime utcNow) => utcNow - LastSeenAt < TimeSpan.FromMinutes(2);
public TimeSpan Duration => LastSeenAt - StartedAt;
}
public sealed partial class InsightRepository
{
private static async Task SaveLongOperationsAsync(SqlConnection connection, int instanceId, LongOperationSnapshot snapshot)
{
if (snapshot.Running.Count == 0)
{
return;
}
await connection.ExecuteAsync("""
MERGE insight.LongOperations AS t
USING (SELECT * FROM OPENJSON(@json) WITH (SessionId int, StartedAt datetime2(0), Command nvarchar(32), DatabaseName nvarchar(128),
LoginName nvarchar(128), PercentComplete float, EstimatedEnd datetime2(0), LastSeenAt datetime2(0))) AS s
ON t.InstanceId = @instanceId AND t.StartedAt = s.StartedAt AND t.SessionId = s.SessionId AND t.Command = s.Command
WHEN MATCHED THEN UPDATE SET PercentComplete = s.PercentComplete, EstimatedEnd = s.EstimatedEnd, LastSeenAt = s.LastSeenAt
WHEN NOT MATCHED THEN INSERT (InstanceId, SessionId, StartedAt, Command, DatabaseName, LoginName, PercentComplete, EstimatedEnd, LastSeenAt)
VALUES (@instanceId, s.SessionId, s.StartedAt, s.Command, s.DatabaseName, s.LoginName, s.PercentComplete, s.EstimatedEnd, s.LastSeenAt);
""", new
{
instanceId,
json = JsonSerializer.Serialize(snapshot.Running.Select(o => new
{
o.SessionId,
StartedAt = Iso(o.StartedAt),
Command = o.Command.Length <= 32 ? o.Command : o.Command[..32],
o.DatabaseName,
o.LoginName,
o.PercentComplete,
EstimatedEnd = o.EstimatedEnd is { } end ? Iso(end) : null,
LastSeenAt = Iso(o.LastSeenAt),
}), Json),
}).ConfigureAwait(false);
static string Iso(DateTimeOffset value) => Utc(value).ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture);
}
/// Long operations that ran at some point in [from, to], newest first.
public async Task> GetLongOperationsAsync(int instanceId, DateTimeOffset from, DateTimeOffset to, int top = 200,
CancellationToken cancellationToken = default)
{
await using var connection = await OpenAsync(cancellationToken).ConfigureAwait(false);
var rows = await connection.QueryAsync("""
SELECT TOP (@top) SessionId, StartedAt, Command, DatabaseName, LoginName, PercentComplete, EstimatedEnd, LastSeenAt
FROM insight.LongOperations
WHERE InstanceId = @instanceId AND StartedAt <= @to AND LastSeenAt >= @from
ORDER BY StartedAt DESC;
""", new { instanceId, from = from.UtcDateTime, to = to.UtcDateTime, top }).ConfigureAwait(false);
return rows.ToList();
}
}