using Strata.SqlTools.SqlBreakdown.Interfaces.QueryEngine; namespace Strata.SqlTools.SqlBreakdown.Classes; /// /// Represents a WITH clause (Common Table Expression) with its structure and parsed query. /// /// /// /// 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. /// /// /// Recursive CTE Limitations and Requirements: /// /// Must have IsRecursive = true /// Must provide a RecursiveQuery (the UNION ALL recursive member) /// The Query property represents the anchor member (non-recursive base case) /// Both anchor and recursive members must return the same number of columns with compatible types /// ColumnList is recommended but not required; helps ensure column consistency /// RecursiveQuery typically references the CTE's TableName in its FROM clause /// Always include a termination condition in the recursive query's WHERE clause to prevent infinite loops /// Parameters are inherited from the ancestor query; main query parameters override CTE parameters /// /// /// public class WithClause : SqlClause, IWithClause { private SqlClauses? _sql; private IQueryBreakdown? _query; /// /// Gets or sets the table name for the CTE. /// public string TableName { get; set; } = string.Empty; /// /// 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. /// 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; } } } /// /// 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. /// 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; } } } /// /// 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. /// /// /// 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. /// public bool IsRecursive { get; set; } = false; /// /// 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. /// /// /// /// The recursive member typically: /// /// References the CTE's TableName in its FROM clause /// Includes a JOIN or WHERE condition that advances the recursion /// Has a termination condition (e.g., depth limit, no more rows to process) /// Returns the same column count and compatible types as the anchor member /// /// /// /// Example recursive scenario: traversing an organizational hierarchy where employees reference their managers. /// /// public IQueryBreakdown? RecursiveQuery { get; set; } /// /// 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 (...) /// /// /// Particularly useful for: /// /// Recursive CTEs where consistent column naming is critical /// CTEs with complex expressions where column aliases may not be clear /// Providing meaningful column names for external consumers of the CTE /// /// The number of column names must match the number of columns in the SELECT clause. /// public List? ColumnList { get; set; } /// /// Initializes a new instance of the class. /// public WithClause() { } /// /// Initializes a new instance of the class with a table name and query. /// /// The table name for the CTE. /// The query breakdown for the CTE. public WithClause(string tableName, IQueryBreakdown query) { TableName = tableName ?? throw new ArgumentNullException(nameof(tableName)); Query = query ?? throw new ArgumentNullException(nameof(query)); } /// /// Initializes a new instance of the class with a table name and SQL clauses. /// /// The table name for the CTE. /// The parsed SQL clauses for the CTE. public WithClause(string tableName, SqlClauses sql) { TableName = tableName ?? throw new ArgumentNullException(nameof(tableName)); Sql = sql ?? throw new ArgumentNullException(nameof(sql)); } }