Merge pull request 'refactor(dedup): IQueryBreakdownCollectionView eliminates Markdown adapter classes' (#23) from chore/sonarqube-src-dedup-final into main
SonarQube Analysis / sonarqube (push) Successful in 5m55s

Reviewed-on: #23
This commit was merged in pull request #23.
This commit is contained in:
2026-05-29 13:51:59 -05:00
10 changed files with 128 additions and 173 deletions
@@ -1,37 +1,39 @@
using Strata.SqlTools.Breakdowns.SqlServer;
namespace Strata.SqlTools.Markdown.Common;
/// <summary>
/// Shared method bodies for the per-dialect <c>QueryBreakdownCollectionGenerator</c>
/// classes. Each dialect wraps its <c>QueryBreakdownCollection</c> in an
/// <see cref="ICollectionMarkdownData"/> adapter and supplies a dialect-specific
/// <see cref="MarkdownDialectFormat"/>; this template handles the rest.
/// Shared method bodies for the per-dialect <c>QueryBreakdownCollectionGenerator</c> classes.
/// Operates against the dialect-neutral <see cref="IQueryBreakdownCollectionView"/> contract
/// that every dialect's <c>QueryBreakdownCollection</c> implements, so the dialect wrappers
/// can pass their collection straight through with no per-dialect adapter required.
/// </summary>
internal static class CollectionMarkdownGenerator
{
public static string GenerateCollectionReport(ICollectionMarkdownData data, MarkdownDialectFormat format, string? title)
public static string GenerateCollectionReport(IQueryBreakdownCollectionView view, MarkdownDialectFormat format, string? title)
=> CollectionReportWriter.CollectionReport(title, new[]
{
GenerateCollectionSummary(data),
GenerateParameterAnalysis(data, format),
GenerateQueryCompositionReport(data, format)
GenerateCollectionSummary(view),
GenerateParameterAnalysis(view, format),
GenerateQueryCompositionReport(view, format)
});
public static string GenerateCollectionSummary(ICollectionMarkdownData data)
public static string GenerateCollectionSummary(IQueryBreakdownCollectionView view)
=> CollectionReportWriter.CollectionSummary(
data.QueryCount,
data.UniqueParameterCount,
data.TotalSelectedColumns,
data.UniqueTableCount);
view.QueryCount,
view.UniqueParameterCount,
view.TotalSelectedColumns,
view.UniqueTableCount);
public static string GenerateParameterAnalysis(ICollectionMarkdownData data, MarkdownDialectFormat format)
=> CollectionReportWriter.ParameterAnalysis(data.ParameterRows.ToList(), data.QueriesForReport, format);
public static string GenerateParameterAnalysis(IQueryBreakdownCollectionView view, MarkdownDialectFormat format)
=> CollectionReportWriter.ParameterAnalysis(view.ParameterUsageRecords.ToList(), view.QueriesForReport, format);
public static string GenerateParameterDependencyDiagram(ICollectionMarkdownData data, MarkdownDialectFormat format)
=> CollectionReportWriter.ParameterDependencyDiagram(data.QueriesForReport, format);
public static string GenerateParameterDependencyDiagram(IQueryBreakdownCollectionView view, MarkdownDialectFormat format)
=> CollectionReportWriter.ParameterDependencyDiagram(view.QueriesForReport, format);
public static string GenerateQueryCompositionReport(ICollectionMarkdownData data, MarkdownDialectFormat format)
=> CollectionReportWriter.QueryCompositionReport(data.QueriesForReport, format);
public static string GenerateQueryCompositionReport(IQueryBreakdownCollectionView view, MarkdownDialectFormat format)
=> CollectionReportWriter.QueryCompositionReport(view.QueriesForReport, format);
public static string GenerateBatchFlowDiagram(ICollectionMarkdownData data, string? openLabel, string? closeLabel)
=> CollectionReportWriter.BatchFlowDiagram(data.QueryCount, openLabel, closeLabel);
public static string GenerateBatchFlowDiagram(IQueryBreakdownCollectionView view, string? openLabel, string? closeLabel)
=> CollectionReportWriter.BatchFlowDiagram(view.QueryCount, openLabel, closeLabel);
}
@@ -32,28 +32,6 @@ internal sealed class MarkdownDialectFormat
public required bool IncludeHavingRow { get; init; }
}
/// <summary>
/// Carries the precomputed usage statistics for a single parameter, decoupling the writer from each
/// dialect's concrete parameter-usage report type.
/// </summary>
internal sealed class ParameterUsageRow
{
/// <summary>The parameter name (without any dialect prefix).</summary>
public required string ParameterName { get; init; }
/// <summary>Whether the parameter is used by every query in the collection.</summary>
public required bool IsUsedInAllQueries { get; init; }
/// <summary>The number of queries that use the parameter.</summary>
public required int UsedInQueryCount { get; init; }
/// <summary>The total number of queries in the collection.</summary>
public required int TotalQueries { get; init; }
/// <summary>The parameter's last-seen value.</summary>
public required object? Value { get; init; }
}
/// <summary>
/// Renders the dialect-agnostic Markdown for query breakdown collection reports. The per-dialect
/// generators supply a <see cref="MarkdownDialectFormat"/> and precomputed data; this writer produces
@@ -113,7 +91,7 @@ internal static class CollectionReportWriter
/// Renders the parameter analysis section, including the dependency diagram.
/// </summary>
public static string ParameterAnalysis(
IReadOnlyList<ParameterUsageRow> parameters,
IReadOnlyList<ParameterUsageRecord> parameters,
IEnumerable<QueryBreakdown> queries,
MarkdownDialectFormat format)
{
@@ -1,30 +0,0 @@
using SqlServerBreakdowns = Strata.SqlTools.Breakdowns.SqlServer;
namespace Strata.SqlTools.Markdown.Common;
/// <summary>
/// Dialect-neutral view of a <c>QueryBreakdownCollection</c> as seen by the Markdown
/// generators. Each per-dialect <c>QueryBreakdownCollectionGenerator</c> wraps its
/// dialect-specific collection in an implementation of this interface so the shared
/// <see cref="CollectionMarkdownGenerator"/> template can operate uniformly.
/// </summary>
internal interface ICollectionMarkdownData
{
int QueryCount { get; }
int UniqueParameterCount { get; }
int TotalSelectedColumns { get; }
int UniqueTableCount { get; }
/// <summary>
/// Queries as the base <see cref="SqlServerBreakdowns.QueryBreakdown"/> type. Used
/// directly by <see cref="CollectionReportWriter.QueryCompositionReport"/>
/// (which needs <see cref="IReadOnlyList{T}"/>) and via covariance by the other
/// writer methods that accept <see cref="IEnumerable{T}"/>.
/// </summary>
IReadOnlyList<SqlServerBreakdowns.QueryBreakdown> QueriesForReport { get; }
/// <summary>
/// Parameter usage already mapped into the writer's dialect-agnostic row type.
/// </summary>
IEnumerable<ParameterUsageRow> ParameterRows { get; }
}
@@ -24,63 +24,41 @@ public static class QueryBreakdownCollectionGenerator
/// Generates a comprehensive collection report in Markdown format with PostgreSQL-specific information.
/// </summary>
public static string GenerateCollectionReport(QueryBreakdownCollection collection, string? title = null)
=> CollectionMarkdownGenerator.GenerateCollectionReport(Adapt(collection), Format, title);
=> CollectionMarkdownGenerator.GenerateCollectionReport(collection, Format, title);
/// <summary>
/// Generates a summary section for the collection.
/// </summary>
public static string GenerateCollectionSummary(QueryBreakdownCollection collection)
=> CollectionMarkdownGenerator.GenerateCollectionSummary(Adapt(collection));
=> CollectionMarkdownGenerator.GenerateCollectionSummary(collection);
/// <summary>
/// Generates a parameter analysis report with PostgreSQL parameter syntax support.
/// </summary>
public static string GenerateParameterAnalysis(QueryBreakdownCollection collection)
=> CollectionMarkdownGenerator.GenerateParameterAnalysis(Adapt(collection), Format);
=> CollectionMarkdownGenerator.GenerateParameterAnalysis(collection, Format);
/// <summary>
/// Generates a Mermaid diagram showing parameter dependencies across queries.
/// </summary>
public static string GenerateParameterDependencyDiagram(QueryBreakdownCollection collection)
=> CollectionMarkdownGenerator.GenerateParameterDependencyDiagram(Adapt(collection), Format);
=> CollectionMarkdownGenerator.GenerateParameterDependencyDiagram(collection, Format);
/// <summary>
/// Generates a detailed query composition report.
/// </summary>
public static string GenerateQueryCompositionReport(QueryBreakdownCollection collection)
=> CollectionMarkdownGenerator.GenerateQueryCompositionReport(Adapt(collection), Format);
=> CollectionMarkdownGenerator.GenerateQueryCompositionReport(collection, Format);
/// <summary>
/// Generates a batch execution flow diagram for PostgreSQL.
/// </summary>
public static string GenerateBatchFlowDiagram(QueryBreakdownCollection collection, bool includeTransaction = false)
=> CollectionMarkdownGenerator.GenerateBatchFlowDiagram(
Adapt(collection),
collection,
includeTransaction ? "BEGIN" : null,
includeTransaction ? "COMMIT" : null);
private static ICollectionMarkdownData Adapt(QueryBreakdownCollection collection) => new Adapter(collection);
private sealed class Adapter : ICollectionMarkdownData
{
private readonly QueryBreakdownCollection _c;
public Adapter(QueryBreakdownCollection c) { _c = c; }
public int QueryCount => _c.QueryBreakdowns.Count;
public int UniqueParameterCount => _c.GetAllUniqueParameters().Count();
public int TotalSelectedColumns => _c.GetTotalSelectedColumns();
public int UniqueTableCount => _c.GetUniqueTableReferences().Count();
public IReadOnlyList<Breakdowns.SqlServer.QueryBreakdown> QueriesForReport => _c.QueryBreakdowns;
public IEnumerable<ParameterUsageRow> ParameterRows
=> _c.GetParameterUsageReport().Select(p => new ParameterUsageRow
{
ParameterName = p.ParameterName,
IsUsedInAllQueries = p.IsUsedInAllQueries,
UsedInQueryCount = p.UsedInQueryCount,
TotalQueries = p.TotalQueries,
Value = p.Value
});
}
/// <summary>
/// Formats a parameter using PostgreSQL syntax: <c>$n</c> for positional, <c>:name</c> for named.
/// </summary>
@@ -24,9 +24,6 @@ public static class QueryBreakdownCollectionGenerator
/// <summary>
/// Generates a comprehensive collection report in Markdown format with Snowflake-specific information.
/// </summary>
/// <param name="collection">The QueryBreakdownCollection to document.</param>
/// <param name="title">Optional title for the report.</param>
/// <returns>A string containing the Markdown documentation.</returns>
public static string GenerateCollectionReport(QueryBreakdownCollection collection, string? title = null)
=> CollectionReportWriter.CollectionReport(title, new[]
{
@@ -39,20 +36,12 @@ public static class QueryBreakdownCollectionGenerator
/// <summary>
/// Generates a summary section for the collection.
/// </summary>
/// <param name="collection">The QueryBreakdownCollection to summarize.</param>
/// <returns>Markdown summary section.</returns>
public static string GenerateCollectionSummary(QueryBreakdownCollection collection)
=> CollectionReportWriter.CollectionSummary(
collection.QueryBreakdowns.Count,
collection.GetAllUniqueParameters().Count(),
collection.GetTotalSelectedColumns(),
collection.GetUniqueTableReferences().Count());
=> CollectionMarkdownGenerator.GenerateCollectionSummary(collection);
/// <summary>
/// Generates a Snowflake-specific features analysis section.
/// </summary>
/// <param name="collection">The QueryBreakdownCollection to analyze.</param>
/// <returns>Markdown Snowflake features section.</returns>
public static string GenerateSnowflakeFeaturesAnalysis(QueryBreakdownCollection collection)
{
var sb = new StringBuilder();
@@ -73,24 +62,18 @@ public static class QueryBreakdownCollectionGenerator
/// <summary>
/// Generates a parameter analysis report with Snowflake parameter syntax support.
/// </summary>
/// <param name="collection">The QueryBreakdownCollection to analyze.</param>
/// <returns>Markdown parameter analysis section.</returns>
public static string GenerateParameterAnalysis(QueryBreakdownCollection collection)
=> CollectionReportWriter.ParameterAnalysis(MapParameters(collection), collection.QueryBreakdowns, Format);
=> CollectionMarkdownGenerator.GenerateParameterAnalysis(collection, Format);
/// <summary>
/// Generates a Mermaid diagram showing parameter dependencies across queries.
/// </summary>
/// <param name="collection">The QueryBreakdownCollection to visualize.</param>
/// <returns>Mermaid diagram markdown.</returns>
public static string GenerateParameterDependencyDiagram(QueryBreakdownCollection collection)
=> CollectionReportWriter.ParameterDependencyDiagram(collection.QueryBreakdowns, Format);
=> CollectionMarkdownGenerator.GenerateParameterDependencyDiagram(collection, Format);
/// <summary>
/// Generates a detailed query composition report with Snowflake-specific information.
/// </summary>
/// <param name="collection">The QueryBreakdownCollection to report on.</param>
/// <returns>Markdown composition report section.</returns>
public static string GenerateQueryCompositionReport(QueryBreakdownCollection collection)
{
var stageQueries = collection.WhereUseStageReference().ToList();
@@ -105,30 +88,12 @@ public static class QueryBreakdownCollectionGenerator
/// <summary>
/// Generates a batch execution flow diagram for Snowflake.
/// </summary>
/// <param name="collection">The QueryBreakdownCollection to visualize.</param>
/// <param name="includeSessionSetup">Whether to show session setup statements.</param>
/// <returns>Mermaid diagram markdown.</returns>
public static string GenerateBatchFlowDiagram(QueryBreakdownCollection collection, bool includeSessionSetup = false)
=> CollectionReportWriter.BatchFlowDiagram(
collection.QueryBreakdowns.Count,
=> CollectionMarkdownGenerator.GenerateBatchFlowDiagram(
collection,
includeSessionSetup ? "Session Setup" : null,
closeLabel: null);
/// <summary>
/// Maps the collection's parameter usage report into the writer's dialect-agnostic rows.
/// </summary>
private static List<ParameterUsageRow> MapParameters(QueryBreakdownCollection collection)
=> collection.GetParameterUsageReport()
.Select(p => new ParameterUsageRow
{
ParameterName = p.ParameterName,
IsUsedInAllQueries = p.IsUsedInAllQueries,
UsedInQueryCount = p.UsedInQueryCount,
TotalQueries = p.TotalQueries,
Value = p.Value
})
.ToList();
/// <summary>
/// Appends Snowflake-specific feature information for a query.
/// </summary>
@@ -23,63 +23,41 @@ public static class QueryBreakdownCollectionGenerator
/// Generates a comprehensive collection report in Markdown format.
/// </summary>
public static string GenerateCollectionReport(QueryBreakdownCollection collection, string? title = null)
=> CollectionMarkdownGenerator.GenerateCollectionReport(Adapt(collection), Format, title);
=> CollectionMarkdownGenerator.GenerateCollectionReport(collection, Format, title);
/// <summary>
/// Generates a summary section for the collection.
/// </summary>
public static string GenerateCollectionSummary(QueryBreakdownCollection collection)
=> CollectionMarkdownGenerator.GenerateCollectionSummary(Adapt(collection));
=> CollectionMarkdownGenerator.GenerateCollectionSummary(collection);
/// <summary>
/// Generates a parameter analysis report.
/// </summary>
public static string GenerateParameterAnalysis(QueryBreakdownCollection collection)
=> CollectionMarkdownGenerator.GenerateParameterAnalysis(Adapt(collection), Format);
=> CollectionMarkdownGenerator.GenerateParameterAnalysis(collection, Format);
/// <summary>
/// Generates a Mermaid diagram showing parameter dependencies across queries.
/// </summary>
public static string GenerateParameterDependencyDiagram(QueryBreakdownCollection collection)
=> CollectionMarkdownGenerator.GenerateParameterDependencyDiagram(Adapt(collection), Format);
=> CollectionMarkdownGenerator.GenerateParameterDependencyDiagram(collection, Format);
/// <summary>
/// Generates a detailed query composition report.
/// </summary>
public static string GenerateQueryCompositionReport(QueryBreakdownCollection collection)
=> CollectionMarkdownGenerator.GenerateQueryCompositionReport(Adapt(collection), Format);
=> CollectionMarkdownGenerator.GenerateQueryCompositionReport(collection, Format);
/// <summary>
/// Generates a batch execution flow diagram.
/// </summary>
public static string GenerateBatchFlowDiagram(QueryBreakdownCollection collection, bool includeTransaction = false)
=> CollectionMarkdownGenerator.GenerateBatchFlowDiagram(
Adapt(collection),
collection,
includeTransaction ? "BEGIN TRANSACTION" : null,
includeTransaction ? "COMMIT TRANSACTION" : null);
private static ICollectionMarkdownData Adapt(QueryBreakdownCollection collection) => new Adapter(collection);
private sealed class Adapter : ICollectionMarkdownData
{
private readonly QueryBreakdownCollection _c;
public Adapter(QueryBreakdownCollection c) { _c = c; }
public int QueryCount => _c.QueryBreakdowns.Count;
public int UniqueParameterCount => _c.GetAllUniqueParameters().Count();
public int TotalSelectedColumns => _c.GetTotalSelectedColumns();
public int UniqueTableCount => _c.GetUniqueTableReferences().Count();
public IReadOnlyList<QueryBreakdown> QueriesForReport => _c.QueryBreakdowns;
public IEnumerable<ParameterUsageRow> ParameterRows
=> _c.GetParameterUsageReport().Select(p => new ParameterUsageRow
{
ParameterName = p.ParameterName,
IsUsedInAllQueries = p.IsUsedInAllQueries,
UsedInQueryCount = p.UsedInQueryCount,
TotalQueries = p.TotalQueries,
Value = p.Value
});
}
/// <summary>
/// Gets the parameter type name from a parameter value.
/// </summary>
@@ -12,7 +12,7 @@ namespace Strata.SqlTools.Breakdowns.PostgreSql;
/// including support for PostgreSQL features like schema-qualified identifiers,
/// LIMIT/OFFSET clauses, parameterized queries using $1, $2 syntax, and CTEs.
/// </remarks>
public class QueryBreakdownCollection : SqlBreakdownCollection
public class QueryBreakdownCollection : SqlBreakdownCollection, SqlServer.IQueryBreakdownCollectionView
{
private readonly List<QueryBreakdown> _queryBreakdowns;
@@ -199,6 +199,17 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
TotalQueries = usage.TotalQueries
});
}
// --- IQueryBreakdownCollectionView ---
int SqlServer.IQueryBreakdownCollectionView.QueryCount => QueryBreakdowns.Count;
int SqlServer.IQueryBreakdownCollectionView.UniqueParameterCount => GetAllUniqueParameters().Count();
int SqlServer.IQueryBreakdownCollectionView.TotalSelectedColumns => GetTotalSelectedColumns();
int SqlServer.IQueryBreakdownCollectionView.UniqueTableCount => GetUniqueTableReferences().Count();
IReadOnlyList<SqlServer.QueryBreakdown> SqlServer.IQueryBreakdownCollectionView.QueriesForReport => QueryBreakdowns;
IEnumerable<SqlServer.ParameterUsageRecord> SqlServer.IQueryBreakdownCollectionView.ParameterUsageRecords
=> GetParameterUsageReport().Select(p => new SqlServer.ParameterUsageRecord(
p.ParameterName, p.Value, p.UsedInQueryCount, p.TotalQueries, p.IsUsedInAllQueries));
}
/// <summary>
@@ -13,7 +13,7 @@ namespace Strata.SqlTools.Breakdowns.Snowflake;
/// time travel, snowflake-specific parameters (:parameter and @parameter syntax),
/// and proper batch handling.
/// </remarks>
public class QueryBreakdownCollection : SqlServer.QueryBreakdownCollectionBase<QueryBreakdown>
public class QueryBreakdownCollection : SqlServer.QueryBreakdownCollectionBase<QueryBreakdown>, SqlServer.IQueryBreakdownCollectionView
{
/// <summary>
/// Initializes a new instance of the <see cref="QueryBreakdownCollection"/> class for Snowflake.
@@ -406,6 +406,17 @@ public class QueryBreakdownCollection : SqlServer.QueryBreakdownCollectionBase<Q
});
}
// --- IQueryBreakdownCollectionView ---
int SqlServer.IQueryBreakdownCollectionView.QueryCount => QueryBreakdowns.Count;
int SqlServer.IQueryBreakdownCollectionView.UniqueParameterCount => GetAllUniqueParameters().Count();
int SqlServer.IQueryBreakdownCollectionView.TotalSelectedColumns => GetTotalSelectedColumns();
int SqlServer.IQueryBreakdownCollectionView.UniqueTableCount => GetUniqueTableReferences().Count();
IReadOnlyList<SqlServer.QueryBreakdown> SqlServer.IQueryBreakdownCollectionView.QueriesForReport => QueryBreakdowns;
IEnumerable<SqlServer.ParameterUsageRecord> SqlServer.IQueryBreakdownCollectionView.ParameterUsageRecords
=> GetParameterUsageReport().Select(p => new SqlServer.ParameterUsageRecord(
p.ParameterName, p.Value, p.UsedInQueryCount, p.TotalQueries, p.IsUsedInAllQueries));
/// <summary>
/// Gets a summary of all queries including their types and basic composition.
/// </summary>
@@ -0,0 +1,51 @@
namespace Strata.SqlTools.Breakdowns.SqlServer;
/// <summary>
/// Dialect-neutral view of a query breakdown collection. Exposes the metrics and the per-query
/// access surface that the Markdown report generators need — implemented by each dialect's
/// <c>QueryBreakdownCollection</c> so the Markdown layer can operate without dialect-specific
/// adapters.
/// </summary>
/// <remarks>
/// <see cref="QueriesForReport"/> is typed against the SqlServer <see cref="QueryBreakdown"/>
/// base; PostgreSQL and Snowflake collections satisfy this via <see cref="IReadOnlyList{T}"/>
/// covariance because their dialect-specific <c>QueryBreakdown</c> classes derive from this one.
/// </remarks>
public interface IQueryBreakdownCollectionView
{
/// <summary>Number of queries in the collection.</summary>
int QueryCount { get; }
/// <summary>Number of unique parameter names across all queries.</summary>
int UniqueParameterCount { get; }
/// <summary>Number of columns selected across all queries.</summary>
int TotalSelectedColumns { get; }
/// <summary>Number of distinct table references across all queries.</summary>
int UniqueTableCount { get; }
/// <summary>The queries, as the shared <see cref="QueryBreakdown"/> base type.</summary>
IReadOnlyList<QueryBreakdown> QueriesForReport { get; }
/// <summary>Per-parameter usage records, mapped to the dialect-neutral
/// <see cref="ParameterUsageRecord"/> type.</summary>
IEnumerable<ParameterUsageRecord> ParameterUsageRecords { get; }
}
/// <summary>
/// Dialect-neutral, immutable record describing how often a parameter is used across the queries
/// in a collection. Each dialect's <c>ParameterUsageReport</c> projects into this type so the
/// Markdown layer can present usage tables without knowing about dialect-specific shapes.
/// </summary>
/// <param name="ParameterName">The parameter name (no dialect prefix).</param>
/// <param name="Value">The parameter's last-seen value.</param>
/// <param name="UsedInQueryCount">How many queries reference this parameter.</param>
/// <param name="TotalQueries">Total queries in the collection.</param>
/// <param name="IsUsedInAllQueries">Whether every query uses the parameter.</param>
public sealed record ParameterUsageRecord(
string ParameterName,
object? Value,
int UsedInQueryCount,
int TotalQueries,
bool IsUsedInAllQueries);
@@ -9,7 +9,7 @@ namespace Strata.SqlTools.Breakdowns.SqlServer;
/// 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>
public class QueryBreakdownCollection : QueryBreakdownCollectionBase<QueryBreakdown>, IQueryBreakdownCollectionView
{
/// <summary>
/// Initializes a new instance of the <see cref="QueryBreakdownCollection"/> class.
@@ -187,6 +187,17 @@ public class QueryBreakdownCollection : QueryBreakdownCollectionBase<QueryBreakd
});
}
// --- IQueryBreakdownCollectionView ---
int IQueryBreakdownCollectionView.QueryCount => QueryBreakdowns.Count;
int IQueryBreakdownCollectionView.UniqueParameterCount => GetAllUniqueParameters().Count();
int IQueryBreakdownCollectionView.TotalSelectedColumns => GetTotalSelectedColumns();
int IQueryBreakdownCollectionView.UniqueTableCount => GetUniqueTableReferences().Count();
IReadOnlyList<QueryBreakdown> IQueryBreakdownCollectionView.QueriesForReport => QueryBreakdowns;
IEnumerable<ParameterUsageRecord> IQueryBreakdownCollectionView.ParameterUsageRecords
=> GetParameterUsageReport().Select(p => new ParameterUsageRecord(
p.ParameterName, p.Value, p.UsedInQueryCount, p.TotalQueries, p.IsUsedInAllQueries));
/// <summary>
/// Helper method to get SQL Server data type from a .NET object.
/// </summary>