Skip to content

Commit 88837cf

Browse files
author
Mohsen Esmailpour
committed
Refactor postgres query builder.
1 parent 2782f32 commit 88837cf

15 files changed

+245
-197
lines changed

Serilog.Ui.sln.DotSettings

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
<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">
2-
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mongo/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
2+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mongo/@EntryIndexedValue">True</s:Boolean>
3+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Postgre/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

src/Serilog.Ui.PostgreSqlProvider/Extensions/PostgreSqlDbOptions.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Serilog.Ui.Core.Models.Options;
2+
using Serilog.Ui.Core.QueryBuilder.Sql;
23
using Serilog.Ui.PostgreSqlProvider.Models;
34

45
namespace Serilog.Ui.PostgreSqlProvider.Extensions;
@@ -9,10 +10,9 @@ public class PostgreSqlDbOptions : RelationalDbOptions
910
/// <inheritdoc />
1011
public PostgreSqlDbOptions(string defaultSchemaName) : base(defaultSchemaName)
1112
{
13+
ColumnNames = new PostgreSqlAlternativeSinkColumnNames();
1214
}
1315

14-
internal SinkColumnNames ColumnNames = new PostgreSqlAlternativeSinkColumnNames();
15-
1616
/// <summary>
1717
/// It gets or sets SinkType.
1818
/// The sink that used to store logs in the PostgreSQL database. This data provider supports
@@ -33,4 +33,6 @@ public PostgreSqlDbOptions WithSinkType(PostgreSqlSinkType sinkType)
3333
: new PostgreSqlSinkColumnNames();
3434
return this;
3535
}
36+
37+
internal SinkColumnNames ColumnNames { get; set; }
3638
}

src/Serilog.Ui.PostgreSqlProvider/Extensions/SerilogUiOptionBuilderExtensions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
using System;
2-
using Microsoft.Extensions.DependencyInjection;
1+
using Microsoft.Extensions.DependencyInjection;
32
using Serilog.Ui.Core;
43
using Serilog.Ui.Core.Interfaces;
54
using Serilog.Ui.PostgreSqlProvider.Models;
5+
using System;
66

77
namespace Serilog.Ui.PostgreSqlProvider.Extensions
88
{
@@ -44,12 +44,12 @@ Action<PostgreSqlDbOptions> setupOptions
4444
if (customModel)
4545
{
4646
optionsBuilder.RegisterColumnsInfo<T>(providerName);
47-
optionsBuilder.Services.AddScoped<IDataProvider>(_ => new PostgresDataProvider<T>(dbOptions));
47+
optionsBuilder.Services.AddScoped<IDataProvider>(_ => new PostgresDataProvider<T>(dbOptions, new PostgresQueryBuilder()));
4848

4949
return optionsBuilder;
5050
}
5151

52-
optionsBuilder.Services.AddScoped<IDataProvider>(_ => new PostgresDataProvider(dbOptions));
52+
optionsBuilder.Services.AddScoped<IDataProvider>(_ => new PostgresDataProvider(dbOptions, new PostgresQueryBuilder()));
5353
return optionsBuilder;
5454
}
5555
}

src/Serilog.Ui.PostgreSqlProvider/Models/PostgreLogModel.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,23 @@ public class PostgresLogModel : LogModel
1717
private string _level = string.Empty;
1818

1919
/// <inheritdoc />
20-
public sealed override int RowNo => base.RowNo;
20+
public override sealed int RowNo => base.RowNo;
2121

2222
/// <inheritdoc />
23-
public sealed override string? Message { get; set; }
23+
public override sealed string? Message { get; set; }
2424

2525
/// <inheritdoc />
26-
public sealed override DateTime Timestamp { get; set; }
26+
public override sealed DateTime Timestamp { get; set; }
2727

2828
/// <inheritdoc />
29-
public sealed override string? Level
29+
public override sealed string? Level
3030
{
3131
get => _level;
3232
set => _level = LogLevelConverter.GetLevelName(value);
3333
}
3434

