using System.Text; namespace Strata.SqlTools.Breakdowns.SqlServer; /// /// SQL Server-specific collection for managing multiple QueryBreakdown objects. /// /// /// This class extends SqlBreakdownCollection with SQL Server-specific functionality, /// including support for T-SQL features like batches (GO), temporary tables, stored procedures, and CTEs. /// public class QueryBreakdownCollection : QueryBreakdownCollectionBase { /// /// Initializes a new instance of the class. /// public QueryBreakdownCollection() : base() { } /// /// Initializes a new instance of the class with initial query breakdowns. /// /// The initial collection of query breakdowns. public QueryBreakdownCollection(IEnumerable queryBreakdowns) : base(queryBreakdowns) { } /// /// Gets the SQL Server T-SQL batch representation with proper batch handling. /// /// /// Generates T-SQL with proper GO separators and optional transaction support. /// /// Whether to include setup and finish clauses. /// Whether to wrap in BEGIN TRANSACTION / COMMIT. /// The formatted T-SQL batch. 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(); } /// /// Filters query breakdowns that have WITH clauses (CTEs). /// /// Query breakdowns with CTE definitions. public IEnumerable WhereHaveCommonTableExpressions() { return QueryBreakdownList.Where(q => q.WithClauses.Count > 0); } /// /// Filters query breakdowns that reference JOIN operations. /// /// Query breakdowns with JOINs. public IEnumerable WhereHaveJoins() { return QueryBreakdownList.Where(q => q.GetSql().Contains("JOIN", StringComparison.OrdinalIgnoreCase)); } /// /// Filters query breakdowns by parameter usage. /// /// The parameter name to search for. /// Query breakdowns using the specified parameter. public IEnumerable WhereUseParameter(string parameterName) { if (string.IsNullOrWhiteSpace(parameterName)) { throw new ArgumentNullException(nameof(parameterName)); } return QueryBreakdownList.Where(q => q.ParameterList.Any(p => p.Name == parameterName)); } /// /// Adds a parameter to all queries in the collection. /// /// The parameter name. /// The parameter value. 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!; } } /// /// Gets a summary of all queries including their types and basic composition. /// /// Summary information for each query. public IEnumerable GetQuerySummaries() => QueryCollectionAnalysisHelper.GetQuerySummaries(QueryBreakdownList); /// /// Gets all T-SQL parameters as a formatted string suitable for SQL Server. /// /// Whether to include estimated data types (uses generic approach). /// A formatted string of parameters. 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(); } /// /// Gets a report of parameter usage across all queries. /// /// Parameter usage information. public IEnumerable GetParameterUsageReport() { return QueryCollectionAnalysisHelper.GetParameterUsage(QueryBreakdownList) .Select(usage => new ParameterUsageReport { ParameterName = usage.Name, Value = usage.Value, UsedInQueryCount = usage.UsedInQueryCount, TotalQueries = usage.TotalQueries }); } /// /// Helper method to get SQL Server data type from a .NET object. /// 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" }; } /// /// Helper method to format a parameter value for SQL output. /// 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 information about a query for quick analysis. /// public class QuerySummary { /// /// Gets or sets the index of the query in the collection. /// public int Index { get; set; } /// /// Gets or sets whether the query has a SELECT clause. /// public bool HasSelectClause { get; set; } /// /// Gets or sets whether the query has a FROM clause. /// public bool HasFromClause { get; set; } /// /// Gets or sets whether the query has a WHERE clause. /// public bool HasWhereClause { get; set; } /// /// Gets or sets whether the query has a GROUP BY clause. /// public bool HasGroupByClause { get; set; } /// /// Gets or sets whether the query has a HAVING clause. /// public bool HasHavingClause { get; set; } /// /// Gets or sets whether the query has an ORDER BY clause. /// public bool HasOrderByClause { get; set; } /// /// Gets or sets whether the query has JOIN clauses. /// public bool HasJoins { get; set; } /// /// Gets or sets whether the query has Common Table Expressions (CTEs). /// public bool HasCTE { get; set; } /// /// Gets or sets the number of columns in the SELECT clause. /// public int ColumnCount { get; set; } /// /// Gets or sets the number of parameters used. /// public int ParameterCount { get; set; } /// /// Gets or sets the number of JOIN clauses. /// public int JoinCount { get; set; } /// /// Returns a string representation of the query 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(); } } /// /// Report of parameter usage across queries in a collection. /// public class ParameterUsageReport { /// /// Gets or sets the parameter name. /// public string ParameterName { get; set; } = string.Empty; /// /// Gets or sets the parameter value. /// public object? Value { get; set; } /// /// Gets or sets the number of queries using this parameter. /// public int UsedInQueryCount { get; set; } /// /// Gets or sets the total number of queries in the collection. /// public int TotalQueries { get; set; } /// /// Gets whether the parameter is used in all queries. /// public bool IsUsedInAllQueries => UsedInQueryCount == TotalQueries; /// /// Returns a string representation of the parameter usage report. /// public override string ToString() { var usagePercentage = TotalQueries > 0 ? (UsedInQueryCount / (decimal)TotalQueries * 100) : 0; return $"@{ParameterName}: {UsedInQueryCount}/{TotalQueries} queries ({usagePercentage:F1}%) - Value: {Value?.ToString() ?? "NULL"}"; } }