chore: initial git load of code space
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
using System.Collections;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions;
|
||||
using Strata.SqlTools.SqlBreakdown.Interfaces.Core;
|
||||
using CommandVisitor = Strata.SqlTools.Visitors.PostgreSql.CommandVisitor;
|
||||
using SqlClause = Strata.SqlTools.SqlBreakdown.Classes.SqlClause;
|
||||
using SqlExpressionClause = Strata.SqlTools.SqlBreakdown.Classes.SqlExpressionClause;
|
||||
using SqlServerCommandVisitor = Strata.SqlTools.Visitors.SqlServer.CommandVisitor;
|
||||
using SqlServerQueryBreakdown = Strata.SqlTools.Breakdowns.SqlServer.QueryBreakdown;
|
||||
using StatementParser = Strata.SqlTools.Statements.PostgreSql.StatementParser;
|
||||
|
||||
namespace Strata.SqlTools.Breakdowns.PostgreSql;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a PostgreSQL query breakdown with all clauses, following PostgreSQL SQL standards.
|
||||
/// Handles positional parameters using $1, $2, ... syntax for parameterized queries.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class QueryBreakdown : SqlServerQueryBreakdown
|
||||
{
|
||||
private const string ExpressionNullErrorMessage = "Expression cannot be null.";
|
||||
private static readonly StatementParser PostgreSqlParserInstance = new StatementParser();
|
||||
private int _parameterIndex = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QueryBreakdown"/> class.
|
||||
/// </summary>
|
||||
public QueryBreakdown() : base()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QueryBreakdown"/> class with SELECT and FROM clauses.
|
||||
/// </summary>
|
||||
/// <param name="selectClause">The SELECT clause.</param>
|
||||
/// <param name="fromClause">The FROM clause.</param>
|
||||
/// <param name="isMicrosoftSql">If true, uses Microsoft T-SQL parsing rules. If false, uses PostgreSQL parsing rules. Defaults to false.</param>
|
||||
public QueryBreakdown(string selectClause, string fromClause, bool isMicrosoftSql = false) : base()
|
||||
{
|
||||
var parser = isMicrosoftSql ? Parser : PostgreSqlParserInstance;
|
||||
|
||||
var cleanSelect = parser.ExtractSqlComments(selectClause, out var selectComments);
|
||||
SelectClause.Clause = cleanSelect.Trim();
|
||||
SelectClause.Comment = selectComments.Count > 0 ? string.Join(" ", selectComments) : null;
|
||||
|
||||
var cleanFrom = parser.ExtractSqlComments(fromClause, out var fromComments);
|
||||
FromClause.Clause = cleanFrom.Trim();
|
||||
FromClause.Comment = fromComments.Count > 0 ? string.Join(" ", fromComments) : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QueryBreakdown"/> class with SELECT, FROM, and WHERE clauses.
|
||||
/// </summary>
|
||||
/// <param name="selectClause">The SELECT clause.</param>
|
||||
/// <param name="fromClause">The FROM clause.</param>
|
||||
/// <param name="whereClause">The WHERE clause.</param>
|
||||
/// <param name="isMicrosoftSql">If true, uses Microsoft T-SQL parsing rules. If false, uses PostgreSQL parsing rules. Defaults to false.</param>
|
||||
public QueryBreakdown(string selectClause, string fromClause, string whereClause, bool isMicrosoftSql = false)
|
||||
: this(selectClause, fromClause, isMicrosoftSql)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(whereClause))
|
||||
{
|
||||
var parser = isMicrosoftSql ? Parser : PostgreSqlParserInstance;
|
||||
|
||||
var cleanWhere = parser.ExtractSqlComments(whereClause, out var whereComments);
|
||||
WhereClause.Clause = cleanWhere.Trim();
|
||||
WhereClause.Comment = whereComments.Count > 0 ? string.Join(" ", whereComments) : null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QueryBreakdown"/> class with SELECT, FROM, WHERE, and ORDER BY clauses.
|
||||
/// </summary>
|
||||
/// <param name="selectClause">The SELECT clause.</param>
|
||||
/// <param name="fromClause">The FROM clause.</param>
|
||||
/// <param name="whereClause">The WHERE clause.</param>
|
||||
/// <param name="orderByClause">The ORDER BY clause.</param>
|
||||
/// <param name="isMicrosoftSql">If true, uses Microsoft T-SQL parsing rules. If false, uses PostgreSQL parsing rules. Defaults to false.</param>
|
||||
public QueryBreakdown(string selectClause, string fromClause, string whereClause, string orderByClause, bool isMicrosoftSql = false)
|
||||
: this(selectClause, fromClause, whereClause, isMicrosoftSql)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(orderByClause))
|
||||
{
|
||||
var parser = isMicrosoftSql ? Parser : PostgreSqlParserInstance;
|
||||
|
||||
var cleanOrderBy = parser.ExtractSqlComments(orderByClause, out var orderByComments);
|
||||
OrderByClause.Clause = cleanOrderBy.Trim();
|
||||
OrderByClause.Comment = orderByComments.Count > 0 ? string.Join(" ", orderByComments) : null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a parameter to the query using PostgreSQL's positional parameter format ($1, $2, ...).
|
||||
/// </summary>
|
||||
/// <param name="parameterName">The parameter name (can be any name; PostgreSQL uses positions).</param>
|
||||
/// <param name="value">The parameter value.</param>
|
||||
public new void AddParameter(string parameterName, object value)
|
||||
{
|
||||
// For PostgreSQL, we track the parameter position and store by name
|
||||
var cleanName = parameterName.TrimStart('@', ':');
|
||||
|
||||
// Use base class internal list
|
||||
base.AddParameter(cleanName, value);
|
||||
|
||||
// Store with PostgreSQL position syntax for reference
|
||||
Parameters[$"${_parameterIndex}"] = value;
|
||||
Parameters[cleanName] = value;
|
||||
Parameters[$"@{cleanName}"] = value;
|
||||
|
||||
_parameterIndex++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of a parameter using PostgreSQL's positional format.
|
||||
/// </summary>
|
||||
/// <param name="parameterName">The parameter name (can be any name; PostgreSQL uses positions).</param>
|
||||
/// <param name="value">The parameter value.</param>
|
||||
public new void SetParameterValue(string parameterName, object value)
|
||||
{
|
||||
var cleanName = parameterName.TrimStart('@', ':');
|
||||
Parameters[cleanName] = value;
|
||||
Parameters[$"@{cleanName}"] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an expression to the SELECT clause.
|
||||
/// </summary>
|
||||
/// <param name="expression">The expression to add.</param>
|
||||
/// <param name="comment">Optional comment to add with the expression.</param>
|
||||
/// <param name="isMicrosoftSql">If true, uses Microsoft T-SQL formatting. If false, uses PostgreSQL formatting. Defaults to false.</param>
|
||||
public void AddSelectExpression(Expression expression, string? comment = null, bool isMicrosoftSql = false)
|
||||
{
|
||||
if (expression is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(expression), ExpressionNullErrorMessage);
|
||||
}
|
||||
|
||||
var visitor = isMicrosoftSql
|
||||
? (IVisitor<string>)new SqlServerCommandVisitor()
|
||||
: new CommandVisitor();
|
||||
var sql = expression.Accept(visitor);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(SelectClause.Clause))
|
||||
{
|
||||
SelectClause.Clause = sql;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectClause.Clause += ", " + sql;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(comment))
|
||||
{
|
||||
SelectClause.Comment = string.IsNullOrEmpty(SelectClause.Comment)
|
||||
? comment
|
||||
: $"{SelectClause.Comment} {comment}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an expression to the WHERE clause.
|
||||
/// </summary>
|
||||
/// <param name="expression">The expression to add.</param>
|
||||
/// <param name="comment">Optional comment to add with the expression.</param>
|
||||
/// <param name="isMicrosoftSql">If true, uses Microsoft T-SQL formatting. If false, uses PostgreSQL formatting. Defaults to false.</param>
|
||||
public void AddWhereExpression(Expression expression, string? comment = null, bool isMicrosoftSql = false)
|
||||
{
|
||||
if (expression is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(expression), ExpressionNullErrorMessage);
|
||||
}
|
||||
|
||||
var visitor = isMicrosoftSql
|
||||
? (IVisitor<string>)new SqlServerCommandVisitor()
|
||||
: new CommandVisitor();
|
||||
var sql = expression.Accept(visitor);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(WhereClause.Clause))
|
||||
{
|
||||
WhereClause.Clause = sql;
|
||||
}
|
||||
else
|
||||
{
|
||||
WhereClause.Clause += " AND " + sql;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(comment))
|
||||
{
|
||||
WhereClause.Comment = string.IsNullOrEmpty(WhereClause.Comment)
|
||||
? comment
|
||||
: $"{WhereClause.Comment} {comment}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a PostgreSQL SELECT statement and populates the query breakdown.
|
||||
/// </summary>
|
||||
/// <param name="sql">The SQL statement to parse.</param>
|
||||
/// <returns>A new QueryBreakdown instance with parsed components.</returns>
|
||||
public static new QueryBreakdown Parse(string sql)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sql))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(sql), "SQL statement cannot be null or empty.");
|
||||
}
|
||||
|
||||
if (!TryParse(sql, out var result, out var error))
|
||||
{
|
||||
throw new FormatException($"Failed to parse SQL statement: {error}");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to parse a PostgreSQL SELECT statement.
|
||||
/// </summary>
|
||||
/// <param name="sql">The SQL statement to parse.</param>
|
||||
/// <param name="result">The resulting QueryBreakdown if successful.</param>
|
||||
/// <param name="errorMessage">The error message if parsing fails.</param>
|
||||
/// <returns>True if parsing succeeded; false otherwise.</returns>
|
||||
public static bool TryParse(string sql, out QueryBreakdown result, out string errorMessage)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sql))
|
||||
{
|
||||
result = new QueryBreakdown();
|
||||
errorMessage = "SQL statement cannot be null or empty.";
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var parser = PostgreSqlParserInstance;
|
||||
var setupClauses = new List<string>();
|
||||
sql = parser.ExtractSetupClauses(sql, setupClauses);
|
||||
|
||||
var finishClauses = new ArrayList();
|
||||
sql = parser.ExtractFinishClauses(sql, finishClauses);
|
||||
|
||||
if (!parser.TryParseSelectStatement(sql, out var clauses, out errorMessage))
|
||||
{
|
||||
result = new QueryBreakdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
result = new QueryBreakdown
|
||||
{
|
||||
SelectClause = clauses?.SelectClause ?? new SqlExpressionClause(splitOnComma: true),
|
||||
FromClause = clauses?.FromClause ?? new SqlClause(),
|
||||
WhereClause = clauses?.WhereClause ?? new SqlExpressionClause(splitOnComma: false),
|
||||
GroupByClause = clauses?.GroupByClause ?? new SqlExpressionClause(splitOnComma: true),
|
||||
HavingClause = clauses?.HavingClause ?? new SqlExpressionClause(splitOnComma: false),
|
||||
OrderByClause = clauses?.OrderByClause ?? new SqlExpressionClause(splitOnComma: true),
|
||||
SetupClauses = setupClauses,
|
||||
FinishClauses = finishClauses,
|
||||
RawSql = sql
|
||||
};
|
||||
|
||||
// Extract parameters using PostgreSQL parser
|
||||
parser.ExtractParameters(result.Parameters, sql);
|
||||
|
||||
errorMessage = string.Empty;
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result = new QueryBreakdown();
|
||||
errorMessage = ex.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a LINQ to SQL query of the specified type based on this breakdown.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The entity type for the query.</typeparam>
|
||||
/// <returns>null by default, as QueryBreakdown operates on SQL. Override in derived classes to provide LINQ query reconstruction.</returns>
|
||||
/// <remarks>
|
||||
/// This PostgreSQL-specific implementation returns null since PostgreSQL QueryBreakdown represents parsed SQL statements.
|
||||
/// Derived classes can override this method to reconstruct LINQ queries from the analyzed components.
|
||||
/// </remarks>
|
||||
public override IQueryable<T>? GetQuery<T>() where T : class
|
||||
{
|
||||
// PostgreSQL breakdown represents parsed SQL statements and does not have a built-in way to create LINQ queries
|
||||
// Override in derived classes to provide LINQ query reconstruction if needed
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,347 @@
|
||||
using System.Text;
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
using Strata.SqlTools.SqlBreakdown.Interfaces;
|
||||
|
||||
namespace Strata.SqlTools.Breakdowns.PostgreSql;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL-specific collection for managing multiple QueryBreakdown objects.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class extends SqlBreakdownCollection with PostgreSQL-specific functionality,
|
||||
/// including support for PostgreSQL features like schema-qualified identifiers,
|
||||
/// LIMIT/OFFSET clauses, parameterized queries using $1, $2 syntax, and CTEs.
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
{
|
||||
private readonly List<QueryBreakdown> _queryBreakdowns;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QueryBreakdownCollection"/> class for PostgreSQL.
|
||||
/// </summary>
|
||||
public QueryBreakdownCollection() : base()
|
||||
{
|
||||
_queryBreakdowns = new List<QueryBreakdown>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QueryBreakdownCollection"/> class with initial query breakdowns.
|
||||
/// </summary>
|
||||
/// <param name="queryBreakdowns">The initial collection of query breakdowns.</param>
|
||||
public QueryBreakdownCollection(IEnumerable<QueryBreakdown> queryBreakdowns)
|
||||
: base(queryBreakdowns?.Cast<ISqlBreakdown>() ?? Enumerable.Empty<ISqlBreakdown>())
|
||||
{
|
||||
_queryBreakdowns = queryBreakdowns?.ToList() ?? new List<QueryBreakdown>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of QueryBreakdown objects.
|
||||
/// </summary>
|
||||
public IReadOnlyList<QueryBreakdown> QueryBreakdowns => _queryBreakdowns.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a QueryBreakdown to the collection.
|
||||
/// </summary>
|
||||
/// <param name="queryBreakdown">The QueryBreakdown to add.</param>
|
||||
public void Add(QueryBreakdown queryBreakdown)
|
||||
{
|
||||
if (queryBreakdown != null)
|
||||
{
|
||||
_queryBreakdowns.Add(queryBreakdown);
|
||||
base.Add(queryBreakdown);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple QueryBreakdowns to the collection.
|
||||
/// </summary>
|
||||
/// <param name="queryBreakdowns">The QueryBreakdowns to add.</param>
|
||||
public void AddRange(IEnumerable<QueryBreakdown> queryBreakdowns)
|
||||
{
|
||||
foreach (var qb in queryBreakdowns ?? new List<QueryBreakdown>())
|
||||
{
|
||||
Add(qb);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a QueryBreakdown from the collection.
|
||||
/// </summary>
|
||||
/// <returns>True if removed; otherwise, false.</returns>
|
||||
public bool Remove(QueryBreakdown queryBreakdown)
|
||||
{
|
||||
base.Remove(queryBreakdown);
|
||||
return _queryBreakdowns.Remove(queryBreakdown);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all query breakdowns from the collection.
|
||||
/// </summary>
|
||||
public new void Clear()
|
||||
{
|
||||
_queryBreakdowns.Clear();
|
||||
base.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the PostgreSQL SQL batch representation with proper statement separation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Generates PostgreSQL SQL with proper semi-colon separation for multiple statements.
|
||||
/// </remarks>
|
||||
/// <returns>The complete SQL batch as a single string.</returns>
|
||||
public string GetPostgreSqlBatch()
|
||||
{
|
||||
if (_queryBreakdowns.Count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var query in _queryBreakdowns)
|
||||
{
|
||||
var sql = query.GetSql();
|
||||
if (!string.IsNullOrEmpty(sql))
|
||||
{
|
||||
sb.AppendLine(sql);
|
||||
if (!sql.TrimEnd().EndsWith(';'))
|
||||
{
|
||||
sb.AppendLine(";");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a batch of PostgreSQL SQL statements into a collection.
|
||||
/// </summary>
|
||||
/// <param name="sqlBatch">The SQL batch to parse.</param>
|
||||
/// <returns>True if parsing succeeded; false otherwise.</returns>
|
||||
public bool ParseBatch(string sqlBatch)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sqlBatch))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Clear();
|
||||
var statements = sqlBatch.Split(';');
|
||||
|
||||
foreach (var statement in statements)
|
||||
{
|
||||
var trimmedStatement = statement.Trim();
|
||||
if (string.IsNullOrEmpty(trimmedStatement))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (QueryBreakdown.TryParse(statement, out var queryBreakdown, out _))
|
||||
{
|
||||
Add(queryBreakdown);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a summary of all queries including their types and basic composition.
|
||||
/// </summary>
|
||||
/// <returns>Summary information for each query.</returns>
|
||||
public IEnumerable<SqlServer.QuerySummary> GetQuerySummaries()
|
||||
{
|
||||
return _queryBreakdowns.Select((q, index) => new SqlServer.QuerySummary
|
||||
{
|
||||
Index = index,
|
||||
HasSelectClause = !string.IsNullOrWhiteSpace(q.SelectClause?.Clause),
|
||||
HasFromClause = !string.IsNullOrWhiteSpace(q.FromClause?.Clause),
|
||||
HasWhereClause = !string.IsNullOrWhiteSpace(q.WhereClause?.Clause),
|
||||
HasGroupByClause = !string.IsNullOrWhiteSpace(q.GroupByClause?.Clause),
|
||||
HasHavingClause = !string.IsNullOrWhiteSpace(q.HavingClause?.Clause),
|
||||
HasOrderByClause = !string.IsNullOrWhiteSpace(q.OrderByClause?.Clause),
|
||||
HasJoins = false,
|
||||
HasCTE = q.WithClauses.Count > 0,
|
||||
ColumnCount = !string.IsNullOrWhiteSpace(q.SelectClause?.Clause) ? q.SelectClause.Clause.Split(',').Length : 0,
|
||||
ParameterCount = q.ParameterList.Count(),
|
||||
JoinCount = 0
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of selected columns across all queries.
|
||||
/// </summary>
|
||||
/// <returns>Total column count.</returns>
|
||||
public int GetTotalSelectedColumns()
|
||||
{
|
||||
return _queryBreakdowns.Sum(q =>
|
||||
!string.IsNullOrWhiteSpace(q.SelectClause?.Clause)
|
||||
? q.SelectClause.Clause.Split(',').Length
|
||||
: 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all unique table names referenced across all queries.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This provides a quick overview of which tables are being queried.
|
||||
/// Note: This is a best-effort extraction and may not capture all table references,
|
||||
/// especially in complex subqueries or with aliasing.
|
||||
/// </remarks>
|
||||
/// <returns>List of unique table names.</returns>
|
||||
public IEnumerable<string> GetUniqueTableReferences()
|
||||
{
|
||||
var tables = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var tableNames = _queryBreakdowns
|
||||
.Where(q => !string.IsNullOrWhiteSpace(q.FromClause?.Clause))
|
||||
.SelectMany(q => ExtractTableNames(q.FromClause!.Clause!));
|
||||
|
||||
foreach (var table in tableNames)
|
||||
{
|
||||
tables.Add(table);
|
||||
}
|
||||
|
||||
return tables;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets parameter usage information across all queries.
|
||||
/// </summary>
|
||||
/// <returns>Parameter usage information.</returns>
|
||||
public IEnumerable<ParameterUsageReport> GetParameterUsageReport()
|
||||
{
|
||||
// Collect all unique parameter names from both ParameterList and Parameters dictionary
|
||||
var allParamNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var query in _queryBreakdowns)
|
||||
{
|
||||
// Add from ParameterList (parsed parameters)
|
||||
foreach (var param in query.ParameterList)
|
||||
{
|
||||
allParamNames.Add(param.Name);
|
||||
}
|
||||
|
||||
// Add from Parameters dictionary (manually added parameters)
|
||||
foreach (var paramName in query.Parameters.Keys)
|
||||
{
|
||||
allParamNames.Add(paramName);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var paramName in allParamNames)
|
||||
{
|
||||
var queriesUsing = 0;
|
||||
object? lastValue = null;
|
||||
|
||||
foreach (var query in _queryBreakdowns)
|
||||
{
|
||||
// Check ParameterList first (parsed)
|
||||
var param = query.ParameterList.FirstOrDefault(p => p.Name.Equals(paramName, StringComparison.OrdinalIgnoreCase));
|
||||
if (param != null)
|
||||
{
|
||||
queriesUsing++;
|
||||
lastValue = param.Value;
|
||||
}
|
||||
// Also check Parameters dictionary (manually added)
|
||||
else if (query.Parameters.TryGetValue(paramName, out var dictValue))
|
||||
{
|
||||
queriesUsing++;
|
||||
lastValue = dictValue;
|
||||
}
|
||||
}
|
||||
|
||||
yield return new ParameterUsageReport
|
||||
{
|
||||
ParameterName = paramName,
|
||||
Value = lastValue,
|
||||
UsedInQueryCount = queriesUsing,
|
||||
TotalQueries = _queryBreakdowns.Count
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to extract table names from a FROM clause.
|
||||
/// </summary>
|
||||
private static IEnumerable<string> ExtractTableNames(string fromClause)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(fromClause))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Simple extraction: split by comma and clean up aliases
|
||||
var parts = fromClause.Split(',');
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var trimmed = part.Trim();
|
||||
|
||||
// Remove alias (assuming format: table AS alias or table alias)
|
||||
var tokens = trimmed.Split(new[] { " AS ", " " }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (tokens.Length > 0)
|
||||
{
|
||||
var tableName = tokens[0].Trim();
|
||||
if (!string.IsNullOrWhiteSpace(tableName))
|
||||
{
|
||||
yield return tableName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents parameter usage information for a specific parameter across all queries in a collection.
|
||||
/// </summary>
|
||||
public class ParameterUsageReport
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the parameter name.
|
||||
/// </summary>
|
||||
public string ParameterName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parameter value.
|
||||
/// </summary>
|
||||
public object? Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of queries using this parameter.
|
||||
/// </summary>
|
||||
public int UsedInQueryCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the total number of queries in the collection.
|
||||
/// </summary>
|
||||
public int TotalQueries { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the parameter is used in all queries.
|
||||
/// </summary>
|
||||
public bool IsUsedInAllQueries => UsedInQueryCount == TotalQueries;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the parameter usage report for PostgreSQL parameters.
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
var usagePercentage = TotalQueries > 0 ? (UsedInQueryCount / (decimal)TotalQueries * 100) : 0;
|
||||
var paramSyntax = int.TryParse(ParameterName, out _) ? $"${ParameterName}" : $":{ParameterName}";
|
||||
return $"{paramSyntax}: {UsedInQueryCount}/{TotalQueries} queries ({usagePercentage:F1}%) - Value: {Value?.ToString() ?? "NULL"}";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user