using System.Runtime.Serialization; using System.Text; using Strata.SqlTools.SqlBreakdown.Classes; using Strata.SqlTools.SqlBreakdown.Interfaces; namespace Strata.SqlTools.Breakdowns.Snowflake; /// /// Snowflake SQL-specific collection for managing multiple QueryBreakdown objects. /// /// /// This class extends SqlBreakdownCollection with Snowflake-specific functionality, /// including support for Snowflake features like semi-structured data, stage references, /// time travel, snowflake-specific parameters (:parameter and @parameter syntax), /// and proper batch handling. /// [Serializable] public class QueryBreakdownCollection : SqlBreakdownCollection { private readonly List _queryBreakdowns; /// /// Initializes a new instance of the class for Snowflake. /// public QueryBreakdownCollection() : base() { _queryBreakdowns = new List(); } /// /// Initializes a new instance of the class with initial query breakdowns. /// /// The initial collection of query breakdowns. public QueryBreakdownCollection(IEnumerable queryBreakdowns) : base(queryBreakdowns?.Cast() ?? Enumerable.Empty()) { _queryBreakdowns = new List(queryBreakdowns ?? Enumerable.Empty()); } /// /// Validates that the backing list survived deserialization, since deserialization bypasses /// the constructors that normally initialize it (SonarQube rule S5766). /// /// The streaming context for the deserialization operation. [OnDeserialized] private void OnDeserialized(StreamingContext context) { if (_queryBreakdowns is null) { throw new SerializationException("Deserialized QueryBreakdownCollection is missing its backing list."); } } /// /// Gets the collection of QueryBreakdown objects. /// public IReadOnlyList QueryBreakdowns => _queryBreakdowns.AsReadOnly(); /// /// Adds a QueryBreakdown to the collection. /// /// The query breakdown to add. /// Thrown when queryBreakdown is null. public void Add(QueryBreakdown queryBreakdown) { if (queryBreakdown == null) { throw new ArgumentNullException(nameof(queryBreakdown)); } _queryBreakdowns.Add(queryBreakdown); base.Add(queryBreakdown); } /// /// Adds multiple QueryBreakdowns to the collection. /// /// The query breakdowns to add. /// Thrown when queryBreakdowns is null. public void AddRange(IEnumerable queryBreakdowns) { if (queryBreakdowns == null) { throw new ArgumentNullException(nameof(queryBreakdowns)); } foreach (var breakdown in queryBreakdowns) { Add(breakdown); } } /// /// Removes a QueryBreakdown from the collection. /// /// The query breakdown to remove. /// True if removed; otherwise, false. public bool Remove(QueryBreakdown queryBreakdown) { var removed = _queryBreakdowns.Remove(queryBreakdown); if (removed) { base.Remove(queryBreakdown); } return removed; } /// /// Clears all query breakdowns from the collection. /// public new void Clear() { _queryBreakdowns.Clear(); base.Clear(); } /// /// Gets the Snowflake SQL batch representation with Snowflake-specific formatting. /// /// /// Generates Snowflake SQL with proper statement separation and optional session setup. /// Snowflake uses semicolons as statement separators instead of GO. /// /// Whether to include setup and finish clauses. /// Whether to include session context setup statements. /// The formatted Snowflake SQL batch. public string GetSnowflakeBatch(bool includeSetupFinish = true, bool includeSessionSetup = false) { var sb = new StringBuilder(); // Add session setup if requested if (includeSessionSetup) { sb.AppendLine("-- Snowflake Session Setup"); sb.AppendLine("ALTER SESSION SET NULLABLE_AS_NULL = FALSE;"); sb.AppendLine("ALTER SESSION SET ERROR_ON_NONDETERMINISTIC_UPDATE = FALSE;"); sb.AppendLine(); } // Add all queries with semicolon separators if (_queryBreakdowns.Count > 0) { for (int i = 0; i < _queryBreakdowns.Count; i++) { var query = _queryBreakdowns[i]; var sql = query.GetSql(includeSetupFinish); // Ensure proper termination var trimmed = sql.TrimEnd(); sb.Append(trimmed); if (!trimmed.EndsWith(';')) { sb.Append(";"); } // Add spacing between statements if (i < _queryBreakdowns.Count - 1) { sb.AppendLine(); sb.AppendLine(); } } } return sb.ToString(); } /// /// Filters queries that reference Snowflake stages (using @ or @~ syntax). /// /// /// Stage references use the pattern @stage_name/ or @~/stage_name/. /// This specifically matches stage references and avoids false positives from @parameter syntax. /// /// Optional stage name to filter by. If null, returns all queries using any stage. /// Query breakdowns that reference stages. public IEnumerable WhereUseStageReference(string? stageName = null) { return _queryBreakdowns.Where(q => { var sql = q.GetSql(); // Use regex to match stage references: @stage_name/ or @~/stage_name/ // This avoids false positives from @parameter syntax var stagePattern = @"@[\w~]+/"; if (!System.Text.RegularExpressions.Regex.IsMatch(sql, stagePattern, System.Text.RegularExpressions.RegexOptions.None, Strata.SqlTools.SqlBreakdown.Utilities.RegexDefaults.MatchTimeout)) { return false; } if (stageName == null) { return true; } var specificPattern = stageName.Contains("~") ? $@"@~/{System.Text.RegularExpressions.Regex.Escape(stageName.TrimStart('@', '~', '/'))}/" : $@"@{System.Text.RegularExpressions.Regex.Escape(stageName.TrimStart('@'))}/"; return System.Text.RegularExpressions.Regex.IsMatch(sql, specificPattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase, Strata.SqlTools.SqlBreakdown.Utilities.RegexDefaults.MatchTimeout); }); } /// /// Filters queries that reference JSON/semi-structured data using Snowflake's JSON operators. /// /// Query breakdowns that use JSON functions or colon notation. public IEnumerable WhereUseSemiStructuredData() { return _queryBreakdowns.Where(q => { var sql = q.GetSql().ToUpperInvariant(); // Check for JSON functions or colon notation used in semi-structured data return sql.Contains("JSON_") || sql.Contains("OBJECT_") || sql.Contains("ARRAY_") || sql.Contains("FLATTEN(") || sql.Contains(":VALUE") || sql.Contains(":NAME") || sql.Contains(":TYPE"); }); } /// /// Filters queries that use Snowflake-specific parameter syntax (:param or @param). /// /// The parameter name (with or without : or @). /// Query breakdowns using the specified Snowflake parameter. public IEnumerable WhereUseSnowflakeParameter(string parameterName) { if (string.IsNullOrWhiteSpace(parameterName)) { throw new ArgumentNullException(nameof(parameterName)); } // Normalize parameter name (remove : or @) var cleanName = parameterName.TrimStart(':', '@'); return _queryBreakdowns.Where(q => { var sql = q.GetSql(); return sql.Contains($":{cleanName}", StringComparison.OrdinalIgnoreCase) || sql.Contains($"@{cleanName}", StringComparison.OrdinalIgnoreCase); }); } /// /// Filters queries that use Snowflake time travel features. /// /// /// Detects use of BEFORE, AT, or MATCH_CONDITION clauses for time travel queries. /// /// Query breakdowns using time travel syntax. public IEnumerable WhereUseTimeTravelFeature() { return _queryBreakdowns.Where(q => { var sql = q.GetSql().ToUpperInvariant(); return sql.Contains("BEFORE (") || sql.Contains("AT (") || sql.Contains("MATCH_CONDITION"); }); } /// /// Filters queries that use Snowflake functions (PARSE_JSON, OBJECT_INSERT, ARRAY, etc.). /// /// Query breakdowns using Snowflake-specific functions. public IEnumerable WhereUseSnowflakeFunctions() { return _queryBreakdowns.Where(q => { var sql = q.GetSql().ToUpperInvariant(); var snowflakeFunctions = new[] { "PARSE_JSON", "OBJECT_INSERT", "ARRAY_CONSTRUCT", "ARRAY_AGG", "FLATTEN", "GET_PATH", "TRY_PARSE_JSON", "JSON_EXTRACT_PATH_TEXT", "JSON_EXTRACT_PATH_WITH_DEFAULT", "HASHAGGREGATE", "LISTAGG", "APPROX_COUNT_DISTINCT", "APPROX_PERCENTILE", "GREATEST", "LEAST", "NULLIF", "ZEROIFNULL", "STRTOK", "SPLIT_PART", "PIVOT", "UNPIVOT" }; return snowflakeFunctions.Any(func => sql.Contains(func)); }); } /// /// Filters queries that reference temporary or dynamic tables. /// /// Query breakdowns using temporary tables. public IEnumerable WhereUseTemporaryTables() { return _queryBreakdowns.Where(q => { var sql = q.GetSql().ToUpperInvariant(); return sql.Contains("TEMPORARY TABLE") || sql.Contains("TEMP TABLE") || sql.Contains("CREATE TEMP ") || sql.Contains("DYNAMIC TABLE"); }); } /// /// Filters queries that reference external tables or stages. /// /// Query breakdowns using external data sources. public IEnumerable WhereUseExternalData() { return _queryBreakdowns.Where(q => { var sql = q.GetSql().ToUpperInvariant(); return sql.Contains("EXTERNAL TABLE") || sql.Contains(" FROM @") || sql.Contains("COPY INTO @"); }); } /// /// Filters queries by the SELECT clause content using Snowflake's format. /// /// The text to find in the SELECT clause. /// Filtered query breakdowns. public IEnumerable WhereSelectContains(string selectContains) { if (string.IsNullOrWhiteSpace(selectContains)) { throw new ArgumentNullException(nameof(selectContains)); } return _queryBreakdowns.Where(q => q.SelectClause?.Clause?.Contains(selectContains, StringComparison.OrdinalIgnoreCase) ?? false); } /// /// Filters queries that reference specific tables or schemas. /// /// The table name or schema pattern to find. /// Filtered query breakdowns. public IEnumerable WhereTableContains(string tableNameContains) { if (string.IsNullOrWhiteSpace(tableNameContains)) { throw new ArgumentNullException(nameof(tableNameContains)); } return _queryBreakdowns.Where(q => q.FromClause?.Clause?.Contains(tableNameContains, StringComparison.OrdinalIgnoreCase) ?? false); } /// /// Filters queries that have WHERE clauses. /// /// Query breakdowns with WHERE clauses. public IEnumerable WhereHaveWhereClause() { return _queryBreakdowns.Where(q => !string.IsNullOrWhiteSpace(q.WhereClause?.Clause)); } /// /// Filters queries without WHERE clauses (potentially risky for full table scans). /// /// Query breakdowns without WHERE clauses. public IEnumerable WhereHaveNoWhereClause() { return _queryBreakdowns.Where(q => string.IsNullOrWhiteSpace(q.WhereClause?.Clause)); } /// /// Filters queries that have GROUP BY clauses. /// /// Query breakdowns with GROUP BY clauses. public IEnumerable WhereHaveGroupByClause() { return _queryBreakdowns.Where(q => !string.IsNullOrWhiteSpace(q.GroupByClause?.Clause)); } /// /// Filters queries that have ORDER BY clauses. /// /// Query breakdowns with ORDER BY clauses. public IEnumerable WhereHaveOrderByClause() { return _queryBreakdowns.Where(q => !string.IsNullOrWhiteSpace(q.OrderByClause?.Clause)); } /// /// Gets a comprehensive analysis of all queries in the collection. /// /// Analysis summary for each query. public IEnumerable AnalyzeQueries() { return _queryBreakdowns.Select((q, index) => new SnowflakeQueryAnalysis { 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), HasOrderByClause = !string.IsNullOrWhiteSpace(q.OrderByClause?.Clause), UsesSemiStructuredData = UseSemiStructuredData(q), UsesStageReference = UsesStageReference(q), UsesTimeTravelFeature = UsesTimeTravelFeature(q), UsesSnowflakeFunctions = UsesSnowflakeFunctions(q), UsesExternalData = UsesExternalData(q), ColumnCount = !string.IsNullOrWhiteSpace(q.SelectClause?.Clause) ? q.SelectClause.Clause.Split(',').Length : 0, ParameterCount = q.ParameterList.Count() }); } /// /// Gets the combined Snowflake SQL from all breakdowns. /// /// Whether to include setup and finish clauses. /// The combined Snowflake SQL. public string GetCombinedSql(bool includeSetupFinish = true) { return GetSnowflakeBatch(includeSetupFinish); } /// /// Helper method to check if a query uses semi-structured data. /// private static bool UseSemiStructuredData(QueryBreakdown query) { var sql = query.GetSql().ToUpperInvariant(); return sql.Contains("JSON_") || sql.Contains("OBJECT_") || sql.Contains("ARRAY_") || sql.Contains("FLATTEN("); } /// /// Helper method to check if a query uses stage references. /// private static bool UsesStageReference(QueryBreakdown query) { return query.GetSql().Contains("@") && (query.GetSql().Contains("FROM @") || query.GetSql().Contains(" @")); } /// /// Helper method to check if a query uses time travel features. /// private static bool UsesTimeTravelFeature(QueryBreakdown query) { var sql = query.GetSql().ToUpperInvariant(); return sql.Contains("BEFORE (") || sql.Contains("AT (") || sql.Contains("MATCH_CONDITION"); } /// /// Helper method to check if a query uses Snowflake-specific functions. /// private static bool UsesSnowflakeFunctions(QueryBreakdown query) { var sql = query.GetSql().ToUpperInvariant(); var snowflakeFunctions = new[] { "PARSE_JSON", "OBJECT_INSERT", "ARRAY_CONSTRUCT", "FLATTEN", "LISTAGG", "APPROX_COUNT_DISTINCT", "HASH", "ZEROIFNULL" }; return snowflakeFunctions.Any(func => sql.Contains(func)); } /// /// Helper method to check if a query uses external data. /// private static bool UsesExternalData(QueryBreakdown query) { var sql = query.GetSql().ToUpperInvariant(); return sql.Contains("EXTERNAL TABLE") || sql.Contains(" FROM @") || sql.Contains("COPY INTO @"); } /// /// Synchronizes parameter values across all queries in the collection. /// Ensures that if a parameter with the same name exists in multiple queries, they all have the same value. /// public void SynchronizeParameters() { // Get all unique parameter names across all queries var allParameterNames = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var query in _queryBreakdowns) { foreach (var paramName in query.Parameters.Keys) { allParameterNames.Add(paramName); } } // For each parameter, use the last query's value and sync to all queries that have it foreach (var paramName in allParameterNames) { object? lastValue = null; bool parameterFound = false; // Find the last query that has this parameter and get its value for (int i = _queryBreakdowns.Count - 1; i >= 0; i--) { if (_queryBreakdowns[i].Parameters.ContainsKey(paramName)) { lastValue = _queryBreakdowns[i].Parameters[paramName]; parameterFound = true; break; } } // Synchronize the parameter value to all queries that have it if (parameterFound) { foreach (var query in _queryBreakdowns.Where(q => q.Parameters.ContainsKey(paramName))) { query.Parameters[paramName] = lastValue!; } } } } /// /// Adds a parameter with a specific value to all queries in the collection. /// /// The name of the parameter (without the : or @ prefix). /// The value to assign to the parameter. Can be null. public void AddParameterToAll(string parameterName, object? value) { if (string.IsNullOrWhiteSpace(parameterName)) { throw new ArgumentException("Parameter name cannot be null or empty.", nameof(parameterName)); } foreach (var query in _queryBreakdowns) { query.Parameters[parameterName] = value!; } } /// /// Gets all unique parameters from all queries in the collection as a combined dictionary. /// /// A dictionary containing all unique parameters across all queries. protected Dictionary GetCombinedParameterDictionary() { var combinedParams = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var query in _queryBreakdowns) { // Add from ParameterList (parsed parameters) foreach (var param in query.ParameterList) { combinedParams[param.Name] = param.Value; } // Add/override from Parameters dictionary (manually added parameters) foreach (var param in query.Parameters) { combinedParams[param.Key] = param.Value; } } return combinedParams; } /// /// Gets a formatted string representation of all unique parameters with Snowflake-specific syntax. /// /// If true, includes Snowflake data types in the output format. /// /// A formatted string such as ":paramName = value" or ":paramName = value -- VARIANT" /// for each unique parameter. /// public string GetParametersAsString(bool includeDataTypes = false) { var parameters = GetCombinedParameterDictionary(); if (parameters.Count == 0) { return string.Empty; } var sb = new StringBuilder(); var isFirst = true; foreach (var param in parameters.OrderBy(p => p.Key, StringComparer.OrdinalIgnoreCase)) { if (!isFirst) { sb.AppendLine(","); } sb.Append($":{param.Key} = {FormatParameterValue(param.Value)}"); if (includeDataTypes) { var dataType = GetSnowflakeDataType(param.Value); sb.Append($" -- {dataType}"); } isFirst = false; } return sb.ToString(); } /// /// Gets a detailed usage report for all parameters across the queries in the collection. /// /// An enumerable of ParameterUsageReport objects with usage statistics. /// /// Gets a report of parameter usage across all queries. /// /// Parameter usage information. public IEnumerable GetParameterUsageReport() { // Collect all unique parameter names from both ParameterList and Parameters dictionary var allParamNames = new HashSet(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 }; } } /// /// Gets the total number of columns selected across all queries. /// /// Total column count. public int GetTotalSelectedColumns() { return _queryBreakdowns.Sum(q => !string.IsNullOrWhiteSpace(q.SelectClause?.Clause) ? q.SelectClause.Clause.Split(',').Length : 0); } /// /// Gets all unique table names referenced across all queries. /// /// /// 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. /// /// List of unique table names. public IEnumerable GetUniqueTableReferences() { var tables = new HashSet(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; } /// /// Gets a summary of all queries including their types and basic composition. /// /// Summary information for each query. public IEnumerable GetQuerySummaries() { var stageQueries = WhereUseStageReference().ToHashSet(); var semiStructuredQueries = WhereUseSemiStructuredData().ToHashSet(); return _queryBreakdowns.Select((q, index) => new SnowflakeQueryAnalysis { 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), HasOrderByClause = !string.IsNullOrWhiteSpace(q.OrderByClause?.Clause), HasCTE = q.WithClauses.Count > 0, UsesSemiStructuredData = semiStructuredQueries.Contains(q), UsesStageReference = stageQueries.Contains(q), UsesTimeTravelFeature = UsesTimeTravelFeature(q), UsesSnowflakeFunctions = false, UsesExternalData = false, ColumnCount = !string.IsNullOrWhiteSpace(q.SelectClause?.Clause) ? q.SelectClause.Clause.Split(',').Length : 0, ParameterCount = q.ParameterList.Count() }); } /// /// Helper method to extract table names from a FROM clause. /// private static IEnumerable ExtractTableNames(string fromClause) { if (string.IsNullOrWhiteSpace(fromClause)) { yield break; } var parts = fromClause.Split(','); foreach (var part in parts) { var trimmed = part.Trim(); var tokens = trimmed.Split(new[] { " AS ", " " }, StringSplitOptions.RemoveEmptyEntries); if (tokens.Length > 0) { var tableName = tokens[0].Trim(); if (!string.IsNullOrWhiteSpace(tableName)) { yield return tableName; } } } } /// /// Helper method to convert a .NET object to its corresponding Snowflake data type string. /// private static string GetSnowflakeDataType(object? value) { return value switch { null => "VARIANT", bool => "BOOLEAN", byte or sbyte or short or ushort or int or uint or long or ulong => "NUMBER", float or double or decimal => "NUMBER", DateTime or DateTimeOffset => "TIMESTAMP_NTZ", TimeSpan => "TIME", string => value.ToString()!.Length > 255 ? "VARCHAR(MAX)" : "VARCHAR(255)", byte[] => "BINARY", _ => "VARIANT" }; } /// /// Helper method to format a parameter value for safe inclusion in Snowflake SQL statements. /// private static string FormatParameterValue(object? value) { return value switch { null => "NULL", bool b => b ? "TRUE" : "FALSE", string s => $"'{s.Replace("'", "''")}'", DateTime dt => $"'{dt:yyyy-MM-dd HH:mm:ss}'", DateTimeOffset dto => $"'{dto:yyyy-MM-dd HH:mm:ss}'", _ => value.ToString() ?? "NULL" }; } } /// /// Analysis information about a Snowflake query. /// public class SnowflakeQueryAnalysis { /// /// 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 an ORDER BY clause. /// public bool HasOrderByClause { get; set; } /// /// Gets or sets whether the query has Common Table Expressions (CTEs). /// public bool HasCTE { get; set; } /// /// Gets or sets whether the query uses semi-structured data functions. /// public bool UsesSemiStructuredData { get; set; } /// /// Gets or sets whether the query references Snowflake stages. /// public bool UsesStageReference { get; set; } /// /// Gets or sets whether the query uses Snowflake time travel features. /// public bool UsesTimeTravelFeature { get; set; } /// /// Gets or sets whether the query uses Snowflake-specific functions. /// public bool UsesSnowflakeFunctions { get; set; } /// /// Gets or sets whether the query uses external data sources. /// public bool UsesExternalData { 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; } /// /// Returns a string representation of the query analysis. /// public override string ToString() { var sb = new StringBuilder(); sb.AppendLine($"Snowflake Query #{Index}"); sb.AppendLine($" Basic Structure:"); 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($" ORDER BY: {(HasOrderByClause ? "Yes" : "No")}"); sb.AppendLine($" Snowflake Features:"); sb.AppendLine($" Semi-Structured Data: {(UsesSemiStructuredData ? "Yes" : "No")}"); sb.AppendLine($" Stage Reference: {(UsesStageReference ? "Yes" : "No")}"); sb.AppendLine($" Time Travel: {(UsesTimeTravelFeature ? "Yes" : "No")}"); sb.AppendLine($" Snowflake Functions: {(UsesSnowflakeFunctions ? "Yes" : "No")}"); sb.AppendLine($" External Data: {(UsesExternalData ? "Yes" : "No")}"); sb.Append($" Parameters: {ParameterCount}"); return sb.ToString(); } } /// /// Reports parameter usage statistics across queries in a Snowflake collection. /// public class ParameterUsageReport { /// /// Gets or sets the name of the parameter. /// public string ParameterName { get; set; } = string.Empty; /// /// Gets or sets the current value of the parameter. /// 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 a value indicating whether this parameter is used in all queries. /// public bool IsUsedInAllQueries => UsedInQueryCount == TotalQueries && TotalQueries > 0; /// /// Returns a string representation of the parameter usage report. /// public override string ToString() { if (TotalQueries == 0) { return $"{ParameterName}: No queries"; } var percentage = (UsedInQueryCount * 100.0) / TotalQueries; var valueStr = Value switch { null => "NULL", string s => $"'{s}'", _ => Value.ToString() ?? "NULL" }; return $"{ParameterName} = {valueStr} ({UsedInQueryCount}/{TotalQueries} queries - {percentage:F1}%)"; } }