chore: initial git load of code space

This commit is contained in:
Thom Lamb
2026-05-12 08:52:33 -05:00
parent 9abada692f
commit 5e467bcc9c
384 changed files with 65960 additions and 2 deletions
@@ -0,0 +1,444 @@
using NUnit.Framework;
using Strata.SqlTools.Breakdowns.SqlServer;
using Strata.SqlTools.SqlBreakdown.Classes;
using Strata.SqlTools.SqlBreakdown.Exceptions;
using Strata.SqlTools.SqlServer.Exceptions;
namespace Strata.SqlTools.SqlBreakdown.Tests.Exceptions;
[TestFixture]
public class ExceptionHandlingTests
{
#region SqlParseException Tests
[Test]
public void SqlParseException_StoresProperties()
{
// Arrange
var message = "Unexpected token";
var sql = "SELECT * FRM users";
var position = 10;
// Act
var exception = new SqlParseException(message, sql, position);
// Assert
Assert.That(exception.Message, Does.Contain(message));
Assert.That(exception.Sql, Is.EqualTo(sql));
Assert.That(exception.Position, Is.EqualTo(position));
Assert.That(exception.NearText, Is.Not.Empty);
}
[Test]
public void SqlParseException_FormatsMessageWithContext()
{
// Arrange
var message = "Invalid syntax";
var sql = "SELECT id, name FROM users WHERE active = INVALID";
var position = 40;
// Act
var exception = new SqlParseException(message, sql, position);
// Assert
Assert.That(exception.Message, Does.Contain("Invalid syntax"));
Assert.That(exception.Message, Does.Contain("Position: 40"));
Assert.That(exception.Message, Does.Contain("Near:"));
}
[Test]
public void SqlParseException_HandlesNullSql()
{
// Arrange
var message = "SQL is null";
string? sql = null;
var position = 0;
// Act
var exception = new SqlParseException(message, sql!, position);
// Assert
Assert.That(exception.Message, Does.Contain("SQL is null"));
Assert.That(exception.Sql, Is.EqualTo(string.Empty));
Assert.That(exception.NearText, Is.EqualTo(string.Empty));
}
[Test]
public void SqlParseException_TruncatesLongSql()
{
// Arrange
var message = "Parse error";
var sql = new string('A', 250) + " FROM users";
var position = 100;
// Act
var exception = new SqlParseException(message, sql, position);
// Assert
Assert.That(exception.Message, Does.Contain("truncated"));
}
[Test]
public void SqlParseException_WithInnerException()
{
// Arrange
var message = "Parse failed";
var sql = "SELECT * FROM users";
var position = 5;
var innerException = new InvalidOperationException("Original error");
// Act
var exception = new SqlParseException(message, sql, position, innerException);
// Assert
Assert.That(exception.InnerException, Is.EqualTo(innerException));
Assert.That(exception.Message, Does.Contain("Parse failed"));
}
#endregion
#region CteValidationException Tests
[Test]
public void CteValidationException_StoresProperties()
{
// Arrange
var message = "CTE name required";
var cteName = "my_cte";
var validationRule = "TableNameRequired";
// Act
var exception = new CteValidationException(message, cteName, validationRule);
// Assert
Assert.That(exception.Message, Does.Contain(message));
Assert.That(exception.CteName, Is.EqualTo(cteName));
Assert.That(exception.ValidationRule, Is.EqualTo(validationRule));
}
[Test]
public void CteValidationException_IncludesHintForKnownRules()
{
// Arrange
var message = "Table name is required";
var validationRule = "TableNameRequired";
// Act
var exception = new CteValidationException(message, null, validationRule);
// Assert
Assert.That(exception.Message, Does.Contain("Hint:"));
Assert.That(exception.Message, Does.Contain("non-empty table name"));
}
[Test]
public void CteValidationException_HandlesUnknownRule()
{
// Arrange
var message = "Validation failed";
var validationRule = "UnknownRule123";
// Act
var exception = new CteValidationException(message, "my_cte", validationRule);
// Assert
Assert.That(exception.ValidationRule, Is.EqualTo(validationRule));
Assert.That(exception.Message, Does.Contain("Validation failed"));
}
[Test]
public void CteValidationException_WithInnerException()
{
// Arrange
var message = "Validation error";
var cteName = "test_cte";
var validationRule = "DuplicateCteName";
var innerException = new ArgumentException("Duplicate");
// Act
var exception = new CteValidationException(message, cteName, validationRule, innerException);
// Assert
Assert.That(exception.InnerException, Is.EqualTo(innerException));
}
#endregion
#region QueryBreakdown.Parse Exception Tests
[Test]
public void Parse_WithNullSql_ThrowsArgumentNullException()
{
// Arrange
string? sql = null;
// Act & Assert
Assert.Throws<ArgumentNullException>(() => QueryBreakdown.Parse(sql!));
}
[Test]
public void Parse_WithEmptySql_ThrowsArgumentNullException()
{
// Arrange
var sql = "";
// Act & Assert
Assert.Throws<ArgumentNullException>(() => QueryBreakdown.Parse(sql));
}
[Test]
public void Parse_WithWhitespaceSql_ThrowsArgumentNullException()
{
// Arrange
var sql = " ";
// Act & Assert
Assert.Throws<ArgumentNullException>(() => QueryBreakdown.Parse(sql));
}
[Test]
public void Parse_WithInvalidSql_ThrowsSqlParseException()
{
// Arrange
var sql = "INVALID SQL STATEMENT";
// Act & Assert
var exception = Assert.Throws<SqlParseException>(() => QueryBreakdown.Parse(sql));
Assert.That(exception!.Sql, Is.EqualTo(sql));
Assert.That(exception.Message, Does.Contain("Failed to parse"));
}
[Test]
public void Parse_WithMalformedSql_ProvidesSqlParseExceptionWithContext()
{
// Arrange - Use SQL that will definitely fail parsing
var sql = "SELECT WHERE FROM";
// Act
var exception = Assert.Throws<SqlParseException>(() => QueryBreakdown.Parse(sql));
// Assert
Assert.That(exception, Is.Not.Null);
Assert.That(exception!.Sql, Is.EqualTo(sql));
}
#endregion
#region AddWithClause Validation Tests
[Test]
public void AddWithClause_WithNullTableName_ThrowsCteValidationException()
{
// Arrange
var query = new QueryBreakdown();
var cteQuery = new QueryBreakdown("id, name", "users");
// Act & Assert
var exception = Assert.Throws<CteValidationException>(() =>
query.AddWithClause(null!, cteQuery));
Assert.That(exception!.ValidationRule, Is.EqualTo("TableNameRequired"));
}
[Test]
public void AddWithClause_WithEmptyTableName_ThrowsCteValidationException()
{
// Arrange
var query = new QueryBreakdown();
var cteQuery = new QueryBreakdown("id, name", "users");
// Act & Assert
var exception = Assert.Throws<CteValidationException>(() =>
query.AddWithClause("", cteQuery));
Assert.That(exception!.ValidationRule, Is.EqualTo("TableNameRequired"));
}
[Test]
public void AddWithClause_WithWhitespaceTableName_ThrowsCteValidationException()
{
// Arrange
var query = new QueryBreakdown();
var cteQuery = new QueryBreakdown("id, name", "users");
// Act & Assert
var exception = Assert.Throws<CteValidationException>(() =>
query.AddWithClause(" ", cteQuery));
Assert.That(exception!.ValidationRule, Is.EqualTo("TableNameRequired"));
}
[Test]
public void AddWithClause_WithNullQuery_ThrowsCteValidationException()
{
// Arrange
var query = new QueryBreakdown();
// Act & Assert
var exception = Assert.Throws<CteValidationException>(() =>
query.AddWithClause("test_cte", (QueryBreakdown)null!));
Assert.That(exception!.ValidationRule, Is.EqualTo("QueryRequired"));
Assert.That(exception.CteName, Is.EqualTo("test_cte"));
}
[Test]
public void AddWithClause_WithDuplicateName_ThrowsCteValidationException()
{
// Arrange
var query = new QueryBreakdown();
var cte1 = new QueryBreakdown("id", "users");
var cte2 = new QueryBreakdown("name", "products");
query.AddWithClause("my_cte", cte1);
// Act & Assert
var exception = Assert.Throws<CteValidationException>(() =>
query.AddWithClause("my_cte", cte2));
Assert.That(exception!.ValidationRule, Is.EqualTo("DuplicateCteName"));
Assert.That(exception.Message, Does.Contain("already exists"));
}
[Test]
public void AddWithClause_WithDuplicateName_CaseInsensitive_ThrowsCteValidationException()
{
// Arrange
var query = new QueryBreakdown();
var cte1 = new QueryBreakdown("id", "users");
var cte2 = new QueryBreakdown("name", "products");
query.AddWithClause("MY_CTE", cte1);
// Act & Assert
var exception = Assert.Throws<CteValidationException>(() =>
query.AddWithClause("my_cte", cte2));
Assert.That(exception!.ValidationRule, Is.EqualTo("DuplicateCteName"));
}
[Test]
public void AddWithClause_WithSql_NullTableName_ThrowsCteValidationException()
{
// Arrange
var query = new QueryBreakdown();
// Act & Assert
var exception = Assert.Throws<CteValidationException>(() =>
query.AddWithClause(null!, "SELECT * FROM users"));
Assert.That(exception!.ValidationRule, Is.EqualTo("TableNameRequired"));
}
[Test]
public void AddWithClause_WithSql_NullSql_ThrowsCteValidationException()
{
// Arrange
var query = new QueryBreakdown();
// Act & Assert
var exception = Assert.Throws<CteValidationException>(() =>
query.AddWithClause("my_cte", (string)null!));
Assert.That(exception!.ValidationRule, Is.EqualTo("QueryRequired"));
}
[Test]
public void AddWithClause_WithSql_InvalidSql_ThrowsSqlParseException()
{
// Arrange
var query = new QueryBreakdown();
// Act & Assert
var exception = Assert.Throws<SqlParseException>(() =>
query.AddWithClause("my_cte", "INVALID SQL"));
Assert.That(exception!.Message, Does.Contain("my_cte"));
Assert.That(exception.Sql, Is.EqualTo("INVALID SQL"));
}
[Test]
public void AddWithClause_WithIWithClause_NullClause_ThrowsCteValidationException()
{
// Arrange
var query = new QueryBreakdown();
// Act & Assert
var exception = Assert.Throws<CteValidationException>(() =>
query.AddWithClause((WithClause)null!));
Assert.That(exception!.ValidationRule, Is.EqualTo("QueryRequired"));
}
[Test]
public void AddWithClause_WithIWithClause_NoQueryOrSql_ThrowsCteValidationException()
{
// Arrange
var query = new QueryBreakdown();
var withClause = new WithClause
{
TableName = "test_cte"
// Query and Sql are both null
};
// Act & Assert
var exception = Assert.Throws<CteValidationException>(() =>
query.AddWithClause(withClause));
Assert.That(exception!.ValidationRule, Is.EqualTo("QueryRequired"));
Assert.That(exception.Message, Does.Contain("Query or Sql"));
}
[Test]
public void AddWithClause_ValidCte_Succeeds()
{
// Arrange
var query = new QueryBreakdown();
var cteQuery = new QueryBreakdown("id, name", "users", "active = 1");
// Act
query.AddWithClause("active_users", cteQuery);
// Assert
Assert.That(query.WithClauses.Count, Is.EqualTo(1));
Assert.That(query.WithClauses[0].TableName, Is.EqualTo("active_users"));
}
#endregion
#region Exception Message Quality Tests
[Test]
public void CteValidationException_TableNameRequired_HasHelpfulMessage()
{
// Act
var exception = new CteValidationException(
"CTE table name cannot be null",
null,
"TableNameRequired");
// Assert
Assert.That(exception.Message, Does.Contain("non-empty table name"));
Assert.That(exception.Message, Does.Contain("Hint:"));
}
[Test]
public void CteValidationException_DuplicateCteName_HasHelpfulMessage()
{
// Act
var exception = new CteValidationException(
"Duplicate CTE name",
"my_cte",
"DuplicateCteName");
// Assert
Assert.That(exception.Message, Does.Contain("unique"));
Assert.That(exception.Message, Does.Contain("Hint:"));
}
[Test]
public void SqlParseException_ShowsNearText()
{
// Arrange
var sql = "SELECT id, name FROM users WHERE active = 1 AND status = 'INVALID'";
var position = 50;
// Act
var exception = new SqlParseException("Syntax error", sql, position);
// Assert
Assert.That(exception.NearText, Does.Contain("status"));
Assert.That(exception.Message, Does.Contain("Near:"));
}
#endregion
}
@@ -0,0 +1,117 @@
using Strata.SqlTools.SqlBreakdown.Classes;
using Strata.SqlTools.SqlBreakdown.Expressions;
using Strata.SqlTools.SqlBreakdown.Expressions.Functions.Aggregate;
using Strata.SqlTools.SqlBreakdown.Expressions.Literals;
namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests;
[TestFixture]
public class ArithmeticExpressionTests : ExpressionTestsBase
{
private static IEnumerable<TestCaseData> ArithmeticExpressionTestCases()
{
var testCases = new[]
{
new ExpressionTestCase
{
Name = "WithSum_{m}",
Arrange = new Dictionary<string, Expression>
{
["revenue"] = new RegisteredTableColumnExpression(2, "NET_REVENUE", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES")),
["cost"] = new RegisteredTableColumnExpression(3, "COST", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES"))
},
Act = (columns, visitor) =>
{
var revenue = columns["revenue"];
var cost = columns["cost"];
var addExp = new SumFunction(revenue - cost - 1001);
return addExp.Accept(visitor);
},
Assertions = result =>
{
var sql = (string)result;
Assert.That(sql, Does.Contain("SUM"));
Assert.That(sql, Does.Contain("-"));
Assert.That(sql, Does.Contain("1001"));
Assert.That(sql, Is.EqualTo("SUM(PES.NET_REVENUE - PES.COST - 1001)"));
return true;
}
},
new ExpressionTestCase
{
Name = "Addition_{m}",
Arrange = new Dictionary<string, Expression>
{
["revenue"] = new RegisteredTableColumnExpression(2, "NET_REVENUE", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES"))
},
Act = (columns, visitor) =>
{
var revenue = columns["revenue"];
var result = revenue + new NumberLiteralExpression(1000);
return result.Accept(visitor);
},
Assertions = result =>
{
var sql = (string)result;
Assert.That(sql, Does.Contain("NET_REVENUE"));
Assert.That(sql, Does.Contain("+"));
Assert.That(sql, Does.Contain("1000"));
return true;
}
},
new ExpressionTestCase
{
Name = "Multiplication_{m}",
Arrange = new Dictionary<string, Expression>
{
["cost"] = new RegisteredTableColumnExpression(3, "COST", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES"))
},
Act = (columns, visitor) =>
{
var cost = columns["cost"];
var result = cost * new NumberLiteralExpression(1.5m);
return result.Accept(visitor);
},
Assertions = result =>
{
var sql = (string)result;
Assert.That(sql, Does.Contain("COST"));
Assert.That(sql, Does.Contain("*"));
Assert.That(sql, Does.Contain("1.5"));
return true;
}
},
new ExpressionTestCase
{
Name = "Division_{m}",
Arrange = new Dictionary<string, Expression>
{
["revenue"] = new RegisteredTableColumnExpression(2, "NET_REVENUE", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES"))
},
Act = (columns, visitor) =>
{
var revenue = columns["revenue"];
var result = revenue / new NumberLiteralExpression(2);
return result.Accept(visitor);
},
Assertions = result =>
{
var sql = (string)result;
Assert.That(sql, Does.Contain("NET_REVENUE"));
Assert.That(sql, Does.Contain("/"));
Assert.That(sql, Does.Contain("2"));
return true;
}
}
};
foreach (var testCase in testCases)
{
yield return new TestCaseData(testCase).SetName(testCase.Name);
}
}
[TestCaseSource(nameof(ArithmeticExpressionTestCases))]
public void ArithmeticExpression_GeneratesCorrectSql(ExpressionTestCase testCase)
=> ExecuteExpressionTest(testCase);
}
@@ -0,0 +1,134 @@
using Strata.SqlTools.SqlBreakdown.Classes;
using Strata.SqlTools.SqlBreakdown.Expressions;
using Strata.SqlTools.SqlBreakdown.Expressions.Conditional;
using Strata.SqlTools.SqlBreakdown.Expressions.Conditional.Comparisons;
using Strata.SqlTools.SqlBreakdown.Expressions.Conditional.Logical;
namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests;
[TestFixture]
public class BooleanExpressionTests : ExpressionTestsBase
{
[Test]
public void BooleanExpression_AndOperator_ChainsCorrectly()
{
// Arrange
var greaterThan250Exp = new GreaterThanExpression(_costColumnExp, 250);
BooleanExpression original = BooleanExpression.False;
// Act
original &= greaterThan250Exp;
var firstResult = original;
original &= greaterThan250Exp;
var secondResult = original;
// Assert
Assert.That(firstResult, Is.Not.SameAs(BooleanExpression.False));
Assert.That(firstResult, Is.AssignableTo<BooleanExpression>());
Assert.That(secondResult, Is.Not.SameAs(BooleanExpression.False));
Assert.That(secondResult, Is.TypeOf<AndExpression>());
}
[Test]
public void BooleanExpression_ComplexWithOperators_CreatesExpression()
{
// Arrange
var paramFoobar = new ParameterExpression("FOOBAR");
// Act
#pragma warning disable S2178 // Short-circuit logic should be used in boolean contexts
var andComp = _revenueColumnExp > 100 & _costColumnExp >= 250 | _nameColumnExp == paramFoobar;
#pragma warning restore S2178
// Assert
Assert.That(andComp, Is.Not.Null);
Assert.That(andComp, Is.AssignableTo<BooleanExpression>());
}
private static IEnumerable<TestCaseData> BooleanExpressionTestCases()
{
var testCases = new[]
{
new ExpressionTestCase
{
Name = "GreaterThan_{m}",
Arrange = new Dictionary<string, Expression>
{
["revenue"] = new RegisteredTableColumnExpression(2, "NET_REVENUE", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES"))
},
Act = (columns, visitor) =>
{
var revenue = columns["revenue"];
var greaterThan100Exp = new GreaterThanExpression(revenue, 100);
return greaterThan100Exp;
},
Assertions = result =>
{
var expression = (BooleanExpression)result;
Assert.That(expression, Is.Not.Null);
Assert.That(expression, Is.TypeOf<GreaterThanExpression>());
return true;
}
},
new ExpressionTestCase
{
Name = "And_{m}",
Arrange = new Dictionary<string, Expression>
{
["revenue"] = new RegisteredTableColumnExpression(2, "NET_REVENUE", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES")),
["cost"] = new RegisteredTableColumnExpression(3, "COST", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES"))
},
Act = (columns, visitor) =>
{
var revenue = columns["revenue"];
var cost = columns["cost"];
var greaterThan100Exp = new GreaterThanExpression(revenue, 100);
var greaterThan250Exp = new GreaterThanExpression(cost, 250);
var andExp1 = new AndExpression(greaterThan100Exp, greaterThan250Exp);
return andExp1;
},
Assertions = result =>
{
var expression = (BooleanExpression)result;
Assert.That(expression, Is.Not.Null);
Assert.That(expression, Is.TypeOf<AndExpression>());
return true;
}
},
new ExpressionTestCase
{
Name = "Or_{m}",
Arrange = new Dictionary<string, Expression>
{
["revenue"] = new RegisteredTableColumnExpression(2, "NET_REVENUE", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES")),
["cost"] = new RegisteredTableColumnExpression(3, "COST", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES"))
},
Act = (columns, visitor) =>
{
var revenue = columns["revenue"];
var cost = columns["cost"];
#pragma warning disable S2178 // Short-circuit logic should be used in boolean contexts
var orComp = revenue > 100 | cost >= 250;
#pragma warning restore S2178
return orComp;
},
Assertions = result =>
{
var expression = (BooleanExpression)result;
Assert.That(expression, Is.Not.Null);
Assert.That(expression, Is.AssignableTo<BooleanExpression>());
return true;
}
}
};
foreach (var testCase in testCases)
{
yield return new TestCaseData(testCase).SetName(testCase.Name);
}
}
[TestCaseSource(nameof(BooleanExpressionTestCases))]
public void BooleanExpression_GeneratesCorrectExpression(ExpressionTestCase testCase)
=> ExecuteExpressionTest(testCase);
}
@@ -0,0 +1,117 @@
using Strata.SqlTools.SqlBreakdown.Classes;
using Strata.SqlTools.SqlBreakdown.Expressions;
using Strata.SqlTools.SqlBreakdown.Expressions.Functions.Aggregate;
using Strata.SqlTools.SqlBreakdown.Expressions.Functions.Conditional;
namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests;
[TestFixture]
public class ConditionalExpressionTests : ExpressionTestsBase
{
private static IEnumerable<TestCaseData> ConditionalExpressionTestCases()
{
var testCases = new[]
{
new ExpressionTestCase
{
Name = "CaseExpression_{m}",
Arrange = new Dictionary<string, Expression>(),
Act = (columns, visitor) =>
{
var table = new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES");
var nameCol = new RegisteredTableColumnExpression(1, "DEPARTMENT_NAME", table);
var revenueCol = new RegisteredTableColumnExpression(2, "NET_REVENUE", table);
var caseExp = new CaseExpression(nameCol == "department 1", revenueCol, 0);
return caseExp.Accept(visitor);
},
Assertions = result =>
{
var sql = (string)result;
Assert.That(sql, Does.Contain("CASE"));
Assert.That(sql, Does.Contain("WHEN"));
Assert.That(sql, Does.Contain("THEN"));
Assert.That(sql, Does.Contain("ELSE"));
Assert.That(sql, Does.Contain("END"));
return true;
}
},
new ExpressionTestCase
{
Name = "CaseExpressionWithSum_{m}",
Arrange = new Dictionary<string, Expression>(),
Act = (columns, visitor) =>
{
var table = new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES");
var nameCol = new RegisteredTableColumnExpression(1, "DEPARTMENT_NAME", table);
var revenueCol = new RegisteredTableColumnExpression(2, "NET_REVENUE", table);
var caseExp = new CaseExpression(nameCol == "department 1", revenueCol, 0);
var sumCaseExp = new SumFunction(caseExp);
return sumCaseExp.Accept(visitor);
},
Assertions = result =>
{
var sql = (string)result;
Assert.That(sql, Does.Contain("SUM"));
Assert.That(sql, Does.Contain("CASE"));
Assert.That(sql, Does.Contain("WHEN"));
Assert.That(sql, Is.EqualTo("SUM(CASE\n WHEN PES.DEPARTMENT_NAME = 'department 1' THEN PES.NET_REVENUE\n ELSE 0\nEND)"));
return true;
}
},
new ExpressionTestCase
{
Name = "CaseExpressionWithSumInSelectColumn_{m}",
Arrange = new Dictionary<string, Expression>(),
Act = (columns, visitor) =>
{
var table = new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES");
var nameCol = new RegisteredTableColumnExpression(1, "DEPARTMENT_NAME", table);
var revenueCol = new RegisteredTableColumnExpression(2, "NET_REVENUE", table);
var caseExp = new CaseExpression(nameCol == "department 1", revenueCol, 0);
var sumCaseExp = new SumFunction(caseExp);
var column = new SelectClauseColumn(sumCaseExp, "mySumCaseCol");
return column.Accept(visitor);
},
Assertions = result =>
{
var sql = (string)result;
Assert.That(sql, Does.Contain("SUM"));
Assert.That(sql, Does.Contain("CASE"));
Assert.That(sql, Does.Contain("mySumCaseCol"));
Assert.That(sql, Is.EqualTo("SUM(CASE\n WHEN PES.DEPARTMENT_NAME = 'department 1' THEN PES.NET_REVENUE\n ELSE 0\nEND) AS mySumCaseCol"));
return true;
}
},
new ExpressionTestCase
{
Name = "IfThenElseExpression_{m}",
Arrange = new Dictionary<string, Expression>(),
Act = (columns, visitor) =>
{
var table = new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES");
var nameCol = new RegisteredTableColumnExpression(1, "DEPARTMENT_NAME", table);
var revenueCol = new RegisteredTableColumnExpression(2, "NET_REVENUE", table);
var costCol = new RegisteredTableColumnExpression(3, "COST", table);
var ifElseExp = new IfThenElseExpression(nameCol == "myDeptName", revenueCol, costCol);
return ifElseExp.Accept(visitor);
},
Assertions = result =>
{
var sql = (string)result;
Assert.That(sql, Does.Contain("IFF"));
Assert.That(sql, Is.EqualTo("IFF(PES.DEPARTMENT_NAME = 'myDeptName', PES.NET_REVENUE, PES.COST)"));
return true;
}
}
};
foreach (var testCase in testCases)
{
yield return new TestCaseData(testCase).SetName(testCase.Name);
}
}
[TestCaseSource(nameof(ConditionalExpressionTestCases))]
public void ConditionalExpression_GeneratesCorrectSql(ExpressionTestCase testCase)
=> ExecuteExpressionTest(testCase);
}
@@ -0,0 +1,46 @@
using Strata.SqlTools.SqlBreakdown.Classes;
using Strata.SqlTools.SqlBreakdown.Expressions;
namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests;
[TestFixture]
public class DateFunctionTests : ExpressionTestsBase
{
private static IEnumerable<TestCaseData> DateFunctionTestCases()
{
var testCases = new[]
{
new ExpressionTestCase
{
Name = "GetFiscalYearMonthExpression_{m}",
Arrange = new Dictionary<string, Expression>
{
["dischargeDate"] = new RegisteredTableColumnExpression(4, "DISCHARGE_DATE", new RegisteredTableSource(101, "CLIENT_DSS", "DEPT", "PES"))
},
Act = (columns, visitor) =>
{
var dischargeDate = columns["dischargeDate"];
var fiscal = TestExpressionFactory.GetFiscalYearMonthExpression(dischargeDate, 7, 1);
return fiscal.Accept(visitor);
},
Assertions = result =>
{
var sql = (string)result;
Assert.That(sql, Is.Not.Null);
Assert.That(sql.Length, Is.GreaterThan(0));
Assert.That(sql, Is.EqualTo("DATE_TRUNC('month', DATEADD('month', 6, PES.DISCHARGE_DATE))"));
return true;
}
}
};
foreach (var testCase in testCases)
{
yield return new TestCaseData(testCase).SetName(testCase.Name);
}
}
[TestCaseSource(nameof(DateFunctionTestCases))]
public void DateFunction_GeneratesCorrectSql(ExpressionTestCase testCase)
=> ExecuteExpressionTest(testCase);
}
@@ -0,0 +1,51 @@
using Strata.SqlTools.SqlServer.ExpressionFactory.Query;
namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests;
[TestFixture]
public class ExpressionFactoryFilterTests : ExpressionTestsBase
{
private static IEnumerable<TestCaseData> FilterTestCases()
{
yield return new TestCaseData(
new Filter(2, FilterType.List, new object[] { "foo", "bar", "baz", 1234 }, Array.Empty<FilterCondition>(), DatePart.Continuous, false, 0, 0),
"DEPT.NAME IN ('foo', 'bar', 'baz', 1234)"
).SetName("ListFilterContinuous_{m}");
yield return new TestCaseData(
new Filter(4, FilterType.List, new[] { "FY2019", "FY2020", "FY2021", "FY2022" }, Array.Empty<FilterCondition>(), DatePart.FiscalYear, false, 0, 0),
"(DEPT.DISCHARGE_DATE >= '2018-07-01' AND DEPT.DISCHARGE_DATE < '2019-07-01') OR \n(DEPT.DISCHARGE_DATE >= '2019-07-01' AND DEPT.DISCHARGE_DATE < '2020-07-01') OR \n(DEPT.DISCHARGE_DATE >= '2020-07-01' AND DEPT.DISCHARGE_DATE < '2021-07-01') OR \n(DEPT.DISCHARGE_DATE >= '2021-07-01' AND DEPT.DISCHARGE_DATE < '2022-07-01')"
).SetName("DateListFilterFiscalYear_{m}");
yield return new TestCaseData(
new Filter(4, FilterType.List, new[] { "01-2020" }, Array.Empty<FilterCondition>(), DatePart.Month, false, 0, 0),
"DEPT.DISCHARGE_DATE >= '2020-01-01' AND DEPT.DISCHARGE_DATE < '2020-02-01'"
).SetName("MonthListFilter_{m}");
yield return new TestCaseData(
new Filter(4, FilterType.Calendar, new[] { "01/01/2019", "01/01/2023" }, Array.Empty<FilterCondition>(), DatePart.Month, false, 0, 0),
"DEPT.DISCHARGE_DATE >= '2019-01-01' AND DEPT.DISCHARGE_DATE < '2023-01-01'"
).SetName("CalendarFilter_{m}");
yield return new TestCaseData(
new Filter(4, FilterType.Timeframe, new object[] { }, Array.Empty<FilterCondition>(), DatePart.Month, false, 1, 3),
"DEPT.DISCHARGE_DATE >= '2026-03-01' AND DEPT.DISCHARGE_DATE < '2026-06-01'"
).SetName("TimeframeFilter_{m}");
}
[TestCaseSource(nameof(FilterTestCases))]
public void ExpressionFactory_Filter_GeneratesCorrectSql(Filter filter, string expectedSql)
{
// Arrange
var factory = new TestExpressionFactory();
// Act
var condition = factory.CreateBooleanExpression(filter);
var result = condition.Accept(_sqlVisitor);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result.Length, Is.GreaterThan(0));
Assert.That(result, Is.EqualTo(expectedSql));
}
}
@@ -0,0 +1,72 @@
using Strata.SqlTools.SqlBreakdown.Expressions;
using Strata.SqlTools.SqlBreakdown.Expressions.Conditional;
using Strata.SqlTools.SqlBreakdown.Expressions.Conditional.Comparisons;
namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests;
[TestFixture]
public class ExpressionObjectTests : ExpressionTestsBase
{
[Test]
public void RegisteredTableColumnExpression_CreatesWithProperties()
{
// Arrange & Act
var columnExp = new RegisteredTableColumnExpression(5, "TEST_COLUMN", _table);
// Assert
Assert.That(columnExp, Is.Not.Null);
Assert.That(columnExp.ColumnName, Is.EqualTo("TEST_COLUMN"));
}
[Test]
public void ParameterExpression_CreatesWithName()
{
// Arrange & Act
var paramExp = new ParameterExpression("MY_PARAM");
// Assert
Assert.That(paramExp, Is.Not.Null);
Assert.That(paramExp.ParameterName, Is.EqualTo("MY_PARAM"));
}
private static IEnumerable<TestCaseData> ComparisonExpressionTestCases()
{
yield return new TestCaseData("GreaterThanOrEqual", 250, typeof(GreaterThanOrEqualToExpression))
.SetName("GreaterThanOrEqual_{m}");
yield return new TestCaseData("LessThan", 1000, typeof(LessThanExpression))
.SetName("LessThan_{m}");
yield return new TestCaseData("Equals", "TestDept", typeof(EqualToExpression))
.SetName("Equals_{m}");
yield return new TestCaseData("NotIn", new[] { "value1", "value2", "value3" }, typeof(NotInExpression))
.SetName("NotIn_{m}");
yield return new TestCaseData("Like", "%pattern%", typeof(LikeExpression))
.SetName("Like_{m}");
yield return new TestCaseData("Between", new object[] { 0, 5000 }, typeof(BetweenExpression))
.SetName("Between_{m}");
}
[TestCaseSource(nameof(ComparisonExpressionTestCases))]
public void ComparisonExpression_CreatesExpression(string expressionType, object testValue, Type expectedType)
{
// Arrange & Act
BooleanExpression expression = expressionType switch
{
"GreaterThanOrEqual" => new GreaterThanOrEqualToExpression(_costColumnExp, (int)testValue),
"LessThan" => new LessThanExpression(_revenueColumnExp, (int)testValue),
"Equals" => new EqualToExpression(_nameColumnExp, (string)testValue),
"NotIn" => new NotInExpression(_nameColumnExp, ((string[])testValue).Select(v => (Expression)v).ToArray()),
"Like" => new LikeExpression(_nameColumnExp, (string)testValue),
"Between" => new BetweenExpression(_revenueColumnExp, (Expression)(int)((object[])testValue)[0], (Expression)(int)((object[])testValue)[1]),
_ => throw new ArgumentException($"Unknown expression type: {expressionType}")
};
// Assert
Assert.That(expression, Is.Not.Null);
Assert.That(expression, Is.TypeOf(expectedType));
}
}
@@ -0,0 +1,30 @@
using Strata.SqlTools.SqlBreakdown.Expressions;
using Strata.SqlTools.Visitors.Snowflake;
namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests;
/// <summary>
/// Represents a test case for expression tests with arrange, act, and assert phases.
/// </summary>
public class ExpressionTestCase
{
/// <summary>
/// Gets or initializes the name of the test case.
/// </summary>
public string Name { get; init; } = string.Empty;
/// <summary>
/// Gets or initializes the dictionary of expressions to arrange for the test.
/// </summary>
public Dictionary<string, Expression> Arrange { get; init; } = new();
/// <summary>
/// Gets or initializes the action to execute during the act phase of the test.
/// </summary>
public Func<Dictionary<string, Expression>, CommandVisitor, object> Act { get; init; } = null!;
/// <summary>
/// Gets or initializes the function to execute during the assert phase of the test that returns true if all assertions pass.
/// </summary>
public Func<object, bool> Assertions { get; init; } = null!;
}
@@ -0,0 +1,72 @@
using Strata.SqlTools.SqlBreakdown.Classes;
using Strata.SqlTools.SqlBreakdown.Expressions;
using Strata.SqlTools.SqlBreakdown.Expressions.Conditional;
using Strata.SqlTools.SqlServer.ExpressionFactory.Query;
using Strata.SqlTools.SqlBreakdown.Tests.RegisteredTables;
using Strata.SqlTools.Visitors.Snowflake;
using ExpressionFactoryBase = Strata.SqlTools.SqlServer.ExpressionFactory.ExpressionFactory;
namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests;
/// <summary>
/// Test implementation of ExpressionFactory for unit testing.
/// Uses July 1 as the fiscal year start date.
/// </summary>
internal class TestExpressionFactory : ExpressionFactoryBase
{
protected override DateTime GetCurrentFiscalYearStart()
{
return new DateTime(DateTime.UtcNow.Year, 7, 1, 0, 0, 0, DateTimeKind.Utc);
}
protected override RegisteredTableColumnExpression GetColumnExpression(int dataColumnId)
{
return RegisteredTableColumns.GetColumn(dataColumnId);
}
// Public wrappers for testing protected methods
public new BooleanExpression CreateBooleanExpression(Filter filter) => base.CreateBooleanExpression(filter);
public static new Expression GetFiscalYearMonthExpression(Expression dateColumnExpr, int fiscalYearStartMonth, int fiscalYearStartDay)
=> ExpressionFactoryBase.GetFiscalYearMonthExpression(dateColumnExpr, fiscalYearStartMonth, fiscalYearStartDay);
}
/// <summary>
/// Base class for expression tests with common setup.
/// </summary>
public abstract class ExpressionTestsBase
{
protected RegisteredTableSource _table = null!;
protected RegisteredTableColumnExpression _nameColumnExp = null!;
protected RegisteredTableColumnExpression _revenueColumnExp = null!;
protected RegisteredTableColumnExpression _costColumnExp = null!;
protected RegisteredTableColumnExpression _dischargeDateColumnExp = null!;
protected CommandVisitor _sqlVisitor = null!;
[SetUp]
public void SetUp()
{
_table = new RegisteredTableSource(101, "CLIENT_DSS", "FACT_PATIENT_ENCOUNTER_SUMMARY", "PES");
_nameColumnExp = new RegisteredTableColumnExpression(1, "DEPARTMENT_NAME", _table);
_revenueColumnExp = new RegisteredTableColumnExpression(2, "NET_REVENUE", _table);
_costColumnExp = new RegisteredTableColumnExpression(3, "COST", _table);
_dischargeDateColumnExp = new RegisteredTableColumnExpression(4, "DISCHARGE_DATE", _table);
_sqlVisitor = new CommandVisitor();
}
/// <summary>
/// Executes an expression test case with arrange, act, and assert phases.
/// </summary>
/// <param name="testCase">The test case to execute.</param>
protected void ExecuteExpressionTest(ExpressionTestCase testCase)
{
// Arrange
var visitor = new CommandVisitor();
var arrange = testCase.Arrange;
// Act
var result = testCase.Act(arrange, visitor);
// Assert
Assert.That(testCase.Assertions(result));
}
}
@@ -0,0 +1,203 @@
using Strata.SqlTools.SqlBreakdown.Expressions;
using Strata.SqlTools.SqlBreakdown.Expressions.Literals;
namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests;
[TestFixture]
public class GenericColumnExpressionTests : ExpressionTestsBase
{
private static IEnumerable<TestCaseData> GenericColumnExpressionTestCases()
{
var testCases = new[]
{
new ExpressionTestCase
{
Name = "WithArithmeticOperatorsAndLiterals_{m}",
Arrange = new Dictionary<string, Expression>
{
["price"] = new GenericColumnExpression("Price", "Products"),
["quantity"] = new GenericColumnExpression("Quantity", "Products")
},
Act = (columns, visitor) =>
{
var price = columns["price"];
var quantity = columns["quantity"];
var priceWithTax = price * new NumberLiteralExpression(1.1m);
var priceWithFee = price + new NumberLiteralExpression(10);
var revenue = quantity * price;
return new Dictionary<string, string>
{
["priceWithTax"] = priceWithTax.Accept(visitor),
["priceWithFee"] = priceWithFee.Accept(visitor),
["revenue"] = revenue.Accept(visitor)
};
},
Assertions = result =>
{
var results = (Dictionary<string, string>)result;
Assert.That(results["priceWithTax"], Does.Contain("Price"));
Assert.That(results["priceWithTax"], Does.Contain("1.1"));
Assert.That(results["priceWithFee"], Does.Contain("Price"));
Assert.That(results["priceWithFee"], Does.Contain("10"));
Assert.That(results["revenue"], Does.Contain("Quantity"));
Assert.That(results["revenue"], Does.Contain("Price"));
return true;
}
},
new ExpressionTestCase
{
Name = "WithComparisonOperators_{m}",
Arrange = new Dictionary<string, Expression>
{
["price"] = new GenericColumnExpression("Price", "Products"),
["category"] = new GenericColumnExpression("Category", "Products")
},
Act = (columns, visitor) =>
{
var price = columns["price"];
var category = columns["category"];
var priceCondition = price > 100;
var categoryCondition = category == "Electronics";
return new Dictionary<string, string>
{
["priceCondition"] = priceCondition.Accept(visitor),
["categoryCondition"] = categoryCondition.Accept(visitor)
};
},
Assertions = result =>
{
var results = (Dictionary<string, string>)result;
Assert.That(results["priceCondition"], Does.Contain("Price"));
Assert.That(results["priceCondition"], Does.Contain("100"));
Assert.That(results["categoryCondition"], Does.Contain("Category"));
Assert.That(results["categoryCondition"], Does.Contain("Electronics"));
return true;
}
},
new ExpressionTestCase
{
Name = "InQueryBreakdown_{m}",
Arrange = new Dictionary<string, Expression>
{
["productId"] = new GenericColumnExpression("ProductID", "Products"),
["price"] = new GenericColumnExpression("Price", "Products"),
["category"] = new GenericColumnExpression("Category", "Products")
},
Act = (columns, visitor) =>
{
var query = new Breakdowns.SqlServer.QueryBreakdown();
query.FromClause.Clause = "Products";
var productId = columns["productId"];
var price = columns["price"];
var category = columns["category"];
query.AddSelectExpression(productId, "ID");
query.AddSelectExpression(price * new NumberLiteralExpression(1.1m), "PriceWithTax");
query.AddSelectExpression(price + new NumberLiteralExpression(10), "PriceWithFee");
query.AddWhereExpression(price > 100);
query.AddWhereExpression(category == "Electronics", null, "AND");
return query.GetSql();
},
Assertions = result =>
{
var sql = (string)result;
Assert.That(sql, Does.Contain("ProductID"));
Assert.That(sql, Does.Contain("Price"));
Assert.That(sql, Does.Contain("Category"));
Assert.That(sql, Does.Contain("Products"));
return true;
}
},
new ExpressionTestCase
{
Name = "ComplexArithmetic_{m}",
Arrange = new Dictionary<string, Expression>
{
["quantity"] = new GenericColumnExpression("Quantity", "OrderDetails"),
["unitPrice"] = new GenericColumnExpression("UnitPrice", "OrderDetails"),
["discount"] = new GenericColumnExpression("Discount", "OrderDetails")
},
Act = (columns, visitor) =>
{
var quantity = columns["quantity"];
var unitPrice = columns["unitPrice"];
var discount = columns["discount"];
var totalRevenue = (quantity * unitPrice) * (new NumberLiteralExpression(1) - discount);
return totalRevenue.Accept(visitor);
},
Assertions = result =>
{
var sql = (string)result;
Assert.That(sql, Does.Contain("Quantity"));
Assert.That(sql, Does.Contain("UnitPrice"));
Assert.That(sql, Does.Contain("Discount"));
return true;
}
},
new ExpressionTestCase
{
Name = "WithSchema_{m}",
Arrange = new Dictionary<string, Expression>
{
["customerId"] = new GenericColumnExpression("CustomerID", "dbo", "Customers"),
["orderDate"] = new GenericColumnExpression("OrderDate", "dbo", "Orders")
},
Act = (columns, visitor) =>
{
return new Dictionary<string, string>
{
["customerResult"] = columns["customerId"].Accept(visitor),
["orderResult"] = columns["orderDate"].Accept(visitor)
};
},
Assertions = result =>
{
var results = (Dictionary<string, string>)result;
Assert.That(results["customerResult"], Does.Contain("CustomerID"));
Assert.That(results["orderResult"], Does.Contain("OrderDate"));
return true;
}
},
new ExpressionTestCase
{
Name = "MultipleComparisonOperators_{m}",
Arrange = new Dictionary<string, Expression>
{
["unitPrice"] = new GenericColumnExpression("UnitPrice", "Products"),
["minPrice"] = new GenericColumnExpression("MinPrice", "PriceRanges"),
["maxPrice"] = new GenericColumnExpression("MaxPrice", "PriceRanges")
},
Act = (columns, visitor) =>
{
var unitPrice = columns["unitPrice"];
var minPrice = columns["minPrice"];
var maxPrice = columns["maxPrice"];
var lowerBound = unitPrice >= minPrice;
var upperBound = unitPrice <= maxPrice;
return new Dictionary<string, string>
{
["lowerBound"] = lowerBound.Accept(visitor),
["upperBound"] = upperBound.Accept(visitor)
};
},
Assertions = result =>
{
var results = (Dictionary<string, string>)result;
Assert.That(results["lowerBound"], Does.Contain("UnitPrice"));
Assert.That(results["lowerBound"], Does.Contain("MinPrice"));
Assert.That(results["upperBound"], Does.Contain("UnitPrice"));
Assert.That(results["upperBound"], Does.Contain("MaxPrice"));
return true;
}
}
};
foreach (var testCase in testCases)
{
yield return new TestCaseData(testCase).SetName(testCase.Name);
}
}
[TestCaseSource(nameof(GenericColumnExpressionTestCases))]
public void GenericColumnExpression_GeneratesCorrectSql(ExpressionTestCase testCase)
=> ExecuteExpressionTest(testCase);
}
@@ -0,0 +1,40 @@
using Strata.SqlTools.SqlBreakdown.Classes;
using Strata.SqlTools.SqlBreakdown.Expressions;
using Strata.SqlTools.SqlBreakdown.Expressions.Functions.Aggregate;
namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests;
[TestFixture]
public class SelectClauseColumnTests : ExpressionTestsBase
{
private static IEnumerable<TestCaseData> SelectClauseColumnTestCases()
{
yield return new TestCaseData("TableColumn", "foo_dept_name", "PES.DEPARTMENT_NAME AS foo_dept_name")
.SetName("TableColumn_{m}");
yield return new TestCaseData("SumFunction", "sum_net_rev", "SUM(PES.NET_REVENUE) AS sum_net_rev")
.SetName("WithSumFunction_{m}");
yield return new TestCaseData("Parameter", "my_var_col", ":FOOBAR AS my_var_col")
.SetName("WithParameter_{m}");
}
[TestCaseSource(nameof(SelectClauseColumnTestCases))]
public void SelectClauseColumn_GeneratesCorrectSql(string columnType, string alias, string expectedSql)
{
// Arrange
SelectClauseColumn column = columnType switch
{
"TableColumn" => SelectClauseColumn.TableColumn(_nameColumnExp, alias),
"SumFunction" => new SelectClauseColumn(new SumFunction(_revenueColumnExp), alias),
"Parameter" => new SelectClauseColumn(new ParameterExpression("FOOBAR"), alias),
_ => throw new ArgumentException($"Unknown column type: {columnType}")
};
// Act
var result = column.Accept(_sqlVisitor);
// Assert
Assert.That(result, Is.EqualTo(expectedSql));
}
}
@@ -0,0 +1,502 @@
using Strata.SqlTools.Breakdowns.SqlServer;
using Strata.SqlTools.Extensions;
namespace Strata.SqlTools.SqlBreakdown.Tests.Extensions;
[TestFixture]
public class QueryBreakdownExtensionsTests
{
#region Basic Clause Tests
[Test]
public void Select_SetsSelectClause()
{
// Arrange
var query = new QueryBreakdown();
// Act
var result = query.Select("id, name");
// Assert
Assert.That(result, Is.SameAs(query));
Assert.That(query.SelectClause.Clause, Is.EqualTo("id, name"));
}
[Test]
public void From_SetsFromClause()
{
// Arrange
var query = new QueryBreakdown();
// Act
var result = query.From("users");
// Assert
Assert.That(result, Is.SameAs(query));
Assert.That(query.FromClause.Clause, Is.EqualTo("users"));
}
[Test]
public void Where_SetsWhereClause()
{
// Arrange
var query = new QueryBreakdown();
// Act
var result = query.Where("active = 1");
// Assert
Assert.That(result, Is.SameAs(query));
Assert.That(query.WhereClause.Clause, Is.EqualTo("active = 1"));
}
[Test]
public void GroupBy_SetsGroupByClause()
{
// Arrange
var query = new QueryBreakdown();
// Act
var result = query.GroupBy("department");
// Assert
Assert.That(result, Is.SameAs(query));
Assert.That(query.GroupByClause.Clause, Is.EqualTo("department"));
}
[Test]
public void Having_SetsHavingClause()
{
// Arrange
var query = new QueryBreakdown();
// Act
var result = query.Having("COUNT(*) > 5");
// Assert
Assert.That(result, Is.SameAs(query));
Assert.That(query.HavingClause.Clause, Is.EqualTo("COUNT(*) > 5"));
}
[Test]
public void OrderBy_SetsOrderByClause()
{
// Arrange
var query = new QueryBreakdown();
// Act
var result = query.OrderBy("name ASC");
// Assert
Assert.That(result, Is.SameAs(query));
Assert.That(query.OrderByClause.Clause, Is.EqualTo("name ASC"));
}
[Test]
public void AddWhere_AppendsToWhereClause()
{
// Arrange
var query = new QueryBreakdown().Where("active = 1");
// Act
var result = query.AddWhere("AND age > 18");
// Assert
Assert.That(result, Is.SameAs(query));
Assert.That(query.WhereClause.Clause, Does.Contain("active = 1"));
Assert.That(query.WhereClause.Clause, Does.Contain("AND age > 18"));
}
#endregion
#region Method Chaining Tests
[Test]
public void FluentAPI_CanChainSelectFromWhere()
{
// Act
var query = new QueryBreakdown()
.Select("id, name")
.From("users")
.Where("active = 1");
// Assert
Assert.That(query.SelectClause.Clause, Is.EqualTo("id, name"));
Assert.That(query.FromClause.Clause, Is.EqualTo("users"));
Assert.That(query.WhereClause.Clause, Is.EqualTo("active = 1"));
}
[Test]
public void FluentAPI_CanChainAllClauses()
{
// Act
var query = new QueryBreakdown()
.Select("department, COUNT(*) as employee_count")
.From("employees")
.Where("salary > 50000")
.GroupBy("department")
.Having("COUNT(*) > 5")
.OrderBy("employee_count DESC");
// Assert
Assert.That(query.SelectClause.Clause, Is.EqualTo("department, COUNT(*) as employee_count"));
Assert.That(query.FromClause.Clause, Is.EqualTo("employees"));
Assert.That(query.WhereClause.Clause, Is.EqualTo("salary > 50000"));
Assert.That(query.GroupByClause.Clause, Is.EqualTo("department"));
Assert.That(query.HavingClause.Clause, Is.EqualTo("COUNT(*) > 5"));
Assert.That(query.OrderByClause.Clause, Is.EqualTo("employee_count DESC"));
}
[Test]
public void FluentAPI_GeneratesCorrectSQL()
{
// Arrange & Act
var query = new QueryBreakdown()
.Select("id, name, email")
.From("users")
.Where("active = 1")
.OrderBy("name ASC");
var sql = query.GetSql();
// Assert
Assert.That(sql, Does.Contain("SELECT"));
Assert.That(sql, Does.Contain("id, name, email"));
Assert.That(sql, Does.Contain("FROM"));
Assert.That(sql, Does.Contain("users"));
Assert.That(sql, Does.Contain("WHERE"));
Assert.That(sql, Does.Contain("active = 1"));
Assert.That(sql, Does.Contain("ORDER BY"));
Assert.That(sql, Does.Contain("name ASC"));
}
#endregion
#region WithCte Tests
[Test]
public void WithCte_WithConfigureAction_AddsCtesToQuery()
{
// Arrange & Act
var query = new QueryBreakdown()
.WithCte("active_users", cte => cte
.Select("id, name")
.From("users")
.Where("active = 1"))
.Select("*")
.From("active_users");
// Assert
Assert.That(query.IsUsingWithClause, Is.True);
Assert.That(query.WithClauses.Count, Is.EqualTo(1));
Assert.That(query.WithClauses[0].TableName, Is.EqualTo("active_users"));
Assert.That(query.SelectClause.Clause, Is.EqualTo("*"));
Assert.That(query.FromClause.Clause, Is.EqualTo("active_users"));
}
[Test]
public void WithCte_GeneratesCorrectSQL()
{
// Arrange & Act
var query = new QueryBreakdown()
.WithCte("active_users", cte => cte
.Select("id, name")
.From("users")
.Where("active = 1"))
.Select("*")
.From("active_users");
var sql = query.GetSql();
// Assert
Assert.That(sql, Does.Contain("WITH"));
Assert.That(sql, Does.Contain("active_users AS"));
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"));
Assert.That(sql, Does.Contain("WHERE"));
Assert.That(sql, Does.Contain("active = 1"));
}
[Test]
public void WithCte_SupportsMultipleCTEs()
{
// Arrange & Act
var query = new QueryBreakdown()
.WithCte("active_users", cte => cte
.Select("id, name")
.From("users")
.Where("active = 1"))
.WithCte("recent_orders", cte => cte
.Select("order_id, user_id, total")
.From("orders")
.Where("created_date > '2024-01-01'"))
.Select("u.name, COUNT(o.order_id) as order_count")
.From("active_users u LEFT JOIN recent_orders o ON u.id = o.user_id")
.GroupBy("u.id, u.name");
// Assert
Assert.That(query.IsUsingWithClause, Is.True);
Assert.That(query.WithClauses.Count, Is.EqualTo(2));
Assert.That(query.WithClauses[0].TableName, Is.EqualTo("active_users"));
Assert.That(query.WithClauses[1].TableName, Is.EqualTo("recent_orders"));
}
[Test]
public void WithCte_WithColumnList_AddsCtesWithColumns()
{
// Arrange & Act
var query = new QueryBreakdown()
.WithCte("active_users", new[] { "id", "name", "email" }, cte => cte
.Select("user_id, user_name, user_email")
.From("users")
.Where("status = 'active'"))
.Select("*")
.From("active_users");
var sql = query.GetSql();
// Assert
Assert.That(query.IsUsingWithClause, Is.True);
Assert.That(query.WithClauses.Count, Is.EqualTo(1));
Assert.That(sql, Does.Contain("active_users (id, name, email)"));
}
[Test]
public void WithCte_WithIQueryBreakdown_AddsCte()
{
// Arrange
var cteQuery = new QueryBreakdown()
.Select("id, name")
.From("users")
.Where("active = 1");
// Act
var query = new QueryBreakdown()
.WithCte("active_users", cteQuery)
.Select("*")
.From("active_users");
// Assert
Assert.That(query.IsUsingWithClause, Is.True);
Assert.That(query.WithClauses.Count, Is.EqualTo(1));
Assert.That(query.WithClauses[0].TableName, Is.EqualTo("active_users"));
}
#endregion
#region Complex Query Tests
[Test]
public void FluentAPI_ComplexQueryWithGrouping()
{
// Arrange & Act
var query = new QueryBreakdown()
.Select("department, AVG(salary) as avg_salary, COUNT(*) as emp_count")
.From("employees")
.Where("hire_date >= '2020-01-01'")
.GroupBy("department")
.Having("COUNT(*) >= 10")
.OrderBy("avg_salary DESC");
var sql = query.GetSql();
// Assert
Assert.That(sql, Does.Contain("SELECT"));
Assert.That(sql, Does.Contain("department, AVG(salary) as avg_salary, COUNT(*) as emp_count"));
Assert.That(sql, Does.Contain("GROUP BY"));
Assert.That(sql, Does.Contain("HAVING"));
Assert.That(sql, Does.Contain("COUNT(*) >= 10"));
}
[Test]
public void FluentAPI_RealWorldExample_SalesSummaryReport()
{
// Arrange & Act - Build a sales summary report with CTE
var query = new QueryBreakdown()
.WithCte("monthly_sales", cte => cte
.Select("YEAR(order_date) as year, MONTH(order_date) as month, SUM(total_amount) as total_sales")
.From("orders")
.Where("status = 'completed'")
.GroupBy("YEAR(order_date), MONTH(order_date)"))
.Select("year, month, total_sales, LAG(total_sales) OVER (ORDER BY year, month) as prev_month_sales")
.From("monthly_sales")
.OrderBy("year DESC, month DESC");
var sql = query.GetSql();
// Assert
Assert.That(sql, Does.Contain("WITH"));
Assert.That(sql, Does.Contain("monthly_sales AS"));
Assert.That(sql, Does.Contain("LAG(total_sales)"));
Assert.That(query.IsUsingWithClause, Is.True);
}
[Test]
public void FluentAPI_RealWorldExample_ActiveUserHierarchy()
{
// Arrange & Act - Build a hierarchical user/order report
var query = new QueryBreakdown()
.WithCte("active_users", cte => cte
.Select("id, name, email")
.From("users")
.Where("active = 1 AND last_login > DATEADD(month, -3, GETDATE())"))
.WithCte("user_orders", cte => cte
.Select("u.id as user_id, u.name, COUNT(o.id) as order_count, SUM(o.total) as total_spent")
.From("active_users u LEFT JOIN orders o ON u.id = o.user_id")
.GroupBy("u.id, u.name"))
.Select("name, order_count, total_spent, CASE WHEN total_spent > 1000 THEN 'VIP' ELSE 'Regular' END as customer_tier")
.From("user_orders")
.Where("order_count > 0")
.OrderBy("total_spent DESC");
var sql = query.GetSql();
// Assert
Assert.That(query.WithClauses.Count, Is.EqualTo(2));
Assert.That(sql, Does.Contain("active_users AS"));
Assert.That(sql, Does.Contain("user_orders AS"));
Assert.That(sql, Does.Contain("customer_tier"));
}
#endregion
#region Validation Tests
[Test]
public void Select_WithNullQuery_ThrowsArgumentNullException()
{
// Arrange
QueryBreakdown? query = null;
// Act & Assert
Assert.Throws<ArgumentNullException>(() => query!.Select("*"));
}
[Test]
public void From_WithNullQuery_ThrowsArgumentNullException()
{
// Arrange
QueryBreakdown? query = null;
// Act & Assert
Assert.Throws<ArgumentNullException>(() => query!.From("users"));
}
[Test]
public void Where_WithNullQuery_ThrowsArgumentNullException()
{
// Arrange
QueryBreakdown? query = null;
// Act & Assert
Assert.Throws<ArgumentNullException>(() => query!.Where("active = 1"));
}
[Test]
public void WithCte_WithNullQuery_ThrowsArgumentNullException()
{
// Arrange
QueryBreakdown? query = null;
// Act & Assert
Assert.Throws<ArgumentNullException>(() =>
query!.WithCte("test", cte => cte.Select("*").From("users")));
}
[Test]
public void WithCte_WithEmptyTableName_ThrowsArgumentException()
{
// Arrange
var query = new QueryBreakdown();
// Act & Assert
Assert.Throws<ArgumentException>(() =>
query.WithCte("", cte => cte.Select("*").From("users")));
}
[Test]
public void WithCte_WithNullConfigureAction_ThrowsArgumentNullException()
{
// Arrange
var query = new QueryBreakdown();
// Act & Assert
Assert.Throws<ArgumentNullException>(() =>
query.WithCte("test", (Action<QueryBreakdown>)null!));
}
[Test]
public void WithCte_WithColumnList_WithEmptyColumns_ThrowsArgumentException()
{
// Arrange
var query = new QueryBreakdown();
// Act & Assert
Assert.Throws<ArgumentException>(() =>
query.WithCte("test", Array.Empty<string>(), cte => cte.Select("*").From("users")));
}
[Test]
public void WithCte_WithIQueryBreakdown_WithNullCteQuery_ThrowsArgumentNullException()
{
// Arrange
var query = new QueryBreakdown();
// Act & Assert
Assert.Throws<ArgumentNullException>(() =>
query.WithCte("test", (QueryBreakdown)null!));
}
#endregion
#region Edge Case Tests
[Test]
public void FluentAPI_CanOverwriteClauses()
{
// Arrange
var query = new QueryBreakdown()
.Select("*")
.From("users")
.Where("active = 1");
// Act - Overwrite clauses
query.Select("id, name").Where("active = 0");
// Assert
Assert.That(query.SelectClause.Clause, Is.EqualTo("id, name"));
Assert.That(query.WhereClause.Clause, Is.EqualTo("active = 0"));
}
[Test]
public void FluentAPI_HandlesEmptyStrings()
{
// Arrange & Act
var query = new QueryBreakdown()
.Select("")
.From("")
.Where("");
// Assert
Assert.That(query.SelectClause.Clause, Is.EqualTo(""));
Assert.That(query.FromClause.Clause, Is.EqualTo(""));
Assert.That(query.WhereClause.Clause, Is.EqualTo(""));
}
[Test]
public void FluentAPI_PreservesWhitespace()
{
// Arrange & Act
var query = new QueryBreakdown()
.Select(" id, name ")
.From(" users ");
// Assert
Assert.That(query.SelectClause.Clause, Is.EqualTo(" id, name "));
Assert.That(query.FromClause.Clause, Is.EqualTo(" users "));
}
#endregion
}
@@ -0,0 +1,64 @@
using System.Text;
using System.Text.Json;
namespace Strata.SqlTools.SqlBreakdown.Tests;
[TestFixture]
public class JsonTokenReaderTests
{
[Test]
public void JsonTokenReader_ReadsJsonTokensCorrectly()
{
// Arrange
var jsonString = """
{
"foo": "bar",
"myList": [1,2,3,4],
"another": 12
}
""";
var jsonBytes = Encoding.UTF8.GetBytes(jsonString);
var reader = new Utf8JsonReader(jsonBytes);
var tokens = new List<(JsonTokenType Type, string Value)>();
// Act
while (reader.Read())
{
var tokenValue = Encoding.UTF8.GetString(reader.ValueSpan);
tokens.Add((reader.TokenType, tokenValue));
}
// Assert - Validate JSON shape and structure
Assert.That(tokens, Is.Not.Empty);
// Validate JSON starts with StartObject and ends with EndObject
Assert.That(tokens.Count, Is.GreaterThan(2));
Assert.That(tokens[0].Type, Is.EqualTo(JsonTokenType.StartObject));
Assert.That(tokens[^1].Type, Is.EqualTo(JsonTokenType.EndObject));
Assert.That(tokens.Count(t => t.Type == JsonTokenType.PropertyName && t.Value == "foo"), Is.EqualTo(1));
Assert.That(tokens.Count(t => t.Type == JsonTokenType.String && t.Value == "bar"), Is.EqualTo(1));
Assert.That(tokens.Count(t => t.Type == JsonTokenType.PropertyName && t.Value == "myList"), Is.EqualTo(1));
Assert.That(tokens.Count(t => t.Type == JsonTokenType.StartArray), Is.EqualTo(1));
Assert.That(tokens.Count(t => t.Type == JsonTokenType.PropertyName && t.Value == "another"), Is.EqualTo(1));
Assert.That(tokens.Count(t => t.Type == JsonTokenType.Number && t.Value == "12"), Is.EqualTo(1));
// Deep validation of JSON content structure
var tokenIndex = 4;
// Token 4: [
Assert.That(tokens[tokenIndex++].Type, Is.EqualTo(JsonTokenType.StartArray));
// Token 5-8: Array elements [1,2,3,4]
Assert.That(tokens[tokenIndex].Type, Is.EqualTo(JsonTokenType.Number));
Assert.That(tokens[tokenIndex++].Value, Is.EqualTo("1"));
Assert.That(tokens[tokenIndex].Type, Is.EqualTo(JsonTokenType.Number));
Assert.That(tokens[tokenIndex++].Value, Is.EqualTo("2"));
Assert.That(tokens[tokenIndex].Type, Is.EqualTo(JsonTokenType.Number));
Assert.That(tokens[tokenIndex++].Value, Is.EqualTo("3"));
Assert.That(tokens[tokenIndex].Type, Is.EqualTo(JsonTokenType.Number));
Assert.That(tokens[tokenIndex++].Value, Is.EqualTo("4"));
// Token 9: ]
Assert.That(tokens[tokenIndex].Type, Is.EqualTo(JsonTokenType.EndArray));
}
}
@@ -0,0 +1,31 @@
using Strata.SqlTools.SqlBreakdown.Classes;
using Strata.SqlTools.SqlBreakdown.Expressions;
namespace Strata.SqlTools.SqlBreakdown.Tests.RegisteredTables;
/// <summary>
/// Helper utilities for retrieving registered table column expressions.
/// Provides mock implementations for testing purposes.
/// </summary>
public static class RegisteredTableColumns
{
/// <summary>
/// Gets a registered table column expression for the specified data column ID.
/// Currently returns mock column definitions for testing purposes.
/// </summary>
/// <param name="dataColumnId">The data column identifier (1=DEPARTMENT_ID, 2=NAME, 3=REVENUE, 4=DISCHARGE_DATE).</param>
/// <returns>A registered table column expression for the specified column ID.</returns>
/// <remarks>This is a mock implementation and should be replaced with actual column lookup logic.</remarks>
public static RegisteredTableColumnExpression GetColumn(int dataColumnId)
{
var tableSource = new RegisteredTableSource(1001, "FW", "DEPARTMENT", "DEPT");
return dataColumnId switch
{
1 => new RegisteredTableColumnExpression(dataColumnId, "DEPARTMENT_ID", tableSource),
2 => new RegisteredTableColumnExpression(dataColumnId, "NAME", tableSource),
3 => new RegisteredTableColumnExpression(dataColumnId, "REVENUE", tableSource),
4 => new RegisteredTableColumnExpression(dataColumnId, "DISCHARGE_DATE", tableSource),
_ => new RegisteredTableColumnExpression(dataColumnId, "FOOBAR", tableSource)
};
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,469 @@
using Strata.SqlTools.SqlBreakdown.Classes;
using Strata.SqlTools.SqlBreakdown.Interfaces;
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
/// <summary>
/// Unit tests for the SqlBreakdownCollection core functionality.
/// </summary>
[TestFixture]
public class SqlBreakdownCollectionCoreTests : SqlBreakdownCollectionTestBase
{
[Test]
public void Constructor_WithNoArguments_CreatesEmptyCollection()
{
// Arrange & Act
var collection = new SqlBreakdownCollection();
// Assert
Assert.That(collection.Count, Is.EqualTo(0));
Assert.That(collection.IsEmpty, Is.True);
Assert.That(collection.Breakdowns, Is.Empty);
Assert.That(collection.RawStatements, Is.Empty);
}
[Test]
public void Constructor_WithBreakdownList_InitializesCollection()
{
// Arrange
var mockBreakdowns = new List<ISqlBreakdown>
{
new MockSqlBreakdown("SELECT * FROM Table1"),
new MockSqlBreakdown("SELECT * FROM Table2")
};
// Act
var collection = new SqlBreakdownCollection(mockBreakdowns);
// Assert
Assert.That(collection.Count, Is.EqualTo(2));
Assert.That(collection.IsEmpty, Is.False);
}
[Test]
public void Add_WithValidBreakdown_AddsToCollection()
{
// Arrange
var collection = new SqlBreakdownCollection();
var breakdown = new MockSqlBreakdown("SELECT * FROM Table1");
// Act
collection.Add(breakdown);
// Assert
Assert.That(collection.Count, Is.EqualTo(1));
Assert.That(collection.GetAt(0), Is.EqualTo(breakdown));
}
[Test]
public void Add_WithNullBreakdown_ThrowsArgumentNullException()
{
// Arrange
var collection = new SqlBreakdownCollection();
// Act & Assert
Assert.Throws<ArgumentNullException>(() => collection.Add(null!));
}
[Test]
public void AddRange_WithMultipleBreakdowns_AddsAllItems()
{
// Arrange
var collection = new SqlBreakdownCollection();
var breakdowns = new List<ISqlBreakdown>
{
new MockSqlBreakdown("SELECT * FROM Table1"),
new MockSqlBreakdown("SELECT * FROM Table2"),
new MockSqlBreakdown("SELECT * FROM Table3")
};
// Act
collection.AddRange(breakdowns);
// Assert
Assert.That(collection.Count, Is.EqualTo(3));
}
[Test]
public void AddRange_WithNullCollection_ThrowsArgumentNullException()
{
// Arrange
var collection = new SqlBreakdownCollection();
// Act & Assert
Assert.Throws<ArgumentNullException>(() => collection.AddRange(null!));
}
[Test]
public void Remove_WithExistingBreakdown_RemovesItem()
{
// Arrange
var breakdown = new MockSqlBreakdown("SELECT * FROM Table1");
var collection = new SqlBreakdownCollection(new[] { breakdown });
// Act
var result = collection.Remove(breakdown);
// Assert
Assert.That(result, Is.True);
Assert.That(collection.Count, Is.EqualTo(0));
}
[Test]
public void Remove_WithNonExistingBreakdown_ReturnsFalse()
{
// Arrange
var collection = new SqlBreakdownCollection(new[] { new MockSqlBreakdown("SELECT * FROM Table1") });
var otherBreakdown = new MockSqlBreakdown("SELECT * FROM Table2");
// Act
var result = collection.Remove(otherBreakdown);
// Assert
Assert.That(result, Is.False);
Assert.That(collection.Count, Is.EqualTo(1));
}
[Test]
public void Clear_WithMultipleItems_RemovesAll()
{
// Arrange
var collection = new SqlBreakdownCollection(new[]
{
new MockSqlBreakdown("SELECT * FROM Table1"),
new MockSqlBreakdown("SELECT * FROM Table2")
});
// Act
collection.Clear();
// Assert
Assert.That(collection.Count, Is.EqualTo(0));
Assert.That(collection.IsEmpty, Is.True);
}
[Test]
public void ParseBatch_WithGoSeparators_ParsesStatements()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
SELECT * FROM Table1
GO
SELECT * FROM Table2
GO
SELECT * FROM Table3
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.RawStatements.Count, Is.EqualTo(3));
Assert.That(collection.RawStatements[0], Does.Contain("Table1"));
Assert.That(collection.RawStatements[1], Does.Contain("Table2"));
Assert.That(collection.RawStatements[2], Does.Contain("Table3"));
}
[Test]
public void ParseBatch_WithMixedCaseGO_ParsesCorrectly()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
SELECT * FROM Table1
go
SELECT * FROM Table2
GO
SELECT * FROM Table3
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.RawStatements.Count, Is.EqualTo(3));
}
[Test]
public void ParseBatch_WithGoAndWhitespace_ParsesCorrectly()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
SELECT * FROM Table1
GO
SELECT * FROM Table2
GO
SELECT * FROM Table3
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.RawStatements.Count, Is.EqualTo(3));
}
[Test]
public void ParseBatch_WithEmptyStatements_FiltersOutEmptyValues()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
SELECT * FROM Table1
GO
GO
SELECT * FROM Table2
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.RawStatements.Count, Is.EqualTo(2));
Assert.That(collection.RawStatements[0], Does.Contain("Table1"));
Assert.That(collection.RawStatements[1], Does.Contain("Table2"));
}
[Test]
public void ParseBatch_WithNullInput_ThrowsArgumentNullException()
{
// Arrange
var collection = new SqlBreakdownCollection();
// Act & Assert
Assert.Throws<ArgumentNullException>(() => collection.ParseBatch(null!));
}
[Test]
public void GetCombinedSql_WithMultipleBreakdowns_CombinesSqlWithSeparator()
{
// Arrange
var breakdowns = new[]
{
new MockSqlBreakdown("SELECT * FROM Table1"),
new MockSqlBreakdown("SELECT * FROM Table2")
};
var collection = new SqlBreakdownCollection(breakdowns);
// Act
var result = collection.GetCombinedSql();
// Assert
Assert.That(result, Does.Contain("Table1"));
Assert.That(result, Does.Contain("Table2"));
Assert.That(result, Does.Contain("GO"));
}
[Test]
public void GetCombinedSql_WithEmptyCollection_ReturnsEmpty()
{
// Arrange
var collection = new SqlBreakdownCollection();
// Act
var result = collection.GetCombinedSql();
// Assert
Assert.That(result, Is.Empty);
}
[Test]
public void GetCombinedSql_WithCustomSeparator_UsesCustomSeparator()
{
// Arrange
var breakdowns = new[]
{
new MockSqlBreakdown("SELECT * FROM Table1"),
new MockSqlBreakdown("SELECT * FROM Table2")
};
var collection = new SqlBreakdownCollection(breakdowns);
// Act
var result = collection.GetCombinedSql(true, ";");
// Assert
Assert.That(result, Does.Contain(";"));
Assert.That(result, Does.Not.Contain("GO"));
}
[Test]
public void GetBatchSql_WithRawStatements_CombinesWithGO()
{
// Arrange
var collection = new SqlBreakdownCollection();
collection.ParseBatch(@"
SELECT * FROM Table1
GO
SELECT * FROM Table2
");
// Act
var result = collection.GetBatchSql();
// Assert
Assert.That(result, Does.Contain("Table1"));
Assert.That(result, Does.Contain("GO"));
Assert.That(result, Does.Contain("Table2"));
}
[Test]
public void GetBatchSql_WithEmptyCollection_ReturnsEmpty()
{
// Arrange
var collection = new SqlBreakdownCollection();
// Act
var result = collection.GetBatchSql();
// Assert
Assert.That(result, Is.Empty);
}
[Test]
public void Where_WithValidPredicate_FiltersBreakdowns()
{
// Arrange
var breakdowns = new[]
{
new MockSqlBreakdown("SELECT * FROM Table1"),
new MockSqlBreakdown("SELECT * FROM Table2"),
new MockSqlBreakdown("SELECT * FROM Table3")
};
var collection = new SqlBreakdownCollection(breakdowns);
// Act
var filtered = collection.Where(b => b.ToString()!.Contains("Table2")).ToList();
// Assert
Assert.That(filtered.Count, Is.EqualTo(1));
Assert.That(filtered[0].ToString()!, Does.Contain("Table2"));
}
[Test]
public void Select_WithValidSelector_ProjectsBreakdowns()
{
// Arrange
var breakdowns = new[]
{
new MockSqlBreakdown("SELECT * FROM Table1"),
new MockSqlBreakdown("SELECT * FROM Table2")
};
var collection = new SqlBreakdownCollection(breakdowns);
// Act
var projected = collection.Select(b => b.ToString()!.Length).ToList();
// Assert
Assert.That(projected.Count, Is.EqualTo(2));
Assert.That(projected[0], Is.GreaterThan(0));
}
[Test]
public void GetAt_WithValidIndex_ReturnsBreakdown()
{
// Arrange
var breakdown1 = new MockSqlBreakdown("SELECT * FROM Table1");
var breakdown2 = new MockSqlBreakdown("SELECT * FROM Table2");
var collection = new SqlBreakdownCollection(new[] { breakdown1, breakdown2 });
// Act
var result = collection.GetAt(1);
// Assert
Assert.That(result, Is.EqualTo(breakdown2));
}
[Test]
public void GetAt_WithInvalidIndex_ThrowsArgumentOutOfRangeException()
{
// Arrange
var collection = new SqlBreakdownCollection(new[] { new MockSqlBreakdown("SELECT * FROM Table1") });
// Act & Assert
Assert.Throws<ArgumentOutOfRangeException>(() => collection.GetAt(5));
}
[Test]
public void FirstOrDefault_WithMatchingPredicate_ReturnsBreakdown()
{
// Arrange
var breakdowns = new[]
{
new MockSqlBreakdown("SELECT * FROM Table1"),
new MockSqlBreakdown("SELECT * FROM Table2")
};
var collection = new SqlBreakdownCollection(breakdowns);
// Act
var result = collection.FirstOrDefault(b => b.ToString()!.Contains("Table2"));
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result!.ToString(), Does.Contain("Table2"));
}
[Test]
public void FirstOrDefault_WithNoMatch_ReturnsNull()
{
// Arrange
var collection = new SqlBreakdownCollection(new[] { new MockSqlBreakdown("SELECT * FROM Table1") });
// Act
var result = collection.FirstOrDefault(b => b.ToString()!.Contains("NonExistent"));
// Assert
Assert.That(result, Is.Null);
}
[Test]
public void GetRawStatementAt_WithValidIndex_ReturnsStatement()
{
// Arrange
var collection = new SqlBreakdownCollection();
collection.ParseBatch(@"
SELECT * FROM Table1
GO
SELECT * FROM Table2
");
// Act
var result = collection.GetRawStatementAt(1);
// Assert
Assert.That(result, Does.Contain("Table2"));
}
[Test]
public void GetRawStatementAt_WithInvalidIndex_ThrowsArgumentOutOfRangeException()
{
// Arrange
var collection = new SqlBreakdownCollection();
collection.ParseBatch("SELECT * FROM Table1");
// Act & Assert
Assert.Throws<ArgumentOutOfRangeException>(() => collection.GetRawStatementAt(5));
}
[Test]
public void ToString_ReturnsFormattedSql()
{
// Arrange
var breakdowns = new[]
{
new MockSqlBreakdown("SELECT * FROM Table1"),
new MockSqlBreakdown("SELECT * FROM Table2")
};
var collection = new SqlBreakdownCollection(breakdowns);
// Act
var result = collection.ToString();
// Assert
Assert.That(result, Does.Contain("Table1"));
Assert.That(result, Does.Contain("Table2"));
Assert.That(result, Does.Contain("GO"));
}
}
@@ -0,0 +1,80 @@
using Strata.SqlTools.SqlBreakdown.Classes;
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
/// <summary>
/// Unit tests for SqlBreakdownCollection with CREATE statements.
/// </summary>
[TestFixture]
public class SqlBreakdownCollectionCreateTests : SqlBreakdownCollectionTestBase
{
[Test]
public void ParseBatch_WithCreateTableStatements_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
CREATE TABLE Users (
Id INT PRIMARY KEY,
Name NVARCHAR(100),
Email NVARCHAR(100)
)
GO
CREATE TABLE Orders (
OrderId INT PRIMARY KEY,
UserId INT FOREIGN KEY REFERENCES Users(Id),
OrderDate DATETIME
)
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.EqualTo(2));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("CREATE TABLE Users"));
Assert.That(collection.GetRawStatementAt(1), Does.Contain("CREATE TABLE Orders"));
}
[Test]
public void ParseBatch_WithCreateIndexStatements_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
CREATE INDEX idx_Users_Email ON Users(Email)
GO
CREATE UNIQUE INDEX idx_Orders_OrderDate ON Orders(OrderDate)
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.EqualTo(2));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("CREATE INDEX"));
Assert.That(collection.GetRawStatementAt(1), Does.Contain("UNIQUE"));
}
[Test]
public void ParseBatch_WithCreateProcedureStatement_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
CREATE PROCEDURE sp_GetUserById
@UserId INT
AS
BEGIN
SELECT * FROM Users WHERE Id = @UserId
END
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.GreaterThanOrEqualTo(1));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("CREATE PROCEDURE"));
}
}
@@ -0,0 +1,75 @@
using Strata.SqlTools.SqlBreakdown.Classes;
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
/// <summary>
/// Unit tests for SqlBreakdownCollection with DELETE statements.
/// </summary>
[TestFixture]
public class SqlBreakdownCollectionDeleteTests : SqlBreakdownCollectionTestBase
{
[Test]
public void ParseBatch_WithDeleteStatements_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
DELETE FROM Orders WHERE OrderDate < '2019-01-01'
GO
DELETE FROM UserProfiles WHERE UserId NOT IN (SELECT Id FROM Users)
GO
DELETE FROM AuditLog WHERE LogDate < DATEADD(year, -2, GETDATE())
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.EqualTo(3));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("DELETE FROM Orders"));
Assert.That(collection.GetRawStatementAt(1), Does.Contain("NOT IN"));
Assert.That(collection.GetRawStatementAt(2), Does.Contain("DATEADD"));
}
[Test]
public void ParseBatch_WithDeleteFromSelectStatements_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
DELETE o FROM Orders o
WHERE NOT EXISTS (SELECT 1 FROM OrderItems oi WHERE oi.OrderId = o.OrderId)
GO
DELETE FROM Users
WHERE Id IN (SELECT UserId FROM Orders WHERE OrderStatus = 'Cancelled')
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.EqualTo(2));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("EXISTS"));
}
[Test]
public void ParseBatch_WithDeleteWithJoinStatements_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
DELETE Orders
FROM Orders o
INNER JOIN OrderStatuses os ON o.StatusId = os.Id
WHERE os.StatusName = 'Archived'
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.GreaterThanOrEqualTo(1));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("DELETE Orders"));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("INNER JOIN"));
}
}
@@ -0,0 +1,55 @@
using Strata.SqlTools.SqlBreakdown.Classes;
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
/// <summary>
/// Unit tests for SqlBreakdownCollection with DROP statements.
/// </summary>
[TestFixture]
public class SqlBreakdownCollectionDropTests : SqlBreakdownCollectionTestBase
{
[Test]
public void ParseBatch_WithDropTableStatements_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
DROP TABLE IF EXISTS OrderItems
GO
DROP TABLE IF EXISTS Orders
GO
DROP TABLE IF EXISTS Users
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.EqualTo(3));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("DROP TABLE"));
Assert.That(collection.GetRawStatementAt(1), Does.Contain("DROP TABLE"));
}
[Test]
public void ParseBatch_WithDropIndexAndProcedureStatements_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
DROP INDEX IF EXISTS idx_Users_Email ON Users
GO
DROP PROCEDURE IF EXISTS sp_GetUserById
GO
DROP VIEW IF EXISTS vw_UserOrders
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.EqualTo(3));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("DROP INDEX"));
Assert.That(collection.GetRawStatementAt(1), Does.Contain("PROCEDURE"));
Assert.That(collection.GetRawStatementAt(2), Does.Contain("VIEW"));
}
}
@@ -0,0 +1,76 @@
using Strata.SqlTools.SqlBreakdown.Classes;
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
/// <summary>
/// Unit tests for SqlBreakdownCollection with INSERT statements.
/// </summary>
[TestFixture]
public class SqlBreakdownCollectionInsertTests : SqlBreakdownCollectionTestBase
{
[Test]
public void ParseBatch_WithInsertStatements_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
INSERT INTO Users (Id, Name, Email) VALUES (1, 'John Doe', 'john@example.com')
GO
INSERT INTO Users (Id, Name, Email) VALUES (2, 'Jane Smith', 'jane@example.com')
GO
INSERT INTO Users (Id, Name, Email) VALUES (3, 'Bob Johnson', 'bob@example.com')
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.EqualTo(3));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("INSERT INTO Users"));
Assert.That(collection.GetRawStatementAt(1), Does.Contain("Jane Smith"));
Assert.That(collection.GetRawStatementAt(2), Does.Contain("Bob Johnson"));
}
[Test]
public void ParseBatch_WithInsertSelectStatements_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
INSERT INTO UsersArchive
SELECT Id, Name, Email FROM Users WHERE CreatedDate < '2020-01-01'
GO
INSERT INTO OrdersBackup
SELECT OrderId, UserId, OrderDate FROM Orders WHERE OrderDate < DATEADD(year, -1, GETDATE())
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.EqualTo(2));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("INSERT INTO UsersArchive"));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("SELECT"));
}
[Test]
public void ParseBatch_WithBulkInsertStatements_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
BULK INSERT Users FROM 'C:\data\users.csv'
WITH (FIELDTERMINATOR=',', ROWTERMINATOR='\n')
GO
BULK INSERT Orders FROM 'C:\data\orders.csv'
WITH (FIELDTERMINATOR=',', ROWTERMINATOR='\n')
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.EqualTo(2));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("BULK INSERT"));
}
}
@@ -0,0 +1,91 @@
using Strata.SqlTools.SqlBreakdown.Classes;
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
/// <summary>
/// Unit tests for SqlBreakdownCollection with mixed CRUD operations.
/// </summary>
[TestFixture]
public class SqlBreakdownCollectionMixedCrudTests : SqlBreakdownCollectionTestBase
{
[Test]
public void ParseBatch_WithMixedCrudOperations_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
CREATE TABLE Users (
Id INT PRIMARY KEY,
Name NVARCHAR(100),
Email NVARCHAR(100)
)
GO
INSERT INTO Users (Id, Name, Email) VALUES (1, 'John Doe', 'john@example.com')
GO
INSERT INTO Users (Id, Name, Email) VALUES (2, 'Jane Smith', 'jane@example.com')
GO
SELECT * FROM Users WHERE Id = 1
GO
UPDATE Users SET Name = 'John Updated' WHERE Id = 1
GO
DELETE FROM Users WHERE Id = 2
GO
DROP TABLE Users
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.EqualTo(7));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("CREATE TABLE"));
Assert.That(collection.GetRawStatementAt(1), Does.Contain("INSERT"));
Assert.That(collection.GetRawStatementAt(3), Does.Contain("SELECT"));
Assert.That(collection.GetRawStatementAt(4), Does.Contain("UPDATE"));
Assert.That(collection.GetRawStatementAt(5), Does.Contain("DELETE"));
Assert.That(collection.GetRawStatementAt(6), Does.Contain("DROP"));
}
[Test]
public void ParseBatch_WithComplexMultipleStatements_ParsesAllStatements()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
CREATE TABLE Products (
Id INT PRIMARY KEY,
Name NVARCHAR(100),
Price DECIMAL(10, 2),
Quantity INT
)
GO
INSERT INTO Products (Id, Name, Price, Quantity)
SELECT Id, ProductName, BasePrice, StockQuantity
FROM StagingProducts
WHERE IsActive = 1
GO
UPDATE Products SET Price = Price * 1.1 WHERE Quantity < 10
GO
SELECT p.Name,
COUNT(oi.Id) AS OrderCount,
SUM(oi.Quantity) AS TotalSold
FROM Products p
LEFT JOIN OrderItems oi ON p.Id = oi.ProductId
GROUP BY p.Id, p.Name
HAVING SUM(oi.Quantity) > 100
GO
DELETE FROM Products WHERE Quantity = 0 AND LastUpdated < DATEADD(month, -6, GETDATE())
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.EqualTo(5));
Assert.That(collection.RawStatements.Count, Is.EqualTo(5));
foreach (var statement in collection.RawStatements)
{
Assert.That(statement, Is.Not.Empty);
}
}
}
@@ -0,0 +1,73 @@
using Strata.SqlTools.SqlBreakdown.Classes;
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
/// <summary>
/// Unit tests for SqlBreakdownCollection with parameterized statements.
/// </summary>
[TestFixture]
public class SqlBreakdownCollectionParameterTests : SqlBreakdownCollectionTestBase
{
[Test]
public void ParseBatch_WithParameterizedInsertStatements_PreservesParameters()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
INSERT INTO Users (Id, Name, Email) VALUES (@UserId, @UserName, @UserEmail)
GO
INSERT INTO Orders (OrderId, UserId, OrderDate) VALUES (@OrderId, @UserId, @OrderDate)
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.EqualTo(2));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("@UserId"));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("@UserEmail"));
}
[Test]
public void ParseBatch_WithParameterizedUpdateStatements_PreservesParameters()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
UPDATE Users SET Name = @Name, Email = @Email WHERE Id = @UserId
GO
UPDATE Orders SET Status = @Status, ModifiedDate = @ModifiedDate WHERE OrderId = @OrderId
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.EqualTo(2));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("@Name"));
Assert.That(collection.GetRawStatementAt(1), Does.Contain("@Status"));
}
[Test]
public void GetCombinedSql_WithMixedCrudOperations_CombinesCorrectly()
{
// Arrange
var collection = new SqlBreakdownCollection();
collection.ParseBatch(@"
INSERT INTO Users (Id, Name) VALUES (1, 'John')
GO
UPDATE Users SET Name = 'Jane' WHERE Id = 1
GO
DELETE FROM Users WHERE Id = 1
");
// Act
var combined = collection.GetCombinedSql();
// Assert
Assert.That(combined, Does.Contain("INSERT"));
Assert.That(combined, Does.Contain("UPDATE"));
Assert.That(combined, Does.Contain("DELETE"));
Assert.That(combined, Does.Contain("GO"));
}
}
@@ -0,0 +1,86 @@
using Strata.SqlTools.SqlBreakdown.Classes;
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
/// <summary>
/// Unit tests for SqlBreakdownCollection with SELECT statements.
/// </summary>
[TestFixture]
public class SqlBreakdownCollectionSelectTests : SqlBreakdownCollectionTestBase
{
[Test]
public void ParseBatch_WithComplexSelectStatements_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
SELECT u.Id, u.Name, COUNT(o.OrderId) AS OrderCount
FROM Users u
LEFT JOIN Orders o ON u.Id = o.UserId
WHERE u.CreatedDate > '2020-01-01'
GROUP BY u.Id, u.Name
HAVING COUNT(o.OrderId) > 0
ORDER BY OrderCount DESC
GO
SELECT * FROM Orders WHERE OrderDate BETWEEN '2020-01-01' AND '2021-12-31'
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.EqualTo(2));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("LEFT JOIN"));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("GROUP BY"));
Assert.That(collection.GetRawStatementAt(1), Does.Contain("BETWEEN"));
}
[Test]
public void ParseBatch_WithSelectWithCommonTableExpression_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
WITH UserOrders AS (
SELECT UserId, OrderId, OrderDate
FROM Orders
WHERE OrderDate > '2020-01-01'
)
SELECT u.Id, u.Name, uo.OrderId
FROM Users u
INNER JOIN UserOrders uo ON u.Id = uo.UserId
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.GreaterThanOrEqualTo(1));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("WITH UserOrders AS"));
}
[Test]
public void ParseBatch_WithSelectFromMultipleTables_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
SELECT u.Id, u.Name, o.OrderId, p.ProductName
FROM Users u
JOIN Orders o ON u.Id = o.UserId
JOIN OrderItems oi ON o.OrderId = oi.OrderId
JOIN Products p ON oi.ProductId = p.Id
WHERE u.Country = 'USA'
GO
SELECT * FROM UserProfiles WHERE UserId IN (SELECT Id FROM Users)
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.EqualTo(2));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("JOIN"));
Assert.That(collection.GetRawStatementAt(1), Does.Contain("IN"));
}
}
@@ -0,0 +1,54 @@
using Strata.SqlTools.SqlBreakdown.Classes;
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
/// <summary>
/// Unit tests for SqlBreakdownCollection with custom separators.
/// </summary>
[TestFixture]
public class SqlBreakdownCollectionSeparatorTests : SqlBreakdownCollectionTestBase
{
[Test]
public void ParseBatch_WithSemicolonSeparator_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
SELECT * FROM Users
;
SELECT * FROM Orders
;
SELECT * FROM Products
";
// Act
collection.ParseBatch(batchSql, ";");
// Assert
Assert.That(collection.Count, Is.EqualTo(3));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("Users"));
Assert.That(collection.GetRawStatementAt(1), Does.Contain("Orders"));
Assert.That(collection.GetRawStatementAt(2), Does.Contain("Products"));
}
[Test]
public void ParseBatch_WithCustomSeparator_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
INSERT INTO Users VALUES (1, 'John')
~~~
INSERT INTO Users VALUES (2, 'Jane')
~~~
INSERT INTO Users VALUES (3, 'Bob')
";
// Act
collection.ParseBatch(batchSql, "~~~");
// Assert
Assert.That(collection.Count, Is.EqualTo(3));
Assert.That(collection.RawStatements.Count, Is.EqualTo(3));
}
}
@@ -0,0 +1,66 @@
using Strata.SqlTools.SqlBreakdown.Interfaces;
using System.Text;
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
/// <summary>
/// Base class for SqlBreakdownCollection tests containing shared test utilities and mock implementations.
/// </summary>
public class SqlBreakdownCollectionTestBase
{
/// <summary>
/// Mock implementation of ISqlBreakdown for testing purposes.
/// </summary>
public class MockSqlBreakdown : ISqlBreakdown
{
private readonly string _sql;
public MockSqlBreakdown(string sql)
{
_sql = sql;
SetupClauses = new List<string>();
FinishClauses = new System.Collections.ArrayList();
}
public string? RawSql { get; set; }
public List<string> SetupClauses { get; set; }
public bool IsUsingSetupClause => SetupClauses.Count > 0;
public System.Collections.ArrayList FinishClauses { get; set; }
public bool IsUsingFinishClause => FinishClauses.Count > 0;
public string GetSql(bool includeSetupFinish = true)
{
var sb = new StringBuilder();
if (includeSetupFinish)
{
foreach (string setup in SetupClauses)
{
sb.AppendLine(setup);
}
}
sb.Append(_sql);
if (includeSetupFinish)
{
foreach (string finish in FinishClauses)
{
sb.AppendLine(finish);
}
}
return sb.ToString();
}
public override string ToString() => GetSql();
public object Clone()
{
var clone = (MockSqlBreakdown)MemberwiseClone();
clone.SetupClauses = new List<string>(SetupClauses);
clone.FinishClauses = new System.Collections.ArrayList(FinishClauses);
return clone;
}
}
}
@@ -0,0 +1,37 @@
using Strata.SqlTools.SqlBreakdown.Classes;
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
/// <summary>
/// Unit tests for SqlBreakdownCollection with transaction statements.
/// </summary>
[TestFixture]
public class SqlBreakdownCollectionTransactionTests : SqlBreakdownCollectionTestBase
{
[Test]
public void ParseBatch_WithTransactionStatements_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
BEGIN TRANSACTION UpdatePrices
UPDATE Products SET Price = Price * 1.1 WHERE Category = 'Electronics'
UPDATE ProductPriceHistory SET NewPrice = Price FROM Products WHERE Products.Id = ProductPriceHistory.ProductId
COMMIT TRANSACTION UpdatePrices
GO
BEGIN TRANSACTION DeleteOldOrders
DELETE FROM OrderItems WHERE OrderId IN (SELECT OrderId FROM Orders WHERE OrderDate < '2018-01-01')
DELETE FROM Orders WHERE OrderDate < '2018-01-01'
COMMIT TRANSACTION DeleteOldOrders
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.GreaterThanOrEqualTo(1));
var statement = collection.GetRawStatementAt(0);
Assert.That(statement, Does.Contain("BEGIN TRANSACTION"));
Assert.That(statement, Does.Contain("COMMIT"));
}
}
@@ -0,0 +1,81 @@
using Strata.SqlTools.SqlBreakdown.Classes;
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlBreakdownCollectionTests;
/// <summary>
/// Unit tests for SqlBreakdownCollection with UPDATE statements.
/// </summary>
[TestFixture]
public class SqlBreakdownCollectionUpdateTests : SqlBreakdownCollectionTestBase
{
[Test]
public void ParseBatch_WithUpdateStatements_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
UPDATE Users SET Name = 'John Updated', ModifiedDate = GETDATE() WHERE Id = 1
GO
UPDATE Orders SET Status = 'Shipped' WHERE OrderDate < DATEADD(day, -30, GETDATE())
GO
UPDATE Products SET Price = Price * 1.1 WHERE Category = 'Electronics'
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.EqualTo(3));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("UPDATE Users"));
Assert.That(collection.GetRawStatementAt(1), Does.Contain("Status"));
Assert.That(collection.GetRawStatementAt(2), Does.Contain("Price * 1.1"));
}
[Test]
public void ParseBatch_WithUpdateFromSelectStatements_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
UPDATE u
SET u.LastOrderDate = (SELECT MAX(OrderDate) FROM Orders WHERE UserId = u.Id)
FROM Users u
WHERE u.Id IN (SELECT DISTINCT UserId FROM Orders)
GO
UPDATE Products
SET Quantity = Quantity - 1
FROM OrderItems oi
WHERE Products.Id = oi.ProductId AND oi.OrderId = 100
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.EqualTo(2));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("UPDATE u"));
Assert.That(collection.GetRawStatementAt(1), Does.Contain("Quantity - 1"));
}
[Test]
public void ParseBatch_WithUpdateWithJoinStatements_ParsesSuccessfully()
{
// Arrange
var collection = new SqlBreakdownCollection();
var batchSql = @"
UPDATE Orders
SET OrderStatus = 'Processed'
FROM Orders o
JOIN OrderItems oi ON o.OrderId = oi.OrderId
WHERE oi.Quantity > 50
";
// Act
collection.ParseBatch(batchSql);
// Assert
Assert.That(collection.Count, Is.GreaterThanOrEqualTo(1));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("UPDATE Orders"));
Assert.That(collection.GetRawStatementAt(0), Does.Contain("JOIN"));
}
}
@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Strata.SqlTools.SqlBreakdown.Tests\**" />
<EmbeddedResource Remove="Strata.SqlTools.SqlBreakdown.Tests\**" />
<None Remove="Strata.SqlTools.SqlBreakdown.Tests\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NUnit" Version="3.14.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
</ItemGroup>
<ItemGroup>
<Using Include="NUnit.Framework" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Strata.SqlTools.SqlBreakdown\Strata.SqlTools.SqlBreakdown.csproj" />
<ProjectReference Include="..\..\src\Strata.SqlTools.SqlServer\Strata.SqlTools.SqlServer.csproj" />
<ProjectReference Include="..\..\src\Strata.SqlTools.Snowflake\Strata.SqlTools.Snowflake.csproj" />
</ItemGroup>
</Project>