- 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>
164 lines
7.0 KiB
C#
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));
|
|
}
|
|
}
|
|
|