Files
sql-utilities/tests/Strata.SqlTools.Snowflake.Tests/Snowflake/QueryBreakdownTests.cs
T
Thom LambandClaude Opus 4.7 f55d8130e6
SonarQube Analysis / sonarqube (pull_request) Successful in 3m41s
chore(sonar): clear residual test-file smells (NUnit2046, NUnit2045, CS8604)
- **NUnit2046 (15)**: extend the prior regex sweep to also catch the
  `Is.GreaterThan(n)` / `Is.GreaterThanOrEqualTo(n)` variants on
  `.Count` and `.Length` — the previous pass only handled `Is.EqualTo`.
  Affects PG/Snowflake `QueryBreakdownTests`, PG `StatementReaderTests`,
  `LinqToSql` `QueryComparatorTests` / `QueryValidatorTests`,
  `Markdown.Tests` `ExpressionGeneratorTests` /
  `QueryBreakdownGeneratorTests`, and `JsonTokenReaderTests`.

- **CS8604 (8)**: `merged[someKey]` access inside `Assert.Multiple(() =>
  { ... })` lambdas where `someKey` is `string?` from `FirstOrDefault`.
  The preceding `Assert.That(someKey, Is.Not.Null)` does not propagate
  null-narrowing into the lambda scope, so add `!` to the dictionary
  index. (Tests fail loud with a meaningful message if the key really
  is null, so this is safe.)

- **NUnit2045 (1)**: wrap a four-assert block in
  `JsonTokenReaderTests.cs:65-68` in `Assert.Multiple`. Mirrors the
  surrounding two `Assert.Multiple` groups in that method.

All 1180 tests stay green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 15:29:20 -05:00

2081 lines
71 KiB
C#

