2021-08-27 15:03:47 +00:00
|
|
|
#nullable enable
|
2020-06-13 16:31:20 +00:00
|
|
|
using System.Data;
|
|
|
|
using System.Data.Common;
|
2021-05-07 08:56:15 +00:00
|
|
|
using System.Diagnostics.CodeAnalysis;
|
2020-06-13 16:31:20 +00:00
|
|
|
|
|
|
|
using App.Metrics;
|
|
|
|
|
|
|
|
using NodaTime;
|
|
|
|
|
|
|
|
using Npgsql;
|
|
|
|
|
|
|
|
using Serilog;
|
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
namespace PluralKit.Core;
|
|
|
|
|
|
|
|
internal class PKCommand: DbCommand, IPKCommand
|
2020-06-13 16:31:20 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
private readonly ILogger _logger;
|
|
|
|
private readonly IMetrics _metrics;
|
|
|
|
|
|
|
|
private readonly PKConnection _ourConnection;
|
|
|
|
|
|
|
|
public PKCommand(NpgsqlCommand inner, PKConnection ourConnection, ILogger logger, IMetrics metrics)
|
2020-06-13 16:31:20 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
Inner = inner;
|
|
|
|
_ourConnection = ourConnection;
|
|
|
|
_logger = logger.ForContext<PKCommand>();
|
|
|
|
_metrics = metrics;
|
|
|
|
}
|
|
|
|
|
|
|
|
private NpgsqlCommand Inner { get; }
|
2021-08-27 15:03:47 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
protected override DbParameterCollection DbParameterCollection => Inner.Parameters;
|
2021-08-27 15:03:47 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
protected override DbTransaction? DbTransaction
|
|
|
|
{
|
|
|
|
get => Inner.Transaction;
|
|
|
|
set => Inner.Transaction = value switch
|
2020-06-13 16:31:20 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
NpgsqlTransaction npg => npg,
|
|
|
|
PKTransaction pk => pk.Inner,
|
|
|
|
_ => throw new ArgumentException($"Can't convert input type {value?.GetType()} to NpgsqlTransaction")
|
|
|
|
};
|
|
|
|
}
|
2020-06-13 16:31:20 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
public override bool DesignTimeVisible
|
|
|
|
{
|
|
|
|
get => Inner.DesignTimeVisible;
|
|
|
|
set => Inner.DesignTimeVisible = value;
|
|
|
|
}
|
2020-06-13 16:31:20 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
protected override DbConnection? DbConnection
|
|
|
|
{
|
|
|
|
get => Inner.Connection;
|
|
|
|
set =>
|
|
|
|
Inner.Connection = value switch
|
|
|
|
{
|
|
|
|
NpgsqlConnection npg => npg,
|
|
|
|
PKConnection pk => pk.Inner,
|
|
|
|
_ => throw new ArgumentException($"Can't convert input type {value?.GetType()} to NpgsqlConnection")
|
|
|
|
};
|
|
|
|
}
|
2020-06-13 16:31:20 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
public override Task<int> ExecuteNonQueryAsync(CancellationToken ct) =>
|
|
|
|
LogQuery(Inner.ExecuteNonQueryAsync(ct));
|
2020-06-13 16:31:20 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
public override Task<object?> ExecuteScalarAsync(CancellationToken ct) =>
|
|
|
|
LogQuery(Inner.ExecuteScalarAsync(ct));
|
2020-06-13 16:31:20 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
public override Task PrepareAsync(CancellationToken ct = default) => Inner.PrepareAsync(ct);
|
|
|
|
public override void Cancel() => Inner.Cancel();
|
2020-06-13 16:31:20 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
[AllowNull]
|
|
|
|
public override string CommandText
|
|
|
|
{
|
|
|
|
get => Inner.CommandText;
|
|
|
|
set => Inner.CommandText = value;
|
|
|
|
}
|
2020-06-13 16:31:20 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
public override int CommandTimeout
|
|
|
|
{
|
|
|
|
get => Inner.CommandTimeout;
|
|
|
|
set => Inner.CommandTimeout = value;
|
|
|
|
}
|
2020-06-13 16:31:20 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
public override CommandType CommandType
|
|
|
|
{
|
|
|
|
get => Inner.CommandType;
|
|
|
|
set => Inner.CommandType = value;
|
|
|
|
}
|
2020-06-13 16:31:20 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
public override UpdateRowSource UpdatedRowSource
|
|
|
|
{
|
|
|
|
get => Inner.UpdatedRowSource;
|
|
|
|
set => Inner.UpdatedRowSource = value;
|
|
|
|
}
|
2020-06-13 16:49:05 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
public override int ExecuteNonQuery() => throw SyncError(nameof(ExecuteNonQuery));
|
|
|
|
public override object ExecuteScalar() => throw SyncError(nameof(ExecuteScalar));
|
|
|
|
public override void Prepare() => throw SyncError(nameof(Prepare));
|
2020-06-13 16:49:05 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
protected override async Task<DbDataReader>
|
|
|
|
ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken ct) =>
|
|
|
|
await LogQuery(Inner.ExecuteReaderAsync(behavior, ct));
|
|
|
|
|
|
|
|
protected override DbParameter CreateDbParameter() => Inner.CreateParameter();
|
2021-08-27 15:03:47 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) =>
|
|
|
|
throw SyncError(nameof(ExecuteDbDataReader));
|
2021-08-27 15:03:47 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
private async Task<T> LogQuery<T>(Task<T> task)
|
|
|
|
{
|
|
|
|
var start = SystemClock.Instance.GetCurrentInstant();
|
|
|
|
try
|
|
|
|
{
|
|
|
|
return await task;
|
2020-06-13 16:31:20 +00:00
|
|
|
}
|
2021-11-27 02:10:56 +00:00
|
|
|
finally
|
|
|
|
{
|
|
|
|
var end = SystemClock.Instance.GetCurrentInstant();
|
|
|
|
var elapsed = end - start;
|
2021-08-27 15:03:47 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
_logger.Verbose("Executed query {Query} in {ElapsedTime} on connection {ConnectionId}", CommandText,
|
|
|
|
elapsed, _ourConnection.ConnectionId);
|
|
|
|
|
|
|
|
// One "BCL compatible tick" is 100 nanoseconds
|
|
|
|
var micros = elapsed.BclCompatibleTicks / 10;
|
|
|
|
_metrics.Provider.Timer.Instance(CoreMetrics.DatabaseQuery, new MetricTags("query", CommandText))
|
|
|
|
.Record(micros, TimeUnit.Microseconds, CommandText);
|
|
|
|
}
|
2020-06-13 16:31:20 +00:00
|
|
|
}
|
2021-11-27 02:10:56 +00:00
|
|
|
|
|
|
|
private static Exception SyncError(string caller) =>
|
|
|
|
throw new Exception($"Executed synchronous IDbCommand function {caller}!");
|
2020-06-13 16:31:20 +00:00
|
|
|
}
|