3535
/// <summary>
36-
/// It get or sets LogEventSerialized.
36+
/// It gets or sets LogEventSerialized.
3737
/// </summary>
3838
[JsonIgnore]
3939
public string LogEvent { get; set; } = string.Empty;
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace Serilog.Ui.PostgreSqlProvider.Models;
1+
using Serilog.Ui.Core.QueryBuilder.Sql;
2+
3+
namespace Serilog.Ui.PostgreSqlProvider.Models;
24

35
internal class PostgreSqlAlternativeSinkColumnNames : SinkColumnNames
46
{
@@ -7,8 +9,8 @@ public PostgreSqlAlternativeSinkColumnNames()
79
Exception = "Exception";
810
Level = "Level";
911
LogEventSerialized = "LogEvent";
12+
Message = "Message";
1013
MessageTemplate = "MessageTemplate";
11-
RenderedMessage = "Message";
1214
Timestamp = "Timestamp";
1315
}
1416
}
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
namespace Serilog.Ui.PostgreSqlProvider.Models;
1+
using Serilog.Ui.Core.QueryBuilder.Sql;
2+
3+
namespace Serilog.Ui.PostgreSqlProvider.Models;
24

35
internal class PostgreSqlSinkColumnNames : SinkColumnNames
46
{
57
public PostgreSqlSinkColumnNames()
68
{
7-
RenderedMessage = "message";
8-
MessageTemplate = "message_template";
9-
Level = "level";
10-
Timestamp = "timestamp";
119
Exception = "exception";
10+
Level = "level";
1211
LogEventSerialized = "log_event";
12+
Message = "message";
13+
MessageTemplate = "message_template";
14+
Timestamp = "timestamp";
1315
}
1416
}

src/Serilog.Ui.PostgreSqlProvider/Models/SinkColumnNames.cs

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/Serilog.Ui.PostgreSqlProvider/PostgreDataProvider.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Threading;
5-
using System.Threading.Tasks;
6-
using Dapper;
1+
using Dapper;
72
using Npgsql;
83
using Serilog.Ui.Core;
94
using Serilog.Ui.Core.Models;
105
using Serilog.Ui.PostgreSqlProvider.Extensions;
116
using Serilog.Ui.PostgreSqlProvider.Models;
7+
using System;
8+
using System.Collections.Generic;
9+
using System.Linq;
10+
using System.Threading;
11+
using System.Threading.Tasks;
1212

1313
namespace Serilog.Ui.PostgreSqlProvider;
1414

1515
/// <inheritdoc/>
16-
public class PostgresDataProvider(PostgreSqlDbOptions options) : PostgresDataProvider<PostgresLogModel>(options);
16+
public class PostgresDataProvider(PostgreSqlDbOptions options, PostgresQueryBuilder queryBuilder)
17+
: PostgresDataProvider<PostgresLogModel>(options, queryBuilder);
1718