using Strata.SqlTools.Breakdowns.Snowflake;
namespace Strata.SqlTools.SqlBreakdown.Tests.Snowflake;
[TestFixture]
public class QueryBreakdownTests
{
#region Constructor Tests
[Test]
public void Constructor_Default_InitializesEmptyProperties()
{
// Arrange & Act
var queryBreakdown = new QueryBreakdown();
Assert.Multiple(() =>
{
// Assert
Assert.That(queryBreakdown.SelectClause.Clause, Is.EqualTo(string.Empty));
Assert.That(queryBreakdown.FromClause.Clause, Is.EqualTo(string.Empty));
Assert.That(queryBreakdown.WhereClause.Clause, Is.EqualTo(string.Empty));
Assert.That(queryBreakdown.OrderByClause.Clause, Is.EqualTo(string.Empty));
Assert.That(queryBreakdown.GroupByClause.Clause, Is.EqualTo(string.Empty));
Assert.That(queryBreakdown.HavingClause.Clause, Is.EqualTo(string.Empty));
});
}
[Test]
public void Constructor_WithSelectAndFrom_SetsProperties()
{
// Arrange
var selectClause = "ID, NAME";
var fromClause = "USERS";
// Act
var queryBreakdown = new QueryBreakdown(selectClause, fromClause);
Assert.Multiple(() =>
{
// Assert
Assert.That(queryBreakdown.SelectClause.Clause, Is.EqualTo(selectClause));
Assert.That(queryBreakdown.FromClause.Clause, Is.EqualTo(fromClause));
});
}
[Test]
public void Constructor_WithSelectFromAndWhere_SetsProperties()
{
// Arrange
var selectClause = "ID, NAME";
var fromClause = "USERS";
var whereClause = "ACTIVE = 1";
// Act
var queryBreakdown = new QueryBreakdown(selectClause, fromClause, whereClause);
Assert.Multiple(() =>
{
// Assert
Assert.That(queryBreakdown.SelectClause.Clause, Is.EqualTo(selectClause));
Assert.That(queryBreakdown.FromClause.Clause, Is.EqualTo(fromClause));
Assert.That(queryBreakdown.WhereClause.Clause, Is.EqualTo(whereClause));
});
}
#endregion
#region GetSql Tests - Snowflake Formatting
[Test]
public void GetSql_SimpleSelect_UsesSnowflakeFormatting()
{
// Arrange
var queryBreakdown = new QueryBreakdown("ID, NAME", "USERS");
// Act
var sql = queryBreakdown.GetSql();
// Assert
Assert.That(sql, Does.Contain("SELECT"));
Assert.That(sql, Does.Contain("ID, NAME"));
Assert.That(sql, Does.Contain("FROM"));
Assert.That(sql, Does.Contain("USERS"));
// Verify 4-space indentation (Snowflake style)
Assert.That(sql, Does.Contain(" "));
}
[Test]
public void GetSql_WithAllClauses_GeneratesCorrectSnowflakeSql()
{
// Arrange
var queryBreakdown = new QueryBreakdown("DEPARTMENT, COUNT(*) AS EMP_COUNT", "EMPLOYEES");
queryBreakdown.WhereClause.Clause = "ACTIVE = 1";
queryBreakdown.GroupByClause.Clause = "DEPARTMENT";
queryBreakdown.HavingClause.Clause = "COUNT(*) > 5";
queryBreakdown.OrderByClause.Clause = "DEPARTMENT ASC";
// Act
var sql = queryBreakdown.GetSql();
// Assert
Assert.That(sql, Does.Contain("SELECT"));
Assert.That(sql, Does.Contain("FROM"));
Assert.That(sql, Does.Contain("WHERE"));
Assert.That(sql, Does.Contain("GROUP BY"));
Assert.That(sql, Does.Contain("HAVING"));
Assert.That(sql, Does.Contain("ORDER BY"));
}
private static readonly char[] separator = new[] { '\r', '\n' };
[Test]
public void GetSql_WithSingleWithClause_UsesSnowflakeIndentation()
{
// Arrange
var mainQueryBreakdown = new QueryBreakdown("P.*, PS.TOTAL_QTY", "PRODUCTS P INNER JOIN PRODUCT_SUMMARY PS ON P.PRODUCT_ID = PS.PRODUCT_ID");
var cte = new QueryBreakdown("PRODUCT_ID, SUM(QUANTITY) AS TOTAL_QTY", "ORDER_DETAILS");
cte.GroupByClause.Clause = "PRODUCT_ID";
mainQueryBreakdown.AddWithClause("PRODUCT_SUMMARY", cte);
// Act
var sql = mainQueryBreakdown.GetSql();
// Assert
Assert.That(sql, Does.Contain("WITH"));
Assert.That(sql, Does.Contain("PRODUCT_SUMMARY AS ("));
// Verify 4-space Snowflake indentation
var lines = sql.Split(separator, StringSplitOptions.RemoveEmptyEntries);
var indentedLines = lines.Where(l => l.StartsWith(" ")).ToList();
Assert.That(indentedLines, Is.Not.Empty);
}
[Test]
public void GetSql_WithMultipleWithClauses_GeneratesCorrectSnowflakeSql()
{
// Arrange
var mainQueryBreakdown = new QueryBreakdown("*", "CTE3");
var cte1 = new QueryBreakdown("COL1", "TABLE1");
var cte2 = new QueryBreakdown("COL2", "TABLE2");
var cte3 = new QueryBreakdown("COL3", "TABLE3");
mainQueryBreakdown.AddWithClause("CTE1", cte1);
mainQueryBreakdown.AddWithClause("CTE2", cte2);
mainQueryBreakdown.AddWithClause("CTE3", cte3);
// Act
var sql = mainQueryBreakdown.GetSql();
// Assert
Assert.That(sql, Does.Contain("WITH"));
Assert.That(sql, Does.Contain("CTE1 AS ("));
Assert.That(sql, Does.Contain("CTE2 AS ("));
Assert.That(sql, Does.Contain("CTE3 AS ("));
// Verify commas between CTEs by checking the pattern
// CTE1 should be followed by ), then comma, then CTE2
var cte1Index = sql.IndexOf("CTE1 AS (");
var cte2Index = sql.IndexOf("CTE2 AS (", cte1Index);
var betweenCtes = sql.Substring(cte1Index, cte2Index - cte1Index);
Assert.That(betweenCtes, Does.Contain("),"), "CTE1 should be followed by a comma before CTE2");
// Verify comma between CTE2 and CTE3
var cte3Index = sql.IndexOf("CTE3 AS (", cte2Index);
var betweenCte2And3 = sql.Substring(cte2Index, cte3Index - cte2Index);
Assert.That(betweenCte2And3, Does.Contain("),"), "CTE2 should be followed by a comma before CTE3");
}
#endregion
#region AddWithClause Tests - Snowflake Specific
[Test]
public void AddWithClause_WithSnowflakeSql_DefaultsToSnowflakeParsing()
{
// Arrange
var mainQueryBreakdown = new QueryBreakdown("*", "PRODUCTS P INNER JOIN PRODUCT_ORDERS PO ON P.PRODUCT_ID = PO.PRODUCT_ID");
var cteSql = "SELECT PRODUCT_ID, COUNT(*) AS ORDER_COUNT FROM ORDERS GROUP BY PRODUCT_ID";
// Act - should default to isMicrosoftSql = false
mainQueryBreakdown.AddWithClause("PRODUCT_ORDERS", cteSql);
// Assert
Assert.That(mainQueryBreakdown.WithClauses, Has.Count.EqualTo(1));
Assert.That(mainQueryBreakdown.WithClauses[0].TableName, Is.EqualTo("PRODUCT_ORDERS"));
}
[Test]
public void AddWithClause_WithMicrosoftSqlTrue_UsesTSqlParsing()
{
// Arrange
var mainQueryBreakdown = new QueryBreakdown("*", "Products");
var cteSql = "SELECT ProductID, COUNT(*) AS OrderCount FROM Orders GROUP BY ProductID";
// Act
mainQueryBreakdown.AddWithClause("ProductOrders", cteSql, isMicrosoftSql: true);
// Assert
Assert.That(mainQueryBreakdown.WithClauses, Has.Count.EqualTo(1));
Assert.That(mainQueryBreakdown.WithClauses[0].TableName, Is.EqualTo("ProductOrders"));
}
[Test]
public void AddWithClause_WithQueryBreakdown_AddsToInheritedList()
{
// Arrange
var mainQueryBreakdown = new QueryBreakdown("*", "PRODUCTS");
var cteQuery = new QueryBreakdown("PRODUCT_ID, COUNT(*) AS ORDER_COUNT", "ORDERS");
cteQuery.GroupByClause.Clause = "PRODUCT_ID";
// Act
mainQueryBreakdown.AddWithClause("PRODUCT_ORDERS", cteQuery);
// Assert
Assert.That(mainQueryBreakdown.WithClauses, Has.Count.EqualTo(1));
Assert.That(mainQueryBreakdown.WithClauses[0].Query, Is.EqualTo(cteQuery));
}
#endregion
#region CTE Integration Tests - Snowflake Specific
[Test]
public void CTE_NestedCTEs_WithParameterFlow()
{
// Arrange: Create inner CTE with Snowflake parameters
var innerCte = new QueryBreakdown("ID, NAME, CREATED_DATE", "USERS", "CREATED_DATE >= :start_date");
innerCte.AddParameter(":start_date", DateTime.Today.AddDays(-30));
// Arrange: Create middle CTE that references inner CTE
var middleCte = new QueryBreakdown("ID, NAME", "RECENT_USERS", "ID > :min_user_id");
middleCte.AddParameter(":min_user_id", 100);
// Arrange: Create main query that references middle CTE
var mainQuery = new QueryBreakdown("COUNT(*) AS ACTIVE_USER_COUNT", "FILTERED_USERS");
mainQuery.AddWithClause("RECENT_USERS", innerCte);
mainQuery.AddWithClause("FILTERED_USERS", middleCte);
// Act
var sql = mainQuery.GetSql();
var innerParams = innerCte.ParameterList.Count();
var middleParams = middleCte.ParameterList.Count();
Assert.Multiple(() =>
{
// Assert - Each CTE maintains its own parameters
Assert.That(innerParams, Is.EqualTo(1));
Assert.That(middleParams, Is.EqualTo(1));
// SQL should contain both WITH clauses
Assert.That(sql, Does.Contain("WITH"));
});
Assert.That(sql, Does.Contain("RECENT_USERS"));
Assert.That(sql, Does.Contain("FILTERED_USERS"));
}
[Test]
public void CTE_MultipleCTEs_WithComplexQueries()
{
// Arrange: Create CTE 1 - Active users (uppercase for Snowflake style)
var activeCte = new QueryBreakdown("ID, EMAIL, STATUS", "USERS", "STATUS = 'active'");
// Arrange: Create CTE 2 - Recent orders with aggregation
var ordersCte = new QueryBreakdown("USER_ID, COUNT(*) AS ORDER_COUNT, SUM(TOTAL) AS TOTAL_SPENT", "ORDERS");
ordersCte.GroupByClause.Clause = "USER_ID";
ordersCte.HavingClause.Clause = "COUNT(*) > 0";
// Arrange: Create main query combining both CTEs
var mainQuery = new QueryBreakdown("A.ID, A.EMAIL, O.ORDER_COUNT, O.TOTAL_SPENT",
"ACTIVE_USERS A LEFT JOIN USER_ORDERS O ON A.ID = O.USER_ID");
mainQuery.OrderByClause.Clause = "O.TOTAL_SPENT DESC";
mainQuery.AddWithClause("ACTIVE_USERS", activeCte);
mainQuery.AddWithClause("USER_ORDERS", ordersCte);
// Act
var sql = mainQuery.GetSql();
// Assert
Assert.That(sql, Does.Contain("WITH"));
Assert.That(sql, Does.Contain("ACTIVE_USERS"));
Assert.That(sql, Does.Contain("USER_ORDERS"));
Assert.That(sql, Does.Contain("GROUP BY"));
Assert.That(sql, Does.Contain("HAVING"));
Assert.That(sql, Does.Contain("LEFT JOIN"));
Assert.That(sql, Does.Contain("ORDER BY"));
}
[Test]
public void CTE_GetSql_GeneratesProperCTESyntax()
{
// Arrange
var cteQuery = new QueryBreakdown("PRODUCT_ID, SUM(QUANTITY) AS TOTAL_QUANTITY", "ORDER_DETAILS");
cteQuery.GroupByClause.Clause = "PRODUCT_ID";
var mainQuery = new QueryBreakdown("P.PRODUCT_NAME, PT.TOTAL_QUANTITY",
"PRODUCTS P INNER JOIN PRODUCT_TOTALS PT ON P.PRODUCT_ID = PT.PRODUCT_ID");
mainQuery.AddWithClause("PRODUCT_TOTALS", cteQuery);
// Act
var sql = mainQuery.GetSql();
// Assert - Verify WITH clause appears before main SELECT
var withIndex = sql.IndexOf("WITH");
var mainSelectIndex = sql.LastIndexOf("SELECT");
Assert.Multiple(() =>
{
Assert.That(withIndex, Is.GreaterThan(-1));
Assert.That(mainSelectIndex, Is.GreaterThan(-1));
});
Assert.Multiple(() =>
{
Assert.That(withIndex, Is.LessThan(mainSelectIndex));
// Assert - Verify CTE definition
Assert.That(sql, Does.Contain("PRODUCT_TOTALS"));
});
Assert.That(sql, Does.Contain("AS"));
Assert.That(sql, Does.Contain("PRODUCT_ID"));
Assert.That(sql, Does.Contain("TOTAL_QUANTITY"));
}
[Test]
public void CTE_WithClauseProperties_RoundTrip()
{
// Arrange: Create original query
var cteQuery = new QueryBreakdown("ID, NAME", "EMPLOYEES", "DEPARTMENT = 'Sales'");
var mainQuery = new QueryBreakdown("*", "SALES_TEAM");
mainQuery.AddWithClause("SALES_TEAM", cteQuery);
// Act: Get SQL and verify CTE is properly included
var sql = mainQuery.GetSql();
var withClause = mainQuery.WithClauses[0];
Assert.Multiple(() =>
{
// Assert: Verify WithClause properties
Assert.That(withClause.TableName, Is.EqualTo("SALES_TEAM"));
Assert.That(withClause.Query, Is.Not.Null);
Assert.That(withClause.Sql, Is.Not.Null);
});
Assert.Multiple(() =>
{
Assert.That(withClause.Sql?.SelectClause?.Clause, Contains.Substring("ID, NAME"));
Assert.That(withClause.Sql?.FromClause?.Clause, Contains.Substring("EMPLOYEES"));
Assert.That(withClause.Sql?.WhereClause?.Clause, Contains.Substring("DEPARTMENT = 'Sales'"));
});
}
[Test]
public void CTE_IsUsingWithClause_ReturnsCorrectStatus()
{
// Arrange
var query = new QueryBreakdown("*", "DATA");
// Act & Assert - No WITH clauses yet
Assert.That(query.IsUsingWithClause, Is.False);
// Act - Add a CTE
var cte = new QueryBreakdown("ID, NAME", "SOURCE_TABLE");
query.AddWithClause("SOURCE_CTE", cte);
Assert.Multiple(() =>
{
// Assert - Now using WITH clause
Assert.That(query.IsUsingWithClause, Is.True);
Assert.That(query.WithClauses, Has.Count.EqualTo(1));
});
}
#endregion
#region CTE Parameter Inheritance Tests - Snowflake
[Test]
public void GetMergedParameters_WithNoCTEs_ReturnsMainQueryParameters()
{
// Arrange
var query = new QueryBreakdown("*", "USERS");
query.AddParameter("UserId", 123);
query.AddParameter("ActiveStatus", true);
// Act
var merged = query.GetMergedParameters();
// Assert
// Snowflake stores parameters in both @ and : formats, so count is doubled
Assert.That(merged, Has.Count.GreaterThanOrEqualTo(2));
Assert.Multiple(() =>
{
Assert.That(merged.ContainsKey("@UserId") || merged.ContainsKey(":UserId"), Is.True);
Assert.That(merged.ContainsKey("@ActiveStatus") || merged.ContainsKey(":ActiveStatus"), Is.True);
});
}
[Test]
public void GetMergedParameters_WithSingleCTE_MergesParameters()
{
// Arrange
var cte = new QueryBreakdown("ID, NAME", "BASE_USERS", "ACTIVE = 1");
cte.AddParameter("MinDate", DateTime.Today.AddDays(-30));
var mainQuery = new QueryBreakdown("*", "ACTIVE_USERS");
mainQuery.AddParameter("ResultLimit", 100);
mainQuery.AddWithClause("ACTIVE_USERS", cte);
// Act
var merged = mainQuery.GetMergedParameters();
// Assert
// Should have parameters from both CTE and main query
var keys = merged.Keys.ToList();
Assert.Multiple(() =>
{
Assert.That(keys.Any(k => k.Contains("MinDate")));
Assert.That(keys.Any(k => k.Contains("ResultLimit")));
});
}
[Test]
public void GetMergedParameters_WithMultipleCTEs_MergesAllParameters()
{
// Arrange
var cte1 = new QueryBreakdown("ID, STATUS", "USERS", "STATUS = 'active'");
cte1.AddParameter("Status1", "active");
var cte2 = new QueryBreakdown("USER_ID, CREATED", "ORDERS", "CREATED >= :DATE");
cte2.AddParameter("MinDate", DateTime.Today.AddDays(-30));
var mainQuery = new QueryBreakdown("*", "RESULTS");
mainQuery.AddParameter("ResultLimit", 50);
mainQuery.AddWithClause("ACTIVE_USERS", cte1);
mainQuery.AddWithClause("RECENT_ORDERS", cte2);
// Act
var merged = mainQuery.GetMergedParameters();
// Assert
var keys = merged.Keys.ToList();
Assert.Multiple(() =>
{
Assert.That(keys.Any(k => k.Contains("Status1")));
Assert.That(keys.Any(k => k.Contains("MinDate")));
Assert.That(keys.Any(k => k.Contains("ResultLimit")));
});
}
[Test]
public void GetMergedParameters_WithParameterConflict_MainQueryTakesPrecedence()
{
// Arrange
var cte = new QueryBreakdown("ID", "USERS");
cte.AddParameter("QueryLimit", 10); // CTE sets to 10
var mainQuery = new QueryBreakdown("*", "FINAL_RESULT");
mainQuery.AddParameter("QueryLimit", 100); // Main query sets to 100
mainQuery.AddWithClause("BASE_DATA", cte);
// Act
var merged = mainQuery.GetMergedParameters();
// Assert
// Main query value should win
var keys = merged.Keys.Where(k => k.Contains("QueryLimit")).ToList();
Assert.That(keys, Is.Not.Empty);
// All QueryLimit entries should be 100 (main query's value)
foreach (var key in keys)
{
Assert.That(merged[key], Is.EqualTo(100));
}
}
[Test]
public void GetMergedParameters_WithNestedCTEs_CollectsAllParameters()
{
// Arrange: Create deeply nested CTEs
var innerCte = new QueryBreakdown("ID", "BASE_TABLE");
innerCte.AddParameter("InnerParam", "inner_value");
var middleCte = new QueryBreakdown("ID, NAME", "LAYER1");
middleCte.AddParameter("MiddleParam", "middle_value");
middleCte.AddWithClause("LAYER1_SOURCE", innerCte);
var mainQuery = new QueryBreakdown("ID, NAME, STATUS", "LAYER2");
mainQuery.AddParameter("MainParam", "main_value");
mainQuery.AddWithClause("LAYER2_SOURCE", middleCte);
// Act
var merged = mainQuery.GetMergedParameters();
// Assert - All parameters from all levels should be present
var keys = merged.Keys.ToList();
Assert.Multiple(() =>
{
Assert.That(keys.Any(k => k.Contains("InnerParam")));
Assert.That(keys.Any(k => k.Contains("MiddleParam")));
Assert.That(keys.Any(k => k.Contains("MainParam")));
});
}
[Test]
public void GetMergedParameters_PreservesParameterTypes()
{
// Arrange
var cte = new QueryBreakdown("ID", "USERS");
cte.AddParameter("IntValue", 123);
cte.AddParameter("StringValue", "test");
cte.AddParameter("DateValue", DateTime.Today);
cte.AddParameter("DecimalValue", 99.99m);
var mainQuery = new QueryBreakdown("*", "RESULTS");
mainQuery.AddWithClause("SOURCE", cte);
// Act
var merged = mainQuery.GetMergedParameters();
// Assert
// Verify all values are present and types are preserved
var intValues = merged.Values.OfType<int>().Count();
var stringValues = merged.Values.OfType<string>().Count();
var dateValues = merged.Values.OfType<DateTime>().Count();
var decimalValues = merged.Values.OfType<decimal>().Count();
Assert.Multiple(() =>
{
Assert.That(intValues, Is.GreaterThan(0), "Should have at least one int value");
Assert.That(stringValues, Is.GreaterThan(0), "Should have at least one string value");
Assert.That(dateValues, Is.GreaterThan(0), "Should have at least one DateTime value");
Assert.That(decimalValues, Is.GreaterThan(0), "Should have at least one decimal value");
});
}
#endregion
#region Parse Tests - Snowflake Specific
[Test]
public void Parse_SimpleSnowflakeSelect_ParsesCorrectly()
{
// Arrange
var sql = "SELECT ID, NAME FROM USERS";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
Assert.Multiple(() =>
{
// Assert
Assert.That(queryBreakdown.SelectClause.Clause?.Trim(), Does.Contain("ID, NAME"));
Assert.That(queryBreakdown.FromClause.Clause?.Trim(), Does.Contain("USERS"));
});
}
[Test]
public void Parse_WithLimitClause_ParsesCorrectly()
{
// Arrange
var sql = "SELECT ID, NAME FROM USERS ORDER BY NAME LIMIT 10";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
// Assert
Assert.That(queryBreakdown.OrderByClause.Clause?.Trim(), Does.Contain("NAME"));
Assert.That(queryBreakdown.OrderByClause.Clause?.Trim(), Does.Contain("LIMIT 10"));
}
[Test]
public void Parse_WithQualifyClause_ParsesCorrectly()
{
// Arrange
var sql = @"SELECT ID, NAME, ROW_NUMBER() OVER (PARTITION BY DEPT ORDER BY SALARY DESC) AS RN
FROM EMPLOYEES
QUALIFY RN = 1";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
// Assert
Assert.That(queryBreakdown.SelectClause.Clause?.Trim(), Does.Contain("ROW_NUMBER()"));
// QUALIFY is typically parsed as part of WHERE or as a separate extension in Snowflake
}
[Test]
public void Parse_WithColonParameters_ExtractsSnowflakeParameters()
{
// Arrange
var sql = "SELECT ID, NAME FROM USERS WHERE USER_ID = :userId AND STATUS = :status";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
// Assert
Assert.That(queryBreakdown.Parameters, Does.ContainKey(":userId"));
Assert.That(queryBreakdown.Parameters, Does.ContainKey(":status"));
}
[Test]
public void Parse_WithAtParameters_ExtractsAndNormalizesParameters()
{
// Arrange
var sql = "SELECT ID, NAME FROM USERS WHERE USER_ID = :userId AND STATUS = @status";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
// Assert
Assert.That(queryBreakdown.Parameters, Does.ContainKey(":userId"));
Assert.That(queryBreakdown.Parameters, Does.Not.ContainKey("@status"));
}
[Test]
public void Parse_WithMixedParameters_ExtractsBothFormats()
{
// Arrange
var sql = "SELECT ID, NAME FROM USERS WHERE USER_ID = :userId AND STATUS = @status";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
// Assert
Assert.That(queryBreakdown.Parameters, Is.Not.Empty);
}
[Test]
public void Parse_WithMicrosoftSqlTrue_DelegatesToBaseClass()
{
// Arrange
var sql = "SELECT ID, Name FROM Users WHERE UserID = @UserId";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, true);
// Assert
Assert.That(queryBreakdown, Is.Not.Null);
Assert.That(queryBreakdown, Is.InstanceOf<QueryBreakdown>());
Assert.That(queryBreakdown.Parameters, Does.ContainKey("@UserId"));
}
[Test]
public void Parse_WithSnowflakeWithClause_ParsesCorrectly()
{
// Arrange
var sql = @"WITH PRODUCT_SUMMARY AS (
SELECT PRODUCT_ID, SUM(QUANTITY) AS TOTAL_QTY
FROM ORDER_DETAILS
GROUP BY PRODUCT_ID
)
SELECT P.*, PS.TOTAL_QTY
FROM PRODUCTS P
INNER JOIN PRODUCT_SUMMARY PS ON P.PRODUCT_ID = PS.PRODUCT_ID";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
Assert.Multiple(() =>
{
// Assert
Assert.That(queryBreakdown.IsUsingWithClause, Is.True);
Assert.That(queryBreakdown.SelectClause.Clause?.Trim(), Does.Contain("P.*, PS.TOTAL_QTY"));
});
}
[Test]
public void Parse_WithNullSql_ThrowsArgumentNullException()
{
// Act & Assert
Assert.Throws<ArgumentNullException>(() => QueryBreakdown.Parse(null!));
}
[Test]
public void Parse_WithInvalidSql_ThrowsFormatException()
{
// Arrange
var sql = "INVALID SNOWFLAKE SQL";
// Act & Assert
Assert.Throws<FormatException>(() => QueryBreakdown.Parse(sql, false));
}
[Test]
public void TryParse_WithValidSnowflakeSql_ReturnsTrue()
{
// Arrange
var sql = "SELECT ID FROM USERS";
// Act
var success = QueryBreakdown.TryParse(sql, out var queryBreakdown, false);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.True);
Assert.That(queryBreakdown, Is.Not.Null);
});
Assert.That(queryBreakdown, Is.InstanceOf<QueryBreakdown>());
}
[Test]
public void TryParse_WithInvalidSql_ReturnsFalse()
{
// Arrange
var sql = "INVALID SQL";
// Act
var success = QueryBreakdown.TryParse(sql, out var queryBreakdown, out var error, false);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.False);
Assert.That(queryBreakdown, Is.Null);
Assert.That(error, Is.Not.Null);
});
}
#endregion
#region Inheritance Tests
[Test]
public void InheritsFromQueryBreakdown_CanUseBaseClassMethods()
{
// Arrange
var queryBreakdown = new QueryBreakdown("*", "USERS");
// Act
queryBreakdown.AddParameter("UserId", 123);
queryBreakdown.AddWhereClause("ACTIVE = 1");
Assert.Multiple(() =>
{
// Assert
Assert.That(queryBreakdown.Parameters, Does.ContainKey("@UserId"));
Assert.That(queryBreakdown.WhereClause.Clause, Does.Contain("ACTIVE = 1"));
});
}
[Test]
public void InheritsWithClauses_SharesSameStorage()
{
// Arrange
var queryBreakdown = new QueryBreakdown("*", "PRODUCTS");
var cte = new QueryBreakdown("*", "ORDERS");
// Act
queryBreakdown.AddWithClause("ORDER_CTE", cte);
Assert.Multiple(() =>
{
// Assert
Assert.That(queryBreakdown.WithClauses, Has.Count.EqualTo(1));
Assert.That(queryBreakdown.IsUsingWithClause, Is.True);
});
}
[Test]
public void Clone_CreatesSnowflakeInstance()
{
// Arrange
var original = new QueryBreakdown("*", "USERS");
original.WhereClause.Clause = "ACTIVE = 1";
// Act
var clone = (QueryBreakdown)original.Clone();
// Assert
Assert.That(clone, Is.InstanceOf<QueryBreakdown>());
Assert.That(clone.SelectClause.Clause, Is.EqualTo(original.SelectClause.Clause));
}
#endregion
#region Integration Tests
[Test]
public void ComplexQuery_WithMultipleCTEsAndAllClauses_WorksCorrectly()
{
// Arrange
var mainQueryBreakdown = new QueryBreakdown("*", "FINAL_DATA FD");
mainQueryBreakdown.WhereClause.Clause = "FD.TOTAL_SALES > :minSales";
mainQueryBreakdown.OrderByClause.Clause = "FD.TOTAL_SALES DESC LIMIT 100";
var salesCte = new QueryBreakdown(
"REGION_ID, SUM(AMOUNT) AS TOTAL_SALES",
"SALES");
salesCte.WhereClause.Clause = "SALE_DATE >= :startDate";
salesCte.GroupByClause.Clause = "REGION_ID";
var productCte = new QueryBreakdown("*", "PRODUCTS");
productCte.WhereClause.Clause = "ACTIVE = 1";
// Act
mainQueryBreakdown.AddWithClause("SALES_SUMMARY", salesCte);
mainQueryBreakdown.AddWithClause("ACTIVE_PRODUCTS", productCte);
mainQueryBreakdown.AddParameter("minSales", 10000);
mainQueryBreakdown.AddParameter("startDate", new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc));
var sql = mainQueryBreakdown.GetSql();
// Assert
Assert.That(sql, Does.Contain("WITH"));
Assert.That(sql, Does.Contain("SALES_SUMMARY AS ("));
Assert.That(sql, Does.Contain("ACTIVE_PRODUCTS AS ("));
Assert.Multiple(() =>
{
Assert.That(sql, Does.Contain("LIMIT 100"));
Assert.That(mainQueryBreakdown.Parameters, Does.ContainKey(":minSales"));
});
Assert.That(mainQueryBreakdown.Parameters, Does.ContainKey(":startDate"));
}
[Test]
public void NestedCTEs_ParseAndGenerate_WorksCorrectly()
{
// Arrange - Create nested WITH clause structure
var innerCte = new QueryBreakdown("ORDER_ID, CUSTOMER_ID", "ORDERS");
var middleCte = new QueryBreakdown("CUSTOMER_ID, COUNT(*) AS ORDER_COUNT", "INNER_CTE");
middleCte.GroupByClause.Clause = "CUSTOMER_ID";
middleCte.AddWithClause("INNER_CTE", innerCte);
var mainQueryBreakdown = new QueryBreakdown("*", "MIDDLE_CTE");
mainQueryBreakdown.AddWithClause("MIDDLE_CTE", middleCte);
// Act
var sql = mainQueryBreakdown.GetSql();
// Assert
Assert.That(sql, Does.Contain("WITH"));
Assert.Multiple(() =>
{
Assert.That(sql, Does.Contain("MIDDLE_CTE AS ("));
Assert.That(mainQueryBreakdown.WithClauses, Has.Count.EqualTo(1));
});
}
[Test]
public void RoundTrip_ParseAndGetSql_ProducesSimilarSql()
{
// Arrange
var originalSql = @"SELECT ID, NAME, EMAIL
FROM USERS
WHERE ACTIVE = 1
ORDER BY NAME ASC";
// Act
var parsed = QueryBreakdown.Parse(originalSql, false);
var generatedSql = parsed.GetSql();
// Assert
Assert.That(generatedSql, Does.Contain("SELECT"));
Assert.That(generatedSql, Does.Contain("ID, NAME, EMAIL"));
Assert.That(generatedSql, Does.Contain("FROM"));
Assert.That(generatedSql, Does.Contain("USERS"));
Assert.That(generatedSql, Does.Contain("WHERE"));
Assert.That(generatedSql, Does.Contain("ACTIVE = 1"));
Assert.That(generatedSql, Does.Contain("ORDER BY"));
Assert.That(generatedSql, Does.Contain("NAME ASC"));
}
#endregion
#region Edge Cases
[Test]
public void Parse_WithComplexSubqueries_HandlesCorrectly()
{
// Arrange
var sql = @"SELECT U.ID, U.NAME,
(SELECT COUNT(*) FROM ORDERS O WHERE O.USER_ID = U.ID) AS ORDER_COUNT
FROM USERS U
WHERE U.ID IN (SELECT DISTINCT USER_ID FROM ORDERS WHERE AMOUNT > 100)";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
// Assert
Assert.That(queryBreakdown, Is.Not.Null);
Assert.That(queryBreakdown.SelectClause.Clause, Does.Contain("(SELECT COUNT(*)"));
}
[Test]
public void GetSql_EmptyQuery_GeneratesMinimalSql()
{
// Arrange
var queryBreakdown = new QueryBreakdown();
// Act
var sql = queryBreakdown.GetSql();
// Assert
Assert.That(sql, Does.Contain("SELECT"));
// Should have minimal structure even when empty
}
[Test]
public void Parse_WithCommentsInSql_HandlesGracefully()
{
// Arrange
var sql = @"-- This is a comment
SELECT ID, NAME -- inline comment
FROM USERS
/* Multi-line
comment */
WHERE ACTIVE = 1";
// Act & Assert
// May throw or handle comments - test should verify behavior
Assert.DoesNotThrow(() =>
{
_ = QueryBreakdown.Parse(sql, false);
});
}
#endregion
#region Outlier Conditions - Snowflake-Specific Edge Cases
[Test]
public void Parse_WithDoubleColonCast_ParsesCorrectly()
{
// Arrange - Snowflake uses :: for casting
var sql = "SELECT ID::VARCHAR, AMOUNT::DECIMAL(10,2) FROM ORDERS";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
// Assert
Assert.That(queryBreakdown.SelectClause.Clause, Does.Contain("::"));
}
[Test]
public void Parse_WithVariantDataType_ParsesCorrectly()
{
// Arrange
var sql = "SELECT JSON_DATA:name::STRING, JSON_DATA:age::NUMBER FROM JSON_TABLE";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
// Assert
Assert.That(queryBreakdown.SelectClause.Clause, Does.Contain("JSON_DATA:name"));
}
[Test]
public void Parse_WithFlattenFunction_ParsesCorrectly()
{
// Arrange
var sql = @"SELECT
F.VALUE:id::NUMBER AS ID,
F.VALUE:name::STRING AS NAME
FROM JSON_TABLE,
LATERAL FLATTEN(input => JSON_DATA:items) F";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
Assert.Multiple(() =>
{
// Assert
Assert.That(queryBreakdown.SelectClause.Clause, Does.Contain("F.VALUE"));
Assert.That(queryBreakdown.FromClause.Clause, Does.Contain("FLATTEN"));
});
}
[Test]
public void Parse_WithSampleClause_ParsesCorrectly()
{
// Arrange
var sql = "SELECT * FROM LARGE_TABLE SAMPLE (10 ROWS)";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
// Assert
Assert.That(queryBreakdown.FromClause.Clause, Does.Contain("SAMPLE"));
}
[Test]
public void Parse_WithAtTimestamp_ParsesCorrectly()
{
// Arrange - Snowflake Time Travel
var sql = "SELECT * FROM USERS AT(TIMESTAMP => '2024-01-01 00:00:00'::TIMESTAMP)";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
// Assert
Assert.That(queryBreakdown.FromClause.Clause, Does.Contain("AT(TIMESTAMP"));
}
[Test]
public void Parse_WithMatchRecognize_HandlesGracefully()
{
// Arrange - Complex Snowflake pattern matching
var sql = @"SELECT * FROM EVENTS
MATCH_RECOGNIZE(
PARTITION BY USER_ID
ORDER BY EVENT_TIME
MEASURES A.EVENT_TIME AS START_TIME
PATTERN (A B+ C)
DEFINE B AS B.EVENT_TYPE = 'CLICK'
)";
// Act & Assert
Assert.DoesNotThrow(() => QueryBreakdown.Parse(sql, false));
}
[Test]
public void Parse_WithWindowFunctionAndQualify_ParsesCorrectly()
{
// Arrange
var sql = @"SELECT
ID,
NAME,
ROW_NUMBER() OVER (PARTITION BY DEPARTMENT ORDER BY SALARY DESC) AS RN
FROM EMPLOYEES
QUALIFY ROW_NUMBER() OVER (PARTITION BY DEPARTMENT ORDER BY SALARY DESC) = 1";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
// Assert
Assert.That(queryBreakdown.SelectClause.Clause, Does.Contain("ROW_NUMBER()"));
}
#endregion
#region Outlier Conditions - Parameter Formats
[Test]
public void Parse_WithManyColonParameters_ExtractsAll()
{
// Arrange
var sql = @"SELECT * FROM ORDERS
WHERE USER_ID = :userId
AND ORDER_DATE >= :startDate
AND ORDER_DATE <= :endDate
AND STATUS = :status
AND AMOUNT > :minAmount";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
// Assert
Assert.That(queryBreakdown.Parameters, Has.Count.GreaterThanOrEqualTo(5));
Assert.That(queryBreakdown.Parameters, Does.ContainKey(":userId"));
Assert.That(queryBreakdown.Parameters, Does.ContainKey(":startDate"));
Assert.That(queryBreakdown.Parameters, Does.ContainKey(":endDate"));
Assert.That(queryBreakdown.Parameters, Does.ContainKey(":status"));
Assert.That(queryBreakdown.Parameters, Does.ContainKey(":minAmount"));
}
[Test]
public void Parse_WithParameterInJsonPath_DoesNotExtractAsParameter()
{
// Arrange - Colon in JSON path should not be treated as parameter
var sql = "SELECT JSON_DATA:user:name FROM JSON_TABLE";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
// Assert - Should not extract 'user' or 'name' as parameters
Assert.That(queryBreakdown.SelectClause.Clause, Does.Contain("JSON_DATA:user:name"));
}
[Test]
public void AddParameter_WithColonPrefix_StoresWithColon()
{
// Arrange
var queryBreakdown = new QueryBreakdown();
// Act
queryBreakdown.AddParameter(":snowflakeParam", 123);
// Assert
// Base class normalizes to @, but Snowflake might handle differently
Assert.That(queryBreakdown.Parameters, Is.Not.Empty);
}
#endregion
#region Outlier Conditions - Snowflake String Handling
[Test]
public void Parse_WithDollarQuotedString_HandlesGracefully()
{
// Arrange - Snowflake supports $$ for string literals
var sql = "SELECT $$This is a 'quoted' string$$ AS TEXT FROM DUAL";
// Act & Assert
Assert.DoesNotThrow(() => QueryBreakdown.Parse(sql, false));
}
[Test]
public void Parse_WithEscapedSingleQuotes_ParsesCorrectly()
{
// Arrange
var sql = "SELECT NAME FROM USERS WHERE NAME = 'O''Brien'";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
// Assert
Assert.That(queryBreakdown.WhereClause.Clause, Does.Contain("O''Brien"));
}
#endregion
#region Outlier Conditions - Complex Snowflake Queries
[Test]
public void Parse_WithMultipleSetOperators_HandlesGracefully()
{
// Arrange
var sql = @"SELECT ID, NAME FROM USERS_A
UNION ALL
SELECT ID, NAME FROM USERS_B
EXCEPT
SELECT ID, NAME FROM USERS_C
INTERSECT
SELECT ID, NAME FROM USERS_D";
// Act & Assert
Assert.DoesNotThrow(() => QueryBreakdown.Parse(sql, false));
}
[Test]
public void Parse_WithPivotClause_HandlesGracefully()
{
// Arrange
var sql = @"SELECT * FROM SALES
PIVOT(SUM(AMOUNT) FOR MONTH IN ('JAN', 'FEB', 'MAR'))";
// Act & Assert
Assert.DoesNotThrow(() => QueryBreakdown.Parse(sql, false));
}
[Test]
public void GetSql_WithVeryLongSnowflakeQuery_GeneratesCorrectly()
{
// Arrange - Build a complex query with many CTEs
var mainQueryBreakdown = new QueryBreakdown("*", "FINAL_RESULT");
for (int i = 0; i < 15; i++)
{
var cte = new QueryBreakdown(
$"COL{i}, COUNT(*) AS CNT{i}",
$"TABLE_{i}");
cte.WhereClause.Clause = $"ACTIVE = 1 AND TYPE = {i}";
cte.GroupByClause.Clause = $"COL{i}";
mainQueryBreakdown.AddWithClause($"CTE_{i}", cte);
}
// Act
var sql = mainQueryBreakdown.GetSql();
// Assert
Assert.That(sql, Does.Contain("CTE_0 AS ("));
Assert.That(sql, Does.Contain("CTE_14 AS ("));
Assert.That(sql, Has.Length.GreaterThan(1000));
}
#endregion
#region Outlier Conditions - Whitespace and Formatting
[Test]
public void Parse_WithExcessiveWhitespace_NormalizesCorrectly()
{
// Arrange
var sql = "SELECT ID, NAME FROM USERS WHERE ACTIVE=1";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
// Assert
Assert.That(queryBreakdown, Is.Not.Null);
Assert.That(queryBreakdown.SelectClause.Clause, Does.Contain("ID"));
}
[Test]
public void GetSql_ComparesIndentationToBaseClass_UsesFourSpaces()
{
// Arrange
var QueryBreakdown = new QueryBreakdown("ID", "USERS");
var baseQueryBreakdown = new QueryBreakdown("ID", "USERS");
// Act
var snowflakeSql = QueryBreakdown.GetSql();
_ = baseQueryBreakdown.GetSql();
// Assert - Snowflake should use 4-space indent, base uses 5-space
var snowflakeLines = snowflakeSql.Split(separator, StringSplitOptions.RemoveEmptyEntries);
var snowflakeIndentedLines = snowflakeLines.Where(l => l.StartsWith(" ") && !l.StartsWith(" ")).ToList();
Assert.That(snowflakeIndentedLines, Is.Not.Empty, "Snowflake should use 4-space indentation");
}
#endregion
#region Outlier Conditions - Boundary and Error Cases
[Test]
public void Parse_WithOnlySelectNoFrom_HandlesGracefully()
{
// Arrange - Snowflake allows SELECT without FROM using DUAL or for constants
var sql = "SELECT 1 AS ONE, CURRENT_TIMESTAMP() AS NOW";
// Act & Assert
Assert.DoesNotThrow(() => QueryBreakdown.Parse(sql, false));
}
[Test]
public void TryParse_WithMalformedSnowflakeSQL_ReturnsFalseWithError()
{
// Arrange
var sql = "SELET * FORM USERS WERE ID = 1"; // Intentional typos
// Act
var success = QueryBreakdown.TryParse(sql, out _, out var error, false);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.False);
Assert.That(error, Is.Not.Null.And.Not.Empty);
});
}
[Test]
public void Parse_WithVeryLongTableName_ParsesCorrectly()
{
// Arrange
var longTableName = "VERY_LONG_TABLE_NAME_" + new string('X', 200);
var sql = $"SELECT ID FROM {longTableName}";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
// Assert
Assert.That(queryBreakdown.FromClause.Clause, Does.Contain(longTableName));
}
[Test]
public void MergeWith_SnowflakeQueryWithBaseQuery_WorksCorrectly()
{
// Arrange
var QueryBreakdown = new QueryBreakdown("*", "USERS U");
var baseQuery = new QueryBreakdown();
baseQuery.FromClause.Clause = "INNER JOIN ORDERS O ON U.ID = O.USER_ID";
// Act
QueryBreakdown.MergeWith(baseQuery);
// Assert
Assert.That(QueryBreakdown.FromClause.Clause, Does.Contain("USERS U"));
Assert.That(QueryBreakdown.FromClause.Clause, Does.Contain("INNER JOIN ORDERS"));
}
#endregion
#region Outlier Conditions - Real-World Snowflake Scenarios
[Test]
public void Parse_WithSnowflakeStageReference_ParsesCorrectly()
{
// Arrange
var sql = "SELECT $1, $2, $3 FROM @MY_STAGE/data/file.csv";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
Assert.Multiple(() =>
{
// Assert
Assert.That(queryBreakdown.SelectClause.Clause, Does.Contain("$1"));
Assert.That(queryBreakdown.FromClause.Clause, Does.Contain("@MY_STAGE"));
});
}
[Test]
public void Parse_WithSequenceNextval_ParsesCorrectly()
{
// Arrange
var sql = "SELECT MY_SEQUENCE.NEXTVAL, NAME FROM USERS";
// Act
var queryBreakdown = QueryBreakdown.Parse(sql, false);
// Assert
Assert.That(queryBreakdown.SelectClause.Clause, Does.Contain("NEXTVAL"));
}
[Test]
public void ComplexRealWorldQuery_WithAllSnowflakeFeatures_ParsesAndGenerates()
{
// Arrange - Realistic complex Snowflake query
var salesCte = new QueryBreakdown(
"REGION_ID, PRODUCT_ID, SUM(AMOUNT::DECIMAL(18,2)) AS TOTAL_SALES",
"SALES_DATA");
salesCte.WhereClause.Clause = "SALE_DATE >= :startDate AND SALE_DATE < :endDate";
salesCte.GroupByClause.Clause = "REGION_ID, PRODUCT_ID";
var topProductsCte = new QueryBreakdown(
"REGION_ID, PRODUCT_ID, TOTAL_SALES, ROW_NUMBER() OVER (PARTITION BY REGION_ID ORDER BY TOTAL_SALES DESC) AS RN",
"SALES_SUMMARY");
var mainQueryBreakdown = new QueryBreakdown("R.REGION_NAME, T.PRODUCT_ID, T.TOTAL_SALES", "REGIONS R INNER JOIN TOP_PRODUCTS T ON R.REGION_ID = T.REGION_ID");
mainQueryBreakdown.WhereClause.Clause = "T.RN <= 10";
mainQueryBreakdown.OrderByClause.Clause = "R.REGION_NAME, T.TOTAL_SALES DESC LIMIT 100";
mainQueryBreakdown.AddWithClause("SALES_SUMMARY", salesCte);
mainQueryBreakdown.AddWithClause("TOP_PRODUCTS", topProductsCte);
mainQueryBreakdown.AddParameter("startDate", "2024-01-01");
mainQueryBreakdown.AddParameter("endDate", "2024-12-31");
// Act
var sql = mainQueryBreakdown.GetSql();
// Assert
Assert.That(sql, Does.Contain("WITH"));
Assert.That(sql, Does.Contain("SALES_SUMMARY AS ("));
Assert.That(sql, Does.Contain("TOP_PRODUCTS AS ("));
Assert.That(sql, Does.Contain("ROW_NUMBER()"));
Assert.Multiple(() =>
{
Assert.That(sql, Does.Contain("LIMIT 100"));
Assert.That(mainQueryBreakdown.Parameters, Has.Count.GreaterThanOrEqualTo(2));
});
}
#endregion
#region Microsoft SQL Server Compatibility Tests
[Test]
public void Parse_MicrosoftSqlQuery_ReturnsQueryBreakdown()
{
// Arrange
var tsqlQuery = "SELECT Id, Name FROM Users WHERE Active = 1";
// Act
var result = QueryBreakdown.Parse(tsqlQuery, isMicrosoftSql: true);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<QueryBreakdown>(), "Should return Snowflake QueryBreakdown instance");
Assert.Multiple(() =>
{
Assert.That(result.SelectClause.Clause?.Trim(), Does.Contain("Id, Name"));
Assert.That(result.FromClause.Clause?.Trim(), Does.Contain("Users"));
Assert.That(result.WhereClause.Clause?.Trim(), Does.Contain("Active = 1"));
});
}
[Test]
public void Parse_MicrosoftSqlWithParameters_ExtractsParameters()
{
// Arrange
var tsqlQuery = "SELECT ProductId, ProductName FROM Products WHERE CategoryId = @categoryId AND Price > @minPrice";
// Act
var result = QueryBreakdown.Parse(tsqlQuery, isMicrosoftSql: true);
// Assert
Assert.That(result.Parameters, Does.ContainKey("@categoryId"));
Assert.That(result.Parameters, Does.ContainKey("@minPrice"));
}
[Test]
public void Parse_MicrosoftSqlWithAllClauses_ParsesAllComponents()
{
// Arrange
var tsqlQuery = @"SELECT DepartmentId, COUNT(*) AS EmployeeCount, AVG(Salary) AS AvgSalary
FROM Employees
WHERE Active = 1
GROUP BY DepartmentId
HAVING COUNT(*) > 5
ORDER BY EmployeeCount DESC";
// Act
var result = QueryBreakdown.Parse(tsqlQuery, isMicrosoftSql: true);
// Assert
Assert.That(result.SelectClause.Clause?.Trim(), Does.Contain("DepartmentId"));
Assert.Multiple(() =>
{
Assert.That(result.SelectClause.Clause?.Trim(), Does.Contain("COUNT(*)"));
Assert.That(result.FromClause.Clause?.Trim(), Does.Contain("Employees"));
Assert.That(result.WhereClause.Clause?.Trim(), Does.Contain("Active = 1"));
Assert.That(result.GroupByClause.Clause?.Trim(), Does.Contain("DepartmentId"));
Assert.That(result.HavingClause.Clause?.Trim(), Does.Contain("COUNT(*) > 5"));
Assert.That(result.OrderByClause.Clause?.Trim(), Does.Contain("EmployeeCount DESC"));
});
}
[Test]
public void GetSql_FromMicrosoftSqlParsedQuery_GeneratesSnowflakeSyntax()
{
// Arrange
var tsqlQuery = "SELECT Id, Name FROM Users WHERE Active = 1 ORDER BY Name";
var result = QueryBreakdown.Parse(tsqlQuery, isMicrosoftSql: true);
// Act
var snowflakeSql = result.GetSql();
// Assert
Assert.That(snowflakeSql, Does.Contain("SELECT"));
Assert.That(snowflakeSql, Does.Contain("FROM"));
Assert.That(snowflakeSql, Does.Contain("WHERE"));
Assert.That(snowflakeSql, Does.Contain("ORDER BY"));
// Verify Snowflake-style formatting (4-space indentation)
var lines = snowflakeSql.Split(separator, StringSplitOptions.RemoveEmptyEntries);
var indentedLines = lines.Where(l => l.StartsWith(" ")).ToList();
Assert.That(indentedLines, Is.Not.Empty, "Should have Snowflake-style indentation");
}
[Test]
public void Parse_MicrosoftSqlWithJoins_ParsesCorrectly()
{
// Arrange
var tsqlQuery = @"SELECT o.OrderId, c.CustomerName, p.ProductName
FROM Orders o
INNER JOIN Customers c ON o.CustomerId = c.CustomerId
LEFT JOIN OrderDetails od ON o.OrderId = od.OrderId
LEFT JOIN Products p ON od.ProductId = p.ProductId
WHERE o.OrderDate >= @startDate";
// Act
var result = QueryBreakdown.Parse(tsqlQuery, isMicrosoftSql: true);
Assert.Multiple(() =>
{
// Assert
Assert.That(result.SelectClause.Clause?.Trim(), Does.Contain("o.OrderId"));
Assert.That(result.FromClause.Clause?.Trim(), Does.Contain("INNER JOIN"));
});
Assert.Multiple(() =>
{
Assert.That(result.FromClause.Clause?.Trim(), Does.Contain("LEFT JOIN"));
Assert.That(result.WhereClause.Clause?.Trim(), Does.Contain("OrderDate"));
});
}
[Test]
public void Parse_MicrosoftSqlWithSubquery_ParsesCorrectly()
{
// Arrange
var tsqlQuery = @"SELECT CustomerId, CustomerName
FROM Customers
WHERE CustomerId IN (SELECT CustomerId FROM Orders WHERE OrderDate >= @startDate)";
// Act
var result = QueryBreakdown.Parse(tsqlQuery, isMicrosoftSql: true);
Assert.Multiple(() =>
{
// Assert
Assert.That(result.SelectClause.Clause?.Trim(), Does.Contain("CustomerId, CustomerName"));
Assert.That(result.WhereClause.Clause?.Trim(), Does.Contain("IN"));
});
Assert.That(result.WhereClause.Clause?.Trim(), Does.Contain("SELECT"));
}
[Test]
public void TryParse_MicrosoftSqlValidQuery_ReturnsTrue()
{
// Arrange
var tsqlQuery = "SELECT TOP 100 Id, Name FROM Users ORDER BY Name";
// Act
var success = QueryBreakdown.TryParse(tsqlQuery, out var result, isMicrosoftSql: true);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.True);
Assert.That(result, Is.Not.Null);
});
Assert.That(result, Is.InstanceOf<QueryBreakdown>());
}
[Test]
public void TryParse_MicrosoftSqlInvalidQuery_ReturnsFalse()
{
// Arrange
var invalidQuery = "INVALID T-SQL QUERY";
// Act
var success = QueryBreakdown.TryParse(invalidQuery, out var result, out var errorMessage, isMicrosoftSql: true);
Assert.Multiple(() =>
{
// Assert
Assert.That(success, Is.False);
Assert.That(result, Is.Null);
Assert.That(errorMessage, Is.Not.Null.And.Not.Empty);
});
}
[Test]
public void Parse_MicrosoftSqlWithBracketedIdentifiers_ParsesCorrectly()
{
// Arrange
var tsqlQuery = "SELECT [Order Id], [Customer Name] FROM [Order Details] WHERE [Active] = 1";
// Act
var result = QueryBreakdown.Parse(tsqlQuery, isMicrosoftSql: true);
Assert.Multiple(() =>
{
// Assert
Assert.That(result.SelectClause.Clause?.Trim(), Does.Contain("[Order Id]"));
Assert.That(result.FromClause.Clause?.Trim(), Does.Contain("[Order Details]"));
Assert.That(result.WhereClause.Clause?.Trim(), Does.Contain("[Active]"));
});
}
[Test]
public void Parse_MicrosoftSqlWithCTE_ParsesCorrectly()
{
// Arrange
var tsqlQuery = @"WITH EmployeeCTE AS (
SELECT DepartmentId, COUNT(*) AS EmployeeCount
FROM Employees
GROUP BY DepartmentId
)
SELECT d.DepartmentName, e.EmployeeCount
FROM Departments d
INNER JOIN EmployeeCTE e ON d.DepartmentId = e.DepartmentId";
// Act
var result = QueryBreakdown.Parse(tsqlQuery, isMicrosoftSql: true);
Assert.Multiple(() =>
{
// Assert
Assert.That(result.IsUsingWithClause, Is.True);
Assert.That(result.SelectClause.Clause?.Trim(), Does.Contain("DepartmentName"));
});
}
[Test]
public void GetSql_MicrosoftSqlWithCTE_GeneratesSnowflakeFormattedOutput()
{
// Arrange
var tsqlQuery = @"WITH SalesSummary AS (
SELECT ProductId, SUM(Quantity) AS TotalQty
FROM OrderDetails
GROUP BY ProductId
)
SELECT p.ProductName, s.TotalQty
FROM Products p
INNER JOIN SalesSummary s ON p.ProductId = s.ProductId";
var result = QueryBreakdown.Parse(tsqlQuery, isMicrosoftSql: true);
// Act
var snowflakeSql = result.GetSql();
// Assert
Assert.That(snowflakeSql, Does.Contain("WITH"));
Assert.Multiple(() =>
{
Assert.That(snowflakeSql, Does.Contain("SELECT"));
Assert.That(result.IsUsingWithClause, Is.True, "Should have WITH clause parsed");
});
// Note: WITH clause content is stored in WithClause property when parsed via isMicrosoftSql
// The GetSql() will output it if WithClauses collection is populated or if we manually add it
}
[Test]
public void Parse_MicrosoftSqlComplexQuery_ParsesAndConvertsToSnowflake()
{
// Arrange
var tsqlQuery = @"SELECT
YEAR(o.OrderDate) AS OrderYear,
MONTH(o.OrderDate) AS OrderMonth,
c.Region,
COUNT(DISTINCT o.OrderId) AS OrderCount,
SUM(od.Quantity * od.UnitPrice) AS TotalRevenue
FROM Orders o
INNER JOIN OrderDetails od ON o.OrderId = od.OrderId
INNER JOIN Customers c ON o.CustomerId = c.CustomerId
WHERE o.OrderDate >= @startDate AND o.OrderDate < @endDate
GROUP BY YEAR(o.OrderDate), MONTH(o.OrderDate), c.Region
HAVING COUNT(DISTINCT o.OrderId) > 10
ORDER BY OrderYear DESC, OrderMonth DESC, TotalRevenue DESC";
// Act
var result = QueryBreakdown.Parse(tsqlQuery, isMicrosoftSql: true);
var snowflakeSql = result.GetSql();
// Assert - Verify parsing
Assert.That(result, Is.InstanceOf<QueryBreakdown>());
Assert.Multiple(() =>
{
Assert.That(result.SelectClause.Clause?.Trim(), Does.Contain("YEAR"));
Assert.That(result.GroupByClause.Clause?.Trim(), Does.Contain("YEAR"));
Assert.That(result.HavingClause.Clause?.Trim(), Does.Contain("COUNT"));
Assert.That(result.Parameters, Does.ContainKey("@startDate"));
});
Assert.Multiple(() =>
{
Assert.That(result.Parameters, Does.ContainKey("@endDate"));
// Assert - Verify Snowflake SQL generation
Assert.That(snowflakeSql, Does.Contain("SELECT"));
});
Assert.That(snowflakeSql, Does.Contain("FROM"));
Assert.That(snowflakeSql, Does.Contain("WHERE"));
Assert.That(snowflakeSql, Does.Contain("GROUP BY"));
Assert.That(snowflakeSql, Does.Contain("HAVING"));
Assert.That(snowflakeSql, Does.Contain("ORDER BY"));
}
[Test]
public void AddWithClause_ToMicrosoftSqlParsedQuery_WorksCorrectly()
{
// Arrange
var mainQuery = "SELECT * FROM ProductSummary";
var result = QueryBreakdown.Parse(mainQuery, isMicrosoftSql: true);
var cteQuery = new QueryBreakdown("ProductId, COUNT(*) AS OrderCount", "Orders");
cteQuery.GroupByClause.Clause = "ProductId";
// Act
result.AddWithClause("ProductSummary", cteQuery);
var snowflakeSql = result.GetSql();
Assert.Multiple(() =>
{
// Assert
Assert.That(result.WithClauses, Has.Count.EqualTo(1));
Assert.That(snowflakeSql, Does.Contain("WITH"));
});
Assert.That(snowflakeSql, Does.Contain("ProductSummary AS ("));
}
[Test]
public void Clone_MicrosoftSqlParsedQuery_CreatesSnowflakeInstance()
{
// Arrange
var tsqlQuery = "SELECT Id, Name FROM Users WHERE Active = 1";
var original = QueryBreakdown.Parse(tsqlQuery, isMicrosoftSql: true);
// Act
var clone = (QueryBreakdown)original.Clone();
// Assert
Assert.That(clone, Is.InstanceOf<QueryBreakdown>());
Assert.Multiple(() =>
{
Assert.That(clone.SelectClause.Clause, Is.EqualTo(original.SelectClause.Clause));
Assert.That(clone.FromClause.Clause, Is.EqualTo(original.FromClause.Clause));
Assert.That(clone.WhereClause.Clause, Is.EqualTo(original.WhereClause.Clause));
});
}
#endregion
#region AddSelectExpression Tests
[Test]
public void AddSelectExpression_WithSnowflakeSyntax_GeneratesCorrectSql()
{
// Arrange
var query = new QueryBreakdown();
var expr = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("CUSTOMER_ID", "CUSTOMERS");
// Act
query.AddSelectExpression(expr, null, isMicrosoftSql: false);
// Assert
// Snowflake doesn't use brackets
Assert.That(query.SelectClause.Clause, Is.EqualTo("CUSTOMERS.CUSTOMER_ID"));
}
[Test]
public void AddSelectExpression_WithSqlServerSyntax_GeneratesCorrectSql()
{
// Arrange
var query = new QueryBreakdown();
var expr = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("CustomerID", "Customers");
// Act
query.AddSelectExpression(expr, null, isMicrosoftSql: true);
// Assert
// SQL Server uses brackets
Assert.That(query.SelectClause.Clause, Is.EqualTo("[Customers].[CustomerID]"));
}
[Test]
public void AddSelectExpression_DefaultBehavior_UsesSnowflakeSyntax()
{
// Arrange
var query = new QueryBreakdown();
var expr = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("CUSTOMER_ID", "CUSTOMERS");
// Act
query.AddSelectExpression(expr); // Default should be Snowflake
// Assert
Assert.That(query.SelectClause.Clause, Is.EqualTo("CUSTOMERS.CUSTOMER_ID"));
Assert.That(query.SelectClause.Clause, Does.Not.Contain("["));
}
[Test]
public void AddSelectExpression_WithMultipleExpressions_AppendsWithComma()
{
// Arrange
var query = new QueryBreakdown();
var expr1 = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("CUSTOMER_ID", "CUSTOMERS");
var expr2 = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("CUSTOMER_NAME", "CUSTOMERS");
// Act
query.AddSelectExpression(expr1);
query.AddSelectExpression(expr2);
// Assert
Assert.That(query.SelectClause.Clause, Is.EqualTo("CUSTOMERS.CUSTOMER_ID, CUSTOMERS.CUSTOMER_NAME"));
}
[Test]
public void AddSelectExpression_WithComment_AddsCommentToSelectClause()
{
// Arrange
var query = new QueryBreakdown();
var expr = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("CUSTOMER_ID", "CUSTOMERS");
// Act
query.AddSelectExpression(expr, "/* Primary key */");
Assert.Multiple(() =>
{
// Assert
Assert.That(query.SelectClause.Clause, Is.EqualTo("CUSTOMERS.CUSTOMER_ID"));
Assert.That(query.SelectClause.Comment, Is.EqualTo("/* Primary key */"));
});
}
[Test]
public void AddSelectExpression_WithNullExpression_ThrowsArgumentNullException()
{
// Arrange
var query = new QueryBreakdown();
// Act & Assert
Assert.Throws<ArgumentNullException>(() => query.AddSelectExpression(null!));
}
[Test]
public void AddSelectExpression_WithBooleanLiteral_UsesSnowflakeSyntax()
{
// Arrange
var query = new QueryBreakdown();
var expr = new Strata.SqlTools.SqlBreakdown.Expressions.Literals.BooleanLiteralExpression(true);
// Act
query.AddSelectExpression(expr, null, isMicrosoftSql: false);
// Assert
// Snowflake uses TRUE/FALSE
Assert.That(query.SelectClause.Clause, Is.EqualTo("TRUE"));
}
[Test]
public void AddSelectExpression_WithBooleanLiteral_UsesSqlServerSyntax()
{
// Arrange
var query = new QueryBreakdown();
var expr = new Strata.SqlTools.SqlBreakdown.Expressions.Literals.BooleanLiteralExpression(true);
// Act
query.AddSelectExpression(expr, null, isMicrosoftSql: true);
// Assert
// SQL Server uses 1/0
Assert.That(query.SelectClause.Clause, Is.EqualTo("1"));
}
#endregion
#region AddWhereExpression Tests
[Test]
public void AddWhereExpression_WithSnowflakeSyntax_GeneratesCorrectSql()
{
// Arrange
var query = new QueryBreakdown();
var col = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("ACTIVE", "USERS");
var expr = col == 1;
// Act
query.AddWhereExpression(expr, null, "and", isMicrosoftSql: false);
// Assert
Assert.That(query.WhereClause.Clause, Does.Contain("USERS.ACTIVE"));
Assert.That(query.WhereClause.Clause, Does.Not.Contain("["));
}
[Test]
public void AddWhereExpression_WithSqlServerSyntax_GeneratesCorrectSql()
{
// Arrange
var query = new QueryBreakdown();
var col = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("Active", "Users");
var expr = col == 1;
// Act
query.AddWhereExpression(expr, null, "and", isMicrosoftSql: true);
// Assert
Assert.That(query.WhereClause.Clause, Does.Contain("[Users].[Active]"));
}
[Test]
public void AddWhereExpression_DefaultBehavior_UsesSnowflakeSyntax()
{
// Arrange
var query = new QueryBreakdown();
var col = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("ACTIVE", "USERS");
var expr = col == 1;
// Act
query.AddWhereExpression(expr); // Default should be Snowflake
// Assert
Assert.That(query.WhereClause.Clause, Does.Contain("USERS.ACTIVE"));
Assert.That(query.WhereClause.Clause, Does.Not.Contain("["));
}
[Test]
public void AddWhereExpression_WithMultipleExpressions_AppendsWithAnd()
{
// Arrange
var query = new QueryBreakdown();
var col1 = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("ACTIVE", "USERS");
var col2 = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("AGE", "USERS");
var expr1 = col1 == 1;
var expr2 = col2 >= 18;
// Act
query.AddWhereExpression(expr1);
query.AddWhereExpression(expr2);
// Assert
Assert.That(query.WhereClause.Clause, Does.Contain("USERS.ACTIVE"));
Assert.That(query.WhereClause.Clause, Does.Contain(" and "));
Assert.That(query.WhereClause.Clause, Does.Contain("USERS.AGE"));
}
[Test]
public void AddWhereExpression_WithOrOperation_AppendsWithOr()
{
// Arrange
var query = new QueryBreakdown();
var col1 = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("STATUS", "ORDERS");
var col2 = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("PRIORITY", "ORDERS");
var expr1 = col1 == "Urgent";
var expr2 = col2 > 5;
// Act
query.AddWhereExpression(expr1);
query.AddWhereExpression(expr2, null, "or");
// Assert
Assert.That(query.WhereClause.Clause, Does.Contain("ORDERS.STATUS"));
Assert.That(query.WhereClause.Clause, Does.Contain(" or "));
Assert.That(query.WhereClause.Clause, Does.Contain("ORDERS.PRIORITY"));
}
[Test]
public void AddWhereExpression_WithComment_AddsCommentToWhereClause()
{
// Arrange
var query = new QueryBreakdown();
var col = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("ACTIVE", "USERS");
var expr = col == 1;
// Act
query.AddWhereExpression(expr, "/* Filter active users */");
// Assert
Assert.That(query.WhereClause.Comment, Is.EqualTo("/* Filter active users */"));
}
[Test]
public void AddWhereExpression_WithMultipleComments_AppendsComments()
{
// Arrange
var query = new QueryBreakdown();
var col1 = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("ACTIVE", "USERS");
var col2 = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("VERIFIED", "USERS");
var expr1 = col1 == 1;
var expr2 = col2 == new Strata.SqlTools.SqlBreakdown.Expressions.Literals.BooleanLiteralExpression(true);
// Act
query.AddWhereExpression(expr1, "/* Active users */");
query.AddWhereExpression(expr2, "/* Verified only */");
// Assert
Assert.That(query.WhereClause.Comment, Is.EqualTo("/* Active users */ /* Verified only */"));
}
[Test]
public void AddWhereExpression_WithNullExpression_ThrowsArgumentNullException()
{
// Arrange
var query = new QueryBreakdown();
// Act & Assert
Assert.Throws<ArgumentNullException>(() => query.AddWhereExpression(null!));
}
[Test]
public void AddWhereExpression_WithStringComparison_UsesCorrectQuotes()
{
// Arrange
var query = new QueryBreakdown();
var col = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("NAME", "USERS");
var expr = col == "John";
// Act
query.AddWhereExpression(expr);
// Assert
Assert.That(query.WhereClause.Clause, Does.Contain("USERS.NAME"));
Assert.That(query.WhereClause.Clause, Does.Contain("'John'"));
}
[Test]
public void AddWhereExpression_MixingSyntaxModes_AllowsBothInSameQuery()
{
// Arrange
var query = new QueryBreakdown();
var col1 = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("ACTIVE", "USERS");
var col2 = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("Status", "Users");
var expr1 = col1 == 1;
var expr2 = col2 == "Active";
// Act
query.AddWhereExpression(expr1, null, "and", isMicrosoftSql: false); // Snowflake
query.AddWhereExpression(expr2, null, "and", isMicrosoftSql: true); // SQL Server
// Assert
// Should contain both syntaxes
Assert.That(query.WhereClause.Clause, Does.Contain("USERS.ACTIVE")); // Snowflake style
Assert.That(query.WhereClause.Clause, Does.Contain("[Users].[Status]")); // SQL Server style
}
[Test]
public void AddWhereExpression_WithComplexExpression_GeneratesCorrectSql()
{
// Arrange
var query = new QueryBreakdown();
var col1 = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("PRICE", "PRODUCTS");
var col2 = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("DISCOUNT", "PRODUCTS");
var expr = (col1 - col2) > 100;
// Act
query.AddWhereExpression(expr);
// Assert
Assert.That(query.WhereClause.Clause, Does.Contain("PRODUCTS.PRICE"));
Assert.That(query.WhereClause.Clause, Does.Contain("-"));
Assert.That(query.WhereClause.Clause, Does.Contain("PRODUCTS.DISCOUNT"));
Assert.That(query.WhereClause.Clause, Does.Contain(">"));
}
[Test]
public void AddSelectExpression_ToExistingClause_PreservesExistingContent()
{
// Arrange
var query = new QueryBreakdown();
query.SelectClause.Clause = "ID";
var expr = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("NAME", "USERS");
// Act
query.AddSelectExpression(expr);
// Assert
Assert.That(query.SelectClause.Clause, Does.StartWith("ID, "));
Assert.That(query.SelectClause.Clause, Does.Contain("USERS.NAME"));
}
[Test]
public void AddWhereExpression_ToExistingClause_PreservesExistingContent()
{
// Arrange
var query = new QueryBreakdown();
query.WhereClause.Clause = "STATUS = 'ACTIVE'";
var col = new Strata.SqlTools.SqlBreakdown.Expressions.GenericColumnExpression("AGE", "USERS");
var expr = col > 18;
// Act
query.AddWhereExpression(expr);
// Assert
Assert.That(query.WhereClause.Clause, Does.StartWith("STATUS = 'ACTIVE' and "));
Assert.That(query.WhereClause.Clause, Does.Contain("USERS.AGE"));
}
#endregion
#region AddWhereClause Parameter Extraction Tests
[Test]
public void AddWhereClause_WithSnowflakeParameters_ExtractsParametersAutomatically()
{
// Arrange
var query = new QueryBreakdown();
// Act - Use Snowflake :param syntax
query.AddWhereClause("USER_ID = :UserId AND STATUS = :Status", false);
Assert.Multiple(() =>
{
// Assert
Assert.That(query.WhereClause.Clause, Is.EqualTo("USER_ID = :UserId AND STATUS = :Status"));
Assert.That(query.Parameters, Does.ContainKey(":UserId"));
});
Assert.That(query.Parameters, Does.ContainKey(":Status"));
Assert.Multiple(() =>
{
Assert.That(query.Parameters[":UserId"], Is.Null);
Assert.That(query.Parameters[":Status"], Is.Null);
});
}
[Test]
public void AddWhereClause_WithMicrosoftParameters_ExtractsParametersAutomatically()
{
// Arrange
var query = new QueryBreakdown();
// Act - Use Microsoft @param syntax with isMicrosoftSql=true
query.AddWhereClause("USER_ID = @UserId AND STATUS = @Status", "and", true);
Assert.Multiple(() =>
{
// Assert
Assert.That(query.WhereClause.Clause, Is.EqualTo("USER_ID = @UserId AND STATUS = @Status"));
Assert.That(query.Parameters, Does.ContainKey("@UserId"));
});
Assert.That(query.Parameters, Does.ContainKey("@Status"));
}
[Test]
public void AddWhereClause_WithExistingParameter_PreservesValue()
{
// Arrange
var query = new QueryBreakdown();
query.Parameters[":UserId"] = 123;
// Act - Add where clause with same parameter
query.AddWhereClause("USER_ID = :UserId", false);
// Assert - Original value should be preserved
Assert.That(query.Parameters[":UserId"], Is.EqualTo(123));
}
[Test]
public void AddWhereClause_WithMultipleCalls_AccumulatesSnowflakeParameters()
{
// Arrange
var query = new QueryBreakdown();
// Act
query.AddWhereClause("USER_ID = :UserId", false);
query.AddWhereClause("STATUS = :Status", "and", false);
query.AddWhereClause("CREATED_DATE > :StartDate", "and", false);
// Assert
Assert.That(query.Parameters, Has.Count.GreaterThanOrEqualTo(3));
Assert.That(query.Parameters, Does.ContainKey(":UserId"));
Assert.That(query.Parameters, Does.ContainKey(":Status"));
Assert.That(query.Parameters, Does.ContainKey(":StartDate"));
}
#endregion
}