Files
sql-utilities/src/Strata.SqlTools.SqlBreakdown/Classes/WithClause.cs
T
Thom LambandClaude Opus 4.7 e1cdcd77a4 chore(sonar): collapse trivial backing-field properties to auto-properties (S2292)
- WithClause.RecursiveQuery and WithClause.ColumnList had get/set bodies that
  only forwarded to private backing fields. Convert both to auto-properties
  and remove the now-orphaned _recursiveQuery / _columnList fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 09:26:35 -05:00

164 lines
7.0 KiB
C#

using Strata.SqlTools.SqlBreakdown.Interfaces.QueryEngine;
namespace Strata.SqlTools.SqlBreakdown.Classes;
/// <summary>
/// Represents a WITH clause (Common Table Expression) with its structure and parsed query.
/// </summary>
/// <remarks>
/// <para>
/// The Clause property inherited from SqlClause contains the full CTE definition text for round-trip parsing.
/// The TableName property identifies the CTE, while Sql contains the parsed query structure.
/// The Query property provides access to the full query breakdown including parameters.
/// </para>
/// <para>
/// <b>Recursive CTE Limitations and Requirements:</b>
/// <list type="bullet">
/// <item><description>Must have IsRecursive = true</description></item>
/// <item><description>Must provide a RecursiveQuery (the UNION ALL recursive member)</description></item>
/// <item><description>The Query property represents the anchor member (non-recursive base case)</description></item>
/// <item><description>Both anchor and recursive members must return the same number of columns with compatible types</description></item>
/// <item><description>ColumnList is recommended but not required; helps ensure column consistency</description></item>
/// <item><description>RecursiveQuery typically references the CTE's TableName in its FROM clause</description></item>
/// <item><description>Always include a termination condition in the recursive query's WHERE clause to prevent infinite loops</description></item>
/// <item><description>Parameters are inherited from the ancestor query; main query parameters override CTE parameters</description></item>
/// </list>
/// </para>
/// </remarks>
public class WithClause : SqlClause, IWithClause
{
private SqlClauses? _sql;
private IQueryBreakdown? _query;
/// <summary>
/// Gets or sets the table name for the CTE.
/// </summary>
public string TableName { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the parsed SQL clauses representing the CTE query.
/// When getting, if Query is not null, returns SqlClauses constructed from the Query's properties.
/// When setting, if Query is not null, applies the clauses to the Query for validation/restructuring.
/// Otherwise stores the value for later use.
/// </summary>
public SqlClauses? Sql
{
get => Query?.GetClauses() ?? _sql;
set
{
_sql = value;
// If Query is already set and we're setting new Sql clauses, apply them to the Query
if (_query != null && value != null)
{
_query.ApplyClauses(value);
// Clear the stored value since it's now in the Query
_sql = null;
}
}
}
/// <summary>
/// Gets or sets the query breakdown representing the CTE.
/// This provides access to the full query structure including parameters.
/// When setting, if Sql clauses were previously set, they will be applied to the Query.
/// </summary>
public IQueryBreakdown? Query
{
get => _query;
set
{
_query = value;
// If we have stored SQL clauses and a new Query is being set, apply the clauses to it
if (_query != null && _sql != null)
{
_query.ApplyClauses(_sql);
// Clear the stored SQL clauses since they're now part of the Query
_sql = null;
}
}
}
/// <summary>
/// Gets or sets a value indicating whether this CTE is recursive.
/// Recursive CTEs require a UNION ALL pattern with an anchor member and recursive member.
/// </summary>
/// <remarks>
/// When set to true, you must also provide a RecursiveQuery. The Query property represents the
/// anchor member (base case), while RecursiveQuery represents the recursive member that typically
/// references the CTE's own TableName. Always ensure the recursive query has a proper termination
/// condition to avoid infinite recursion.
/// </remarks>
public bool IsRecursive { get; set; } = false;
/// <summary>
/// Gets or sets the recursive query (UNION ALL part) for recursive CTEs.
/// This query represents the recursive member that joins back to the CTE.
/// Only applicable when IsRecursive is true.
/// </summary>
/// <remarks>
/// <para>
/// The recursive member typically:
/// <list type="bullet">
/// <item><description>References the CTE's TableName in its FROM clause</description></item>
/// <item><description>Includes a JOIN or WHERE condition that advances the recursion</description></item>
/// <item><description>Has a termination condition (e.g., depth limit, no more rows to process)</description></item>
/// <item><description>Returns the same column count and compatible types as the anchor member</description></item>
/// </list>
/// </para>
/// <para>
/// Example recursive scenario: traversing an organizational hierarchy where employees reference their managers.
/// </para>
/// </remarks>
public IQueryBreakdown? RecursiveQuery { get; set; }
/// <summary>
/// Gets or sets the explicit column list for the CTE.
/// When specified, defines column names for the CTE that can differ from the underlying query columns.
/// Example: WITH users (id, name, email) AS (...)
/// </summary>
/// <remarks>
/// Particularly useful for:
/// <list type="bullet">
/// <item><description>Recursive CTEs where consistent column naming is critical</description></item>
/// <item><description>CTEs with complex expressions where column aliases may not be clear</description></item>
/// <item><description>Providing meaningful column names for external consumers of the CTE</description></item>
/// </list>
/// The number of column names must match the number of columns in the SELECT clause.
/// </remarks>
public List<string>? ColumnList { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="WithClause"/> class.
/// </summary>
public WithClause()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WithClause"/> class with a table name and query.
/// </summary>
/// <param name="tableName">The table name for the CTE.</param>
/// <param name="query">The query breakdown for the CTE.</param>
public WithClause(string tableName, IQueryBreakdown query)
{
TableName = tableName ?? throw new ArgumentNullException(nameof(tableName));
Query = query ?? throw new ArgumentNullException(nameof(query));
}
/// <summary>
/// Initializes a new instance of the <see cref="WithClause"/> class with a table name and SQL clauses.
/// </summary>
/// <param name="tableName">The table name for the CTE.</param>
/// <param name="sql">The parsed SQL clauses for the CTE.</param>
public WithClause(string tableName, SqlClauses sql)
{
TableName = tableName ?? throw new ArgumentNullException(nameof(tableName));
Sql = sql ?? throw new ArgumentNullException(nameof(sql));
}
}