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