353 lines
11 KiB
C#
353 lines
11 KiB
C#
using System.Text;
|
|
|
|
namespace Strata.SqlTools.Breakdowns.SqlServer;
|
|
|
|
/// <summary>
|
|
/// SQL Server-specific collection for managing multiple QueryBreakdown objects.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This class extends SqlBreakdownCollection with SQL Server-specific functionality,
|
|
/// including support for T-SQL features like batches (GO), temporary tables, stored procedures, and CTEs.
|
|
/// </remarks>
|
|
public class QueryBreakdownCollection : QueryBreakdownCollectionBase<QueryBreakdown>
|
|
{
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="QueryBreakdownCollection"/> class.
|
|
/// </summary>
|
|
public QueryBreakdownCollection() : base()
|
|
{
|
|
}
|
|
|
|
/// <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)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the SQL Server T-SQL batch representation with proper batch handling.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Generates T-SQL with proper GO separators and optional transaction support.
|
|
/// </remarks>
|
|
/// <param name="includeSetupFinish">Whether to include setup and finish clauses.</param>
|
|
/// <param name="includeTransaction">Whether to wrap in BEGIN TRANSACTION / COMMIT.</param>
|
|
/// <returns>The formatted T-SQL batch.</returns>
|
|
public string GetSqlServerBatch(bool includeSetupFinish = true, bool includeTransaction = false)
|
|
{
|
|
var sb = new StringBuilder();
|
|
|
|
// Add transaction wrapper if requested
|
|
if (includeTransaction)
|
|
{
|
|
sb.AppendLine("BEGIN TRANSACTION;");
|
|
sb.AppendLine();
|
|
}
|
|
|
|
// Add all queries with GO separators
|
|
if (QueryBreakdownList.Count > 0)
|
|
{
|
|
for (int i = 0; i < QueryBreakdownList.Count; i++)
|
|
{
|
|
var query = QueryBreakdownList[i];
|
|
sb.Append(query.GetSql(includeSetupFinish));
|
|
|
|
// Add GO separator between queries (not after last)
|
|
if (i < QueryBreakdownList.Count - 1)
|
|
{
|
|
sb.AppendLine();
|
|
sb.AppendLine("GO");
|
|
sb.AppendLine();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Close transaction if opened
|
|
if (includeTransaction)
|
|
{
|
|
sb.AppendLine();
|
|
sb.AppendLine("COMMIT TRANSACTION;");
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Filters query breakdowns that have WITH clauses (CTEs).
|
|
/// </summary>
|
|
/// <returns>Query breakdowns with CTE definitions.</returns>
|
|
public IEnumerable<QueryBreakdown> WhereHaveCommonTableExpressions()
|
|
{
|
|
return QueryBreakdownList.Where(q => q.WithClauses.Count > 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Filters query breakdowns that reference JOIN operations.
|
|
/// </summary>
|
|
/// <returns>Query breakdowns with JOINs.</returns>
|
|
public IEnumerable<QueryBreakdown> WhereHaveJoins()
|
|
{
|
|
return QueryBreakdownList.Where(q => q.GetSql().Contains("JOIN", StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Filters query breakdowns by parameter usage.
|
|
/// </summary>
|
|
/// <param name="parameterName">The parameter name to search for.</param>
|
|
/// <returns>Query breakdowns using the specified parameter.</returns>
|
|
public IEnumerable<QueryBreakdown> WhereUseParameter(string parameterName)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(parameterName))
|
|
{
|
|
throw new ArgumentNullException(nameof(parameterName));
|
|
}
|
|
|
|
return QueryBreakdownList.Where(q =>
|
|
q.ParameterList.Any(p => p.Name == parameterName));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a parameter to all queries in the collection.
|
|
/// </summary>
|
|
/// <param name="parameterName">The parameter name.</param>
|
|
/// <param name="value">The parameter value.</param>
|
|
public void AddParameterToAll(string parameterName, object? value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(parameterName))
|
|
{
|
|
throw new ArgumentNullException(nameof(parameterName));
|
|
}
|
|
|
|
foreach (var query in QueryBreakdownList)
|
|
{
|
|
query.Parameters[parameterName] = value!;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a summary of all queries including their types and basic composition.
|
|
/// </summary>
|
|
/// <returns>Summary information for each query.</returns>
|
|
public IEnumerable<QuerySummary> GetQuerySummaries()
|
|
=> QueryCollectionAnalysisHelper.GetQuerySummaries(QueryBreakdownList);
|
|
|
|
/// <summary>
|
|
/// Gets all T-SQL parameters as a formatted string suitable for SQL Server.
|
|
/// </summary>
|
|
/// <param name="includeDataTypes">Whether to include estimated data types (uses generic approach).</param>
|
|
/// <returns>A formatted string of parameters.</returns>
|
|
public string GetParametersAsString(bool includeDataTypes = false)
|
|
{
|
|
var parameters = GetCombinedParameterDictionary();
|
|
if (parameters.Count == 0)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
var sb = new StringBuilder();
|
|
bool first = true;
|
|
|
|
foreach (var param in parameters)
|
|
{
|
|
if (!first)
|
|
{
|
|
sb.Append(", ");
|
|
}
|
|
|
|
sb.Append($"@{param.Key}");
|
|
|
|
if (includeDataTypes)
|
|
{
|
|
var dataType = GetSqlDataType(param.Value);
|
|
sb.Append($" {dataType}");
|
|
}
|
|
|
|
sb.Append($" = {FormatParameterValue(param.Value)}");
|
|
first = false;
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a report of parameter usage across all queries.
|
|
/// </summary>
|
|
/// <returns>Parameter usage information.</returns>
|
|
public IEnumerable<ParameterUsageReport> GetParameterUsageReport()
|
|
{
|
|
return QueryCollectionAnalysisHelper.GetParameterUsage(QueryBreakdownList)
|
|
.Select(usage => new ParameterUsageReport
|
|
{
|
|
ParameterName = usage.Name,
|
|
Value = usage.Value,
|
|
UsedInQueryCount = usage.UsedInQueryCount,
|
|
TotalQueries = usage.TotalQueries
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method to get SQL Server data type from a .NET object.
|
|
/// </summary>
|
|
private static string GetSqlDataType(object? value)
|
|
{
|
|
return value switch
|
|
{
|
|
null => "SQL_VARIANT",
|
|
bool => "BIT",
|
|
byte => "TINYINT",
|
|
short => "SMALLINT",
|
|
int => "INT",
|
|
long => "BIGINT",
|
|
float => "REAL",
|
|
double => "FLOAT",
|
|
decimal => "DECIMAL(18, 2)",
|
|
string => "NVARCHAR(MAX)",
|
|
DateTime => "DATETIME2",
|
|
_ => "SQL_VARIANT"
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method to format a parameter value for SQL output.
|
|
/// </summary>
|
|
private static string FormatParameterValue(object? value)
|
|
{
|
|
return value switch
|
|
{
|
|
null => "NULL",
|
|
bool b => b ? "1" : "0",
|
|
string s => $"'{s.Replace("'", "''")}'",
|
|
DateTime dt => $"'{dt:yyyy-MM-dd HH:mm:ss}'",
|
|
byte or short or int or long or float or double or decimal => value.ToString() ?? "NULL",
|
|
_ => throw new ArgumentException($"Unsupported parameter type: {value.GetType().Name}. Only primitive types, strings, and DateTime are supported.")
|
|
};
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Summary information about a query for quick analysis.
|
|
/// </summary>
|
|
public class QuerySummary
|
|
{
|
|
/// <summary>
|
|
/// Gets or sets the index of the query in the collection.
|
|
/// </summary>
|
|
public int Index { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets whether the query has a SELECT clause.
|
|
/// </summary>
|
|
public bool HasSelectClause { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets whether the query has a FROM clause.
|
|
/// </summary>
|
|
public bool HasFromClause { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets whether the query has a WHERE clause.
|
|
/// </summary>
|
|
public bool HasWhereClause { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets whether the query has a GROUP BY clause.
|
|
/// </summary>
|
|
public bool HasGroupByClause { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets whether the query has a HAVING clause.
|
|
/// </summary>
|
|
public bool HasHavingClause { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets whether the query has an ORDER BY clause.
|
|
/// </summary>
|
|
public bool HasOrderByClause { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets whether the query has JOIN clauses.
|
|
/// </summary>
|
|
public bool HasJoins { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets whether the query has Common Table Expressions (CTEs).
|
|
/// </summary>
|
|
public bool HasCTE { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the number of columns in the SELECT clause.
|
|
/// </summary>
|
|
public int ColumnCount { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the number of parameters used.
|
|
/// </summary>
|
|
public int ParameterCount { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the number of JOIN clauses.
|
|
/// </summary>
|
|
public int JoinCount { get; set; }
|
|
|
|
/// <summary>
|
|
/// Returns a string representation of the query summary.
|
|
/// </summary>
|
|
public override string ToString()
|
|
{
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine($"Query #{Index}");
|
|
sb.AppendLine($" SELECT: {(HasSelectClause ? "Yes" : "No")} ({ColumnCount} columns)");
|
|
sb.AppendLine($" FROM: {(HasFromClause ? "Yes" : "No")}");
|
|
sb.AppendLine($" WHERE: {(HasWhereClause ? "Yes" : "No")}");
|
|
sb.AppendLine($" GROUP BY: {(HasGroupByClause ? "Yes" : "No")}");
|
|
sb.AppendLine($" HAVING: {(HasHavingClause ? "Yes" : "No")}");
|
|
sb.AppendLine($" ORDER BY: {(HasOrderByClause ? "Yes" : "No")}");
|
|
sb.AppendLine($" JOINs: {(HasJoins ? "Yes" : "No")} ({JoinCount} joins)");
|
|
sb.AppendLine($" CTEs: {(HasCTE ? "Yes" : "No")}");
|
|
sb.Append($" Parameters: {ParameterCount}");
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Report of parameter usage across 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.
|
|
/// </summary>
|
|
public override string ToString()
|
|
{
|
|
var usagePercentage = TotalQueries > 0 ? (UsedInQueryCount / (decimal)TotalQueries * 100) : 0;
|
|
return $"@{ParameterName}: {UsedInQueryCount}/{TotalQueries} queries ({usagePercentage:F1}%) - Value: {Value?.ToString() ?? "NULL"}";
|
|
}
|
|
}
|