1819
/// <inheritdoc />
19-
public class PostgresDataProvider<T>(PostgreSqlDbOptions options) : IDataProvider
20+
public class PostgresDataProvider<T>(PostgreSqlDbOptions options, PostgresQueryBuilder queryBuilder) : IDataProvider
2021
where T : PostgresLogModel
2122
{
2223
internal const string ProviderName = "NPGSQL";
23-
2424
private readonly PostgreSqlDbOptions _options = options ?? throw new ArgumentNullException(nameof(options));
2525

2626
/// <inheritdoc/>
@@ -40,7 +40,7 @@ public class PostgresDataProvider<T>(PostgreSqlDbOptions options) : IDataProvide
4040

4141
private async Task<IEnumerable<LogModel>> GetLogsAsync(FetchLogsQuery queryParams)
4242
{
43-
var query = options.ColumnNames.BuildFetchLogsQuery<T>(_options.Schema, _options.TableName, queryParams);
43+
var query = queryBuilder.BuildFetchLogsQuery<T>(options.ColumnNames, _options.Schema, _options.TableName, queryParams);
4444
var rowNoStart = queryParams.Page * queryParams.Count;
4545

4646
await using var connection = new NpgsqlConnection(_options.ConnectionString);
@@ -68,7 +68,7 @@ private async Task<IEnumerable<LogModel>> GetLogsAsync(FetchLogsQuery queryParam
6868

6969
private async Task<int> CountLogsAsync(FetchLogsQuery queryParams)
7070
{
71-
var query = options.ColumnNames.BuildCountLogsQuery<T>(_options.Schema, _options.TableName, queryParams);
71+
var query = queryBuilder.BuildCountLogsQuery<T>(_options.ColumnNames, _options.Schema, _options.TableName, queryParams);
7272

7373
await using var connection = new NpgsqlConnection(_options.ConnectionString);
7474

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
using Serilog.Ui.Core.Attributes;
2+
using Serilog.Ui.Core.Models;
3+
using Serilog.Ui.Core.QueryBuilder.Sql;
4+
using Serilog.Ui.PostgreSqlProvider.Models;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Reflection;
8+
using System.Text;
9+
10+
namespace Serilog.Ui.PostgreSqlProvider;
11+
12+
/// <summary>
13+
/// Provides methods to build SQL queries specifically for PostgreSQL to fetch and count logs.
14+
/// </summary>
15+
public class PostgresQueryBuilder : SqlQueryBuilder
16+
{
17+
/// <summary>
18+
/// Builds a SQL query to fetch logs from the specified PostgreSQL table.
19+
/// </summary>
20+
/// <typeparam name="T">The type of the log model.</typeparam>
21+
/// <param name="columns">The column names used in the sink for logging.</param>
22+
/// <param name="schema">The schema of the table.</param>
23+
/// <param name="tableName">The name of the table.</param>
24+
/// <param name="query">The query parameters for fetching logs.</param>
25+
/// <returns>A SQL query string to fetch logs.</returns>
26+
public override string BuildFetchLogsQuery<T>(SinkColumnNames columns, string schema, string tableName, FetchLogsQuery query)
27+
{
28+
StringBuilder queryStr = new();
29+
30+
GenerateSelectClause<T>(queryStr, columns, schema, tableName);
31+
32+
GenerateWhereClause<T>(queryStr, columns, query.Level, query.SearchCriteria, query.StartDate, query.EndDate);
33+
34+
string sortClause = GenerateSortClause(columns, query.SortOn, query.SortBy);
35+
36+
queryStr.Append($" ORDER BY {sortClause} LIMIT @Count OFFSET @Offset");
37+
38+
return queryStr.ToString();
39+
}
40+
41+
/// <summary>
42+
/// Builds a SQL query to count logs in the specified PostgreSQL table.
43+
/// </summary>
44+
/// <typeparam name="T">The type of the log model.</typeparam>
45+
/// <param name="columns">The column names used in the sink for logging.</param>
46+
/// <param name="schema">The schema of the table.</param>
47+
/// <param name="tableName">The name of the table.</param>
48+
/// <param name="query">The query parameters for counting logs.</param>
49+
/// <returns>A SQL query string to count logs.</returns>
50+
public override string BuildCountLogsQuery<T>(SinkColumnNames columns, string schema, string tableName, FetchLogsQuery query)
51+
{
52+
StringBuilder queryStr = new();
53+
54+
queryStr.Append($"SELECT COUNT(\"{columns.Message}\") ")
55+
.Append($"FROM \"{schema}\".\"{tableName}\"");
56+
57+
GenerateWhereClause<T>(queryStr, columns, query.Level, query.SearchCriteria, query.StartDate, query.EndDate);
58+
59+
return queryStr.ToString();
60+
}
61+
62+
/// <summary>
63+
/// Generates the SELECT clause for the SQL query.
64+
/// </summary>
65+
/// <typeparam name="T">The type of the log model.</typeparam>
66+
/// <param name="queryBuilder">The StringBuilder to append the SELECT clause to.</param>
67+
/// <param name="columns">The column names used in the sink for logging.</param>
68+
/// <param name="schema">The schema of the table.</param>
69+
/// <param name="tableName">The name of the table.</param>
70+
private static void GenerateSelectClause<T>(StringBuilder queryBuilder, SinkColumnNames columns, string schema, string tableName)
71+
where T : LogModel
72+
{
73+
if (typeof(T) != typeof(PostgresLogModel))
74+
{
75+
queryBuilder.Append("SELECT *");
76+
}
77+
else
78+
{
79+
queryBuilder.Append($"SELECT \"{columns.Message}\", ")
80+
.Append($"\"{columns.MessageTemplate}\", ")
81+
.Append($"\"{columns.Level}\", ")
82+
.Append($"\"{columns.Timestamp}\", ")
83+
.Append($"\"{columns.Exception}\", ")
84+
.Append($"\"{columns.LogEventSerialized}\" AS \"Properties\"");
85+
}
86+
87+
queryBuilder.Append($" FROM \"{schema}\".\"{tableName}\"");
88+
}
89+
90+
/// <summary>
91+
/// Generates the WHERE clause for the SQL query.
92+
/// </summary>
93+
/// <typeparam name="T">The type of the log model.</typeparam>
94+
/// <param name="queryBuilder">The StringBuilder to append the WHERE clause to.</param>
95+
/// <param name="columns">The column names used in the sink for logging.</param>
96+
/// <param name="level">The log level to filter by.</param>
97+
/// <param name="searchCriteria">The search criteria to filter by.</param>
98+
/// <param name="startDate">The start date to filter by.</param>
99+
/// <param name="endDate">The end date to filter by.</param>
100+
private static void GenerateWhereClause<T>(
101+
StringBuilder queryBuilder,
102+
SinkColumnNames columns,
103+
string? level,
104+
string? searchCriteria,
105+
DateTime? startDate,
106+
DateTime? endDate)
107+
where T : LogModel
108+
{
109+
List<string> conditions = new();
110+
111+
if (!string.IsNullOrWhiteSpace(level))
112+
{
113+
conditions.Add($"\"{columns.Level}\" = @Level");
114+
}
115+
116+
if (!string.IsNullOrWhiteSpace(searchCriteria))
117+
{
118+
string exceptionCondition = AddExceptionToWhereClause<T>() ? $"OR \"{columns.Exception}\" LIKE @Search" : string.Empty;
119+
conditions.Add($"(\"{columns.Message}\" LIKE @Search {exceptionCondition})");
120+
}
121+
122+
if (startDate.HasValue)
123+
{
124+
conditions.Add($"\"{columns.Timestamp}\" >= @StartDate");
125+
}
126+
127+
if (endDate.HasValue)
128+
{
129+
conditions.Add($"\"{columns.Timestamp}\" <= @EndDate");
130+
}
131+
132+
if (conditions.Count <= 0)
133+
{
134+
return;
135+
}
136+
137+
queryBuilder
138+
.Append(" WHERE TRUE AND ")
139+
.Append(string.Join(" AND ", conditions));
140+
}
141+
142+
/// <summary>
143+
/// Determines whether to add the exception column to the WHERE clause based on the log model type.
144+
/// </summary>
145+
/// <typeparam name="T">The type of the log model.</typeparam>
146+
/// <returns>True if the exception column should be added; otherwise, false.</returns>
147+
private static bool AddExceptionToWhereClause<T>()
148+
where T : LogModel
149+
{
150+
PropertyInfo? exceptionProperty = typeof(T).GetProperty(nameof(PostgresLogModel.Exception));
151+
RemovedColumnAttribute? att = exceptionProperty?.GetCustomAttribute<RemovedColumnAttribute>();
152+
153+
return att is null;
154+
}
155+
}

0 commit comments

Comments
 (0)