using Strata.SqlTools.Breakdowns.SqlServer; using Strata.SqlTools.SqlBreakdown.Classes; namespace Strata.SqlTools.SqlBreakdown.Tests.SqlServer; [TestFixture] public class CTEColumnListTests { #region Basic Column List Tests [Test] public void WithClause_ColumnList_CanBeSet() { // Arrange var cte = new QueryBreakdown("id, name, email", "users"); var withClause = new WithClause("user_summary", cte) { ColumnList = new List { "user_id", "user_name", "user_email" } }; // Act & Assert Assert.That(withClause.ColumnList, Has.Count.EqualTo(3)); Assert.That(withClause.ColumnList[0], Is.EqualTo("user_id")); Assert.That(withClause.ColumnList[1], Is.EqualTo("user_name")); Assert.That(withClause.ColumnList[2], Is.EqualTo("user_email")); } [Test] public void GetSql_WithColumnList_IncludesColumnsInWithClauseName() { // Arrange var cte = new QueryBreakdown("id, name, email", "users", "active = 1"); var mainQuery = new QueryBreakdown("*", "active_users"); var withClause = new WithClause("active_users", cte) { ColumnList = new List { "user_id", "user_name", "user_email" } }; mainQuery.AddWithClause(withClause); // Act var sql = mainQuery.GetSql(); // Assert Assert.That(sql, Contains.Substring("active_users (user_id, user_name, user_email)"), "SQL should include column list in CTE definition"); } [Test] public void GetSql_WithoutColumnList_DoesNotIncludeParens() { // Arrange var cte = new QueryBreakdown("id, name, email", "users"); var mainQuery = new QueryBreakdown("*", "all_users"); var withClause = new WithClause("all_users", cte) { ColumnList = null // No column list }; mainQuery.AddWithClause(withClause); // Act var sql = mainQuery.GetSql(); // Assert Assert.That(sql, Contains.Substring("all_users AS"), "Without column list, should just have table name"); Assert.That(sql, Does.Not.Contain("all_users ()")); } [Test] public void GetSql_WithEmptyColumnList_DoesNotIncludeParens() { // Arrange var cte = new QueryBreakdown("id, name", "users"); var mainQuery = new QueryBreakdown("*", "data"); var withClause = new WithClause("data", cte) { ColumnList = new List() // Empty list }; mainQuery.AddWithClause(withClause); // Act var sql = mainQuery.GetSql(); // Assert Assert.That(sql, Contains.Substring("data AS"), "Empty column list should not generate parentheses"); Assert.That(sql, Does.Not.Contain("data ()")); } #endregion #region Multi-CTE Column List Tests [Test] public void GetSql_WithMultipleCTEsWithColumnLists_IncludesAllLists() { // Arrange var cte1 = new QueryBreakdown("id, name", "users"); var cte2 = new QueryBreakdown("user_id, order_count", "orders"); var mainQuery = new QueryBreakdown("*", "results"); var withClause1 = new WithClause("active_users", cte1) { ColumnList = new List { "user_id", "user_name" } }; var withClause2 = new WithClause("user_orders", cte2) { ColumnList = new List { "uid", "order_count" } }; mainQuery.AddWithClause(withClause1); mainQuery.AddWithClause(withClause2); // Act var sql = mainQuery.GetSql(); // Assert Assert.That(sql, Contains.Substring("active_users (user_id, user_name)")); Assert.That(sql, Contains.Substring("user_orders (uid, order_count)")); } [Test] public void GetSql_WithMixedColumnListPresence_HandlesCorrectly() { // Arrange var cte1 = new QueryBreakdown("id, name", "users"); var cte2 = new QueryBreakdown("dept_id, dept_name", "departments"); var mainQuery = new QueryBreakdown("*", "combined"); var withClause1 = new WithClause("user_data", cte1) { ColumnList = new List { "uid", "uname" } }; var withClause2 = new WithClause("dept_data", cte2) { // No column list for second CTE }; mainQuery.AddWithClause(withClause1); mainQuery.AddWithClause(withClause2); // Act var sql = mainQuery.GetSql(); // Assert Assert.That(sql, Contains.Substring("user_data (uid, uname)"), "First CTE should have column list"); Assert.That(sql, Contains.Substring("dept_data AS"), "Second CTE should not have column list"); Assert.That(sql, Does.Not.Contain("dept_data ()"), "Second CTE should not have empty parentheses"); } #endregion #region Column List with Parameters [Test] public void ColumnList_PreservesParameters() { // Arrange var cte = new QueryBreakdown("id, name, salary", "employees", "salary > @MinSalary"); cte.AddParameter("MinSalary", 50000); var mainQuery = new QueryBreakdown("*", "high_earners"); cte.AddParameter("CompanyId", 1); var withClause = new WithClause("high_earners", cte) { ColumnList = new List { "emp_id", "emp_name", "emp_salary" } }; mainQuery.AddWithClause(withClause); // Act var merged = mainQuery.GetMergedParameters(); var sql = mainQuery.GetSql(); // Assert Assert.That(sql, Contains.Substring("high_earners (emp_id, emp_name, emp_salary)")); var keys = merged.Keys.ToList(); Assert.That(keys.Any(k => k.Contains("MinSalary")), "Parameters should be preserved with column list"); } #endregion #region Column List with Recursive CTEs [Test] public void ColumnList_WorksWithRecursiveCTE() { // Arrange var anchorQuery = new QueryBreakdown("id, manager_id, name, 1 as level", "employees", "manager_id IS NULL"); var recursiveQuery = new QueryBreakdown( "e.id, e.manager_id, e.name, h.level + 1", "employees e INNER JOIN hierarchy h ON e.manager_id = h.id" ); var mainQuery = new QueryBreakdown("*", "hierarchy"); var withClause = new WithClause("hierarchy", anchorQuery) { IsRecursive = true, RecursiveQuery = recursiveQuery, ColumnList = new List { "emp_id", "mgr_id", "emp_name", "lvl" } }; mainQuery.AddWithClause(withClause); // Act var sql = mainQuery.GetSql(); // Assert Assert.That(sql, Contains.Substring("WITH RECURSIVE")); Assert.That(sql, Contains.Substring("hierarchy (emp_id, mgr_id, emp_name, lvl)")); Assert.That(sql, Contains.Substring("UNION ALL")); } #endregion #region Nested CTE Column Lists [Test] public void ColumnList_WithNestedCTEs() { // Arrange var innerCte = new QueryBreakdown("id, dept_id", "employees"); var outerCte = new QueryBreakdown("*", "emp_summary"); var innerWithClause = new WithClause("emp_data", innerCte) { ColumnList = new List { "emp_id", "dept_id" } }; outerCte.AddWithClause(innerWithClause); var mainQuery = new QueryBreakdown("*", "final_result"); var outerWithClause = new WithClause("emp_hierarchy", outerCte) { ColumnList = new List { "info" } }; mainQuery.AddWithClause(outerWithClause); // Act var sql = mainQuery.GetSql(); // Assert Assert.That(sql, Contains.Substring("emp_hierarchy (info)"), "Outer CTE should have column list"); Assert.That(sql, Contains.Substring("emp_data (emp_id, dept_id)"), "Inner CTE should have column list"); } #endregion #region Column List Length and Naming [Test] public void ColumnList_CanHaveManyColumns() { // Arrange var cte = new QueryBreakdown("col1, col2, col3, col4, col5, col6, col7, col8, col9, col10", "source"); var mainQuery = new QueryBreakdown("*", "results"); var columns = new List { "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10" }; var withClause = new WithClause("data_cte", cte) { ColumnList = columns }; mainQuery.AddWithClause(withClause); // Act var sql = mainQuery.GetSql(); // Assert var expectedColumns = string.Join(", ", columns); Assert.That(sql, Contains.Substring($"data_cte ({expectedColumns})")); } [Test] public void ColumnList_PreservesColumnOrder() { // Arrange var cte = new QueryBreakdown("a, b, c", "source"); var mainQuery = new QueryBreakdown("*", "output"); var columns = new List { "z", "y", "x" }; // Different order from source var withClause = new WithClause("ordered_cte", cte) { ColumnList = columns }; mainQuery.AddWithClause(withClause); // Act var sql = mainQuery.GetSql(); // Assert Assert.That(sql, Contains.Substring("ordered_cte (z, y, x)"), "Column order should be preserved"); } [Test] public void ColumnList_AllowsSpecialCharacterNames() { // Arrange var cte = new QueryBreakdown("col1, col2", "source"); var mainQuery = new QueryBreakdown("*", "output"); var columns = new List { "[column 1]", "[column-2]" }; var withClause = new WithClause("special_cte", cte) { ColumnList = columns }; mainQuery.AddWithClause(withClause); // Act var sql = mainQuery.GetSql(); // Assert Assert.That(sql, Contains.Substring("special_cte ([column 1], [column-2])")); } #endregion #region Real-World Scenarios [Test] public void ColumnList_CompleteBIScenario() { // Scenario: Business Intelligence query with renamed columns // Arrange var salesData = new QueryBreakdown( "customer_id, order_id, order_date, order_amount", "sales", "order_date >= @StartDate AND order_amount >= @MinAmount" ); salesData.AddParameter("StartDate", DateTime.Today.AddMonths(-12)); salesData.AddParameter("MinAmount", 1000); var mainQuery = new QueryBreakdown( "customer_id, order_count, total_revenue", "sales_summary" ); mainQuery.AddParameter("CurrencyConversion", 1.1); var withClause = new WithClause("recent_sales", salesData) { ColumnList = new List { "cust_id", // Maps to customer_id "order_id", // Maps to order_id "sale_date", // Maps to order_date "amount" // Maps to order_amount } }; mainQuery.AddWithClause(withClause); // Act var sql = mainQuery.GetSql(); var params_all = mainQuery.GetMergedParameters(); // Assert Assert.That(sql, Contains.Substring("recent_sales (cust_id, order_id, sale_date, amount)")); var paramKeys = params_all.Keys.ToList(); Assert.That(paramKeys.Any(k => k.Contains("StartDate"))); Assert.That(paramKeys.Any(k => k.Contains("MinAmount"))); Assert.That(paramKeys.Any(k => k.Contains("CurrencyConversion"))); } #endregion }