Skip to content

Commit

Permalink
feat(sql-providers): unify SQL query generation (serilog-contrib#147)
Browse files Browse the repository at this point in the history
  • Loading branch information
mo-esmp authored Sep 29, 2024
1 parent 00964cc commit ea83b81
Show file tree
Hide file tree
Showing 50 changed files with 1,296 additions and 743 deletions.
3 changes: 2 additions & 1 deletion Serilog.Ui.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mongo/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mongo/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Postgre/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
37 changes: 37 additions & 0 deletions src/Serilog.Ui.Core/QueryBuilder/Sql/SinkColumnNames.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
namespace Serilog.Ui.Core.QueryBuilder.Sql;

/// <summary>
/// Represents the column names used in the SQL-based sink for logging.
/// </summary>
public abstract class SinkColumnNames
{
/// <summary>
/// Gets or sets the message of the log entry.
/// </summary>
public string Message { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the message template of the log entry.
/// </summary>
public string MessageTemplate { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the level of the log entry.
/// </summary>
public string Level { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the timestamp of the log entry.
/// </summary>
public string Timestamp { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the exception of the log entry.
/// </summary>
public string Exception { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the serialized log event like properties.
/// </summary>
public string LogEventSerialized { get; set; } = string.Empty;
}
67 changes: 67 additions & 0 deletions src/Serilog.Ui.Core/QueryBuilder/Sql/SqlQueryBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using Serilog.Ui.Core.Attributes;
using Serilog.Ui.Core.Models;
using System.Reflection;
using static Serilog.Ui.Core.Models.SearchOptions;

namespace Serilog.Ui.Core.QueryBuilder.Sql;

/// <summary>
/// Abstract class that provides methods to build SQL queries for fetching and counting logs.
/// </summary>
public abstract class SqlQueryBuilder<TModel> where TModel : LogModel
{
/// <summary>
/// Builds a SQL query to fetch logs from the specified table.
/// </summary>
/// <param name="columns">The column names used in the sink for logging.</param>
/// <param name="schema">The schema of the table.</param>
/// <param name="tableName">The name of the table.</param>
/// <param name="query">The query parameters for fetching logs.</param>
/// <returns>A SQL query string to fetch logs.</returns>
public abstract string BuildFetchLogsQuery(SinkColumnNames columns, string schema, string tableName, FetchLogsQuery query);

/// <summary>
/// Builds a SQL query to count logs in the specified table.
/// </summary>
/// <param name="columns">The column names used in the sink for logging.</param>
/// <param name="schema">The schema of the table.</param>
/// <param name="tableName">The name of the table.</param>
/// <param name="query">The query parameters for counting logs.</param>
/// <returns>A SQL query string to count logs.</returns>
public abstract string BuildCountLogsQuery(SinkColumnNames columns, string schema, string tableName, FetchLogsQuery query);

/// <summary>
/// Generates a SQL sort clause based on the specified sort property and direction.
/// </summary>
/// <param name="columns">The column names used in the sink for logging.</param>
/// <param name="sortOn">The property to sort on.</param>
/// <param name="sortBy">The direction to sort by.</param>
/// <returns>A SQL sort clause string.</returns>
protected abstract string GenerateSortClause(SinkColumnNames columns, SortProperty sortOn, SortDirection sortBy);

/// <summary>
/// Generates a SQL sort clause based on the specified sort property and direction.
/// </summary>
/// <param name="columns">The column names used in the sink for logging.</param>
/// <param name="sortOn">The property to sort on.</param>
/// <returns>A SQL sort clause string.</returns>
protected static string GetSortColumnName(SinkColumnNames columns, SortProperty sortOn) => sortOn switch
{
SortProperty.Timestamp => columns.Timestamp,
SortProperty.Level => columns.Level,
SortProperty.Message => columns.Message,
_ => columns.Timestamp
};

/// <summary>
/// Determines whether to add the exception column to the WHERE clause based on the presence of the RemovedColumnAttribute.
/// </summary>
/// <returns>True if the exception column should be added to the WHERE clause; otherwise, false.</returns>
protected static bool AddExceptionToWhereClause()
{
PropertyInfo? exceptionProperty = typeof(TModel).GetProperty("Exception");
RemovedColumnAttribute? att = exceptionProperty?.GetCustomAttribute<RemovedColumnAttribute>();

return att is null;
}
}
2 changes: 1 addition & 1 deletion src/Serilog.Ui.Core/Serilog.Ui.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Primitives" Version="8.0.0" />
<PackageReference Include="System.Text.Json" Version="8.0.4"/>
<PackageReference Include="System.Text.Json" Version="8.0.4" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,64 +1,64 @@
using System;
using Dapper;
using Dapper;
using Microsoft.Extensions.DependencyInjection;
using Serilog.Ui.Core;
using Serilog.Ui.Core.Interfaces;
using Serilog.Ui.Core.Models.Options;
using System;

namespace Serilog.Ui.MsSqlServerProvider.Extensions
{
/// <summary>
/// SQL Server data provider specific extension methods for <see cref="ISerilogUiOptionsBuilder"/>.
/// </summary>
public static class SerilogUiOptionBuilderExtensions
{
/// <summary>Configures the SerilogUi to connect to a SQL Server database.</summary>
/// <param name="optionsBuilder"> The options builder. </param>
/// <param name="setupOptions">The Ms Sql options action.</param>
/// <param name="dateTimeCustomParsing">
/// Delegate to customize the DateTime parsing.
/// It throws <see cref="InvalidOperationException" /> if the return DateTime isn't UTC kind.
/// </param>
public static ISerilogUiOptionsBuilder UseSqlServer(
this ISerilogUiOptionsBuilder optionsBuilder,
Action<RelationalDbOptions> setupOptions,
Func<string, DateTime>? dateTimeCustomParsing = null
) => optionsBuilder.UseSqlServer<SqlServerLogModel>(setupOptions, dateTimeCustomParsing);

/// <summary>Configures the SerilogUi to connect to a SQL Server database.</summary>
/// <typeparam name="T">The log model, containing any additional columns. It must inherit <see cref="SqlServerLogModel"/>.</typeparam>
/// <param name="optionsBuilder"> The options builder. </param>
/// <param name="setupOptions">The Ms Sql options action.</param>
/// <param name="dateTimeCustomParsing">
/// Delegate to customize the DateTime parsing.
/// It throws <see cref="InvalidOperationException" /> if the return DateTime isn't UTC kind.
/// </param>
public static ISerilogUiOptionsBuilder UseSqlServer<T>(
this ISerilogUiOptionsBuilder optionsBuilder,
Action<RelationalDbOptions> setupOptions,
Func<string, DateTime>? dateTimeCustomParsing = null
) where T : SqlServerLogModel
{
var dbOptions = new RelationalDbOptions("dbo");
setupOptions(dbOptions);
dbOptions.Validate();

var providerName = dbOptions.GetProviderName(SqlServerDataProvider.MsSqlProviderName);
namespace Serilog.Ui.MsSqlServerProvider.Extensions;

optionsBuilder.RegisterExceptionAsStringForProviderKey(providerName);
SqlMapper.AddTypeHandler(new DapperDateTimeHandler(dateTimeCustomParsing));
/// <summary>
/// SQL Server data provider specific extension methods for <see cref="ISerilogUiOptionsBuilder"/>.
/// </summary>
public static class SerilogUiOptionBuilderExtensions
{
/// <summary>Configures the SerilogUi to connect to a SQL Server database.</summary>
/// <param name="optionsBuilder"> The options builder. </param>
/// <param name="setupOptions">The Ms Sql options action.</param>
/// <param name="dateTimeCustomParsing">
/// Delegate to customize the DateTime parsing.
/// It throws <see cref="InvalidOperationException" /> if the return DateTime isn't UTC kind.
/// </param>
public static ISerilogUiOptionsBuilder UseSqlServer(
this ISerilogUiOptionsBuilder optionsBuilder,
Action<RelationalDbOptions> setupOptions,
Func<string, DateTime>? dateTimeCustomParsing = null
) => optionsBuilder.UseSqlServer<SqlServerLogModel>(setupOptions, dateTimeCustomParsing);

var customModel = typeof(T) != typeof(SqlServerLogModel);
if (customModel)
{
optionsBuilder.RegisterColumnsInfo<T>(providerName);
optionsBuilder.Services.AddScoped<IDataProvider>(_ => new SqlServerDataProvider<T>(dbOptions));
/// <summary>Configures the SerilogUi to connect to a SQL Server database.</summary>
/// <typeparam name="T">The log model, containing any additional columns. It must inherit <see cref="SqlServerLogModel"/>.</typeparam>
/// <param name="optionsBuilder"> The options builder. </param>
/// <param name="setupOptions">The Ms Sql options action.</param>
/// <param name="dateTimeCustomParsing">
/// Delegate to customize the DateTime parsing.
/// It throws <see cref="InvalidOperationException" /> if the return DateTime isn't UTC kind.
/// </param>
public static ISerilogUiOptionsBuilder UseSqlServer<T>(
this ISerilogUiOptionsBuilder optionsBuilder,
Action<RelationalDbOptions> setupOptions,
Func<string, DateTime>? dateTimeCustomParsing = null
) where T : SqlServerLogModel
{
SqlServerDbOptions dbOptions = new("dbo");
setupOptions(dbOptions);
dbOptions.Validate();

return optionsBuilder;
}
string providerName = dbOptions.GetProviderName(SqlServerDataProvider.MsSqlProviderName);
optionsBuilder.RegisterExceptionAsStringForProviderKey(providerName);
SqlMapper.AddTypeHandler(new DapperDateTimeHandler(dateTimeCustomParsing));

optionsBuilder.Services.AddScoped<IDataProvider>(_ => new SqlServerDataProvider(dbOptions));
return optionsBuilder;
bool customModel = typeof(T) != typeof(SqlServerLogModel);
if (customModel)
{
optionsBuilder.RegisterColumnsInfo<T>(providerName);
optionsBuilder.Services.AddScoped<IDataProvider>(_ => new SqlServerDataProvider<T>(dbOptions, new SqlServerQueryBuilder<T>()));
}
else
{
optionsBuilder.Services.AddScoped<IDataProvider>(_ =>
new SqlServerDataProvider(dbOptions, new SqlServerQueryBuilder<SqlServerLogModel>()));
}

return optionsBuilder;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Serilog.Ui.Core.Models.Options;
using Serilog.Ui.Core.QueryBuilder.Sql;
using Serilog.Ui.MsSqlServerProvider.Models;

namespace Serilog.Ui.MsSqlServerProvider.Extensions;

public class SqlServerDbOptions(string defaultSchemaName) : RelationalDbOptions(defaultSchemaName)
{
public SinkColumnNames ColumnNames { get; } = new SqlServerSinkColumnNames();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Serilog.Ui.Core.QueryBuilder.Sql;

namespace Serilog.Ui.MsSqlServerProvider.Models;

internal class SqlServerSinkColumnNames : SinkColumnNames
{
public SqlServerSinkColumnNames()
{
Exception = "Exception";
Level = "Level";
LogEventSerialized = "Properties";
Message = "Message";
MessageTemplate = "";
Timestamp = "TimeStamp";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Version>3.0.0</Version>
<Version>3.1.0</Version>

<Description>Microsoft SQL Server data provider for Serilog UI.</Description>
<PackageTags>serilog serilog-ui serilog.sinks.mssqlserver mssqlserver</PackageTags>
Expand All @@ -18,5 +18,6 @@

<ItemGroup>
<ProjectReference Include="..\Serilog.Ui.Core\Serilog.Ui.Core.csproj" PrivateAssets="All" />
<InternalsVisibleTo Include="MsSql.Tests" />
</ItemGroup>
</Project>
Loading

0 comments on commit ea83b81

Please sign in to comment.