Files
sql-utilities/tests/Strata.SqlTools.SqlServer.Tests/SqlServer/StatementExpressionParserTests.cs
T

908 lines
21 KiB
C#

using Strata.SqlTools.SqlBreakdown.Expressions;
using Strata.SqlTools.SqlBreakdown.Expressions.Functions.Aggregate;
using Strata.SqlTools.SqlBreakdown.Expressions.Literals;
using Strata.SqlTools.Statements.SqlServer;
namespace Strata.SqlTools.SqlBreakdown.Tests.SqlServer;
[TestFixture]
public class StatementExpressionParserTests
{
private StatementExpressionParser _parser = null!;
[SetUp]
public void Setup()
{
_parser = new StatementExpressionParser();
}
#region Parse Method Tests
[Test]
public void Parse_SimpleNumber_ReturnsNumberLiteralExpression()
{
// Arrange
var sql = "42";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<NumberLiteralExpression>());
}
[Test]
public void Parse_SimpleAddition_ReturnsCorrectExpression()
{
// Arrange
var sql = "10 + 20";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<Expression>());
}
[Test]
public void Parse_SimpleSubtraction_ReturnsCorrectExpression()
{
// Arrange
var sql = "50 - 30";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<Expression>());
}
[Test]
public void Parse_SimpleMultiplication_ReturnsCorrectExpression()
{
// Arrange
var sql = "5 * 3";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<Expression>());
}
[Test]
public void Parse_SimpleDivision_ReturnsCorrectExpression()
{
// Arrange
var sql = "100 / 4";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<Expression>());
}
[Test]
public void Parse_ComplexExpression_ReturnsCorrectExpression()
{
// Arrange
var sql = "10 + 20 * 3 - 5";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<Expression>());
}
[Test]
public void Parse_ParenthesizedExpression_ReturnsCorrectExpression()
{
// Arrange
var sql = "(10 + 20) * 3";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<Expression>());
}
[Test]
public void Parse_NestedParentheses_ReturnsCorrectExpression()
{
// Arrange
var sql = "((10 + 5) * 2) / 3";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<Expression>());
}
[Test]
public void Parse_SumFunction_ReturnsSumFunctionExpression()
{
// Arrange
var sql = "SUM(3_REVENUE)";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<SumFunction>());
}
[Test]
public void Parse_AvgFunction_ReturnsAverageFunctionExpression()
{
// Arrange
var sql = "AVG(3_REVENUE)";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<AverageFunction>());
}
[Test]
public void Parse_FunctionWithExpression_ReturnsCorrectExpression()
{
// Arrange
var sql = "SUM(3_REVENUE) + 100";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<Expression>());
}
[Test]
public void Parse_ColumnIdentifier_ReturnsColumnExpression()
{
// Arrange
var sql = "1_DEPARTMENT_ID";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<RegisteredTableColumnExpression>());
}
[Test]
public void Parse_ColumnWithArithmetic_ReturnsCorrectExpression()
{
// Arrange
var sql = "3_REVENUE * 2";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<Expression>());
}
[Test]
public void Parse_DecimalNumber_ReturnsNumberLiteralExpression()
{
// Arrange
var sql = "123.456";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<NumberLiteralExpression>());
}
[Test]
public void Parse_WithSingleLineComment_ParsesSuccessfully()
{
// Arrange
var sql = "10 + 20 -- This is a comment";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<Expression>());
}
[Test]
public void Parse_WithMultiLineComment_ParsesSuccessfully()
{
// Arrange
var sql = "10 /* this is a comment */ + 20";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<Expression>());
}
[Test]
public void Parse_WithExtraWhitespace_ParsesSuccessfully()
{
// Arrange
var sql = " 10 + 20 ";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<Expression>());
}
[Test]
public void Parse_WithNewlines_ParsesSuccessfully()
{
// Arrange
var sql = @"10
+
20";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<Expression>());
}
[Test]
public void Parse_NullInput_ThrowsArgumentNullException()
{
// Arrange
string sql = null!;
// Act & Assert
var ex = Assert.Throws<ArgumentNullException>(() => _parser.Parse(sql));
Assert.That(ex.ParamName, Is.EqualTo("sqlStatement"));
Assert.That(ex.Message, Does.Contain("SQL statement cannot be null or empty"));
}
[Test]
public void Parse_EmptyString_ThrowsArgumentNullException()
{
// Arrange
var sql = string.Empty;
// Act & Assert
var ex = Assert.Throws<ArgumentNullException>(() => _parser.Parse(sql));
Assert.That(ex.ParamName, Is.EqualTo("sqlStatement"));
}
[Test]
public void Parse_WhitespaceOnly_ThrowsArgumentNullException()
{
// Arrange
var sql = " ";
// Act & Assert
var ex = Assert.Throws<ArgumentNullException>(() => _parser.Parse(sql));
Assert.That(ex.ParamName, Is.EqualTo("sqlStatement"));
}
[Test]
public void Parse_UnclosedParenthesis_ThrowsFormatException()
{
// Arrange
var sql = "(10 + 20";
// Act & Assert
var ex = Assert.Throws<FormatException>(() => _parser.Parse(sql));
Assert.That(ex.Message, Does.Contain("Failed to parse SQL statement"));
}
[Test]
public void Parse_MismatchedParenthesis_ThrowsFormatException()
{
// Arrange
var sql = "10 + 20)";
// Act & Assert
Assert.Throws<FormatException>(() => _parser.Parse(sql));
}
[Test]
public void Parse_UnrecognizedFunction_ThrowsFormatException()
{
// Arrange
var sql = "UNKNOWN(100)";
// Act & Assert
var ex = Assert.Throws<FormatException>(() => _parser.Parse(sql));
Assert.That(ex.Message, Does.Contain("Failed to parse SQL statement"));
}
#endregion
#region TryParse Method Tests
[Test]
public void TryParse_ValidSimpleExpression_ReturnsTrue()
{
// Arrange
var sql = "42";
// Act
var success = _parser.TryParse(sql, out var result);
// Assert
Assert.That(success, Is.True);
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<NumberLiteralExpression>());
}
[Test]
public void TryParse_ValidComplexExpression_ReturnsTrue()
{
// Arrange
var sql = "(10 + 20) * 3 - 5";
// Act
var success = _parser.TryParse(sql, out var result);
// Assert
Assert.That(success, Is.True);
Assert.That(result, Is.Not.Null);
}
[Test]
public void TryParse_ValidFunction_ReturnsTrue()
{
// Arrange
var sql = "SUM(3_REVENUE)";
// Act
var success = _parser.TryParse(sql, out var result);
// Assert
Assert.That(success, Is.True);
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<SumFunction>());
}
[Test]
public void TryParse_NullInput_ReturnsFalse()
{
// Arrange
string sql = null!;
// Act
var success = _parser.TryParse(sql, out var result);
// Assert
Assert.That(success, Is.False);
Assert.That(result, Is.Null);
}
[Test]
public void TryParse_EmptyString_ReturnsFalse()
{
// Arrange
var sql = string.Empty;
// Act
var success = _parser.TryParse(sql, out var result);
// Assert
Assert.That(success, Is.False);
Assert.That(result, Is.Null);
}
[Test]
public void TryParse_WhitespaceOnly_ReturnsFalse()
{
// Arrange
var sql = " ";
// Act
var success = _parser.TryParse(sql, out var result);
// Assert
Assert.That(success, Is.False);
Assert.That(result, Is.Null);
}
[Test]
public void TryParse_InvalidSyntax_ReturnsFalse()
{
// Arrange
var sql = "(10 + 20";
// Act
var success = _parser.TryParse(sql, out var result);
// Assert
Assert.That(success, Is.False);
Assert.That(result, Is.Null);
}
[Test]
public void TryParse_WithErrorMessage_ValidInput_ReturnsTrue()
{
// Arrange
var sql = "100 + 50";
// Act
var success = _parser.TryParse(sql, out var result, out var errorMessage);
// Assert
Assert.That(success, Is.True);
Assert.That(result, Is.Not.Null);
Assert.That(errorMessage, Is.Null);
}
[Test]
public void TryParse_WithErrorMessage_NullInput_ReturnsFalseWithMessage()
{
// Arrange
string sql = null!;
// Act
var success = _parser.TryParse(sql, out var result, out var errorMessage);
// Assert
Assert.That(success, Is.False);
Assert.That(result, Is.Null);
Assert.That(errorMessage, Is.Not.Null);
Assert.That(errorMessage, Does.Contain("SQL statement cannot be null or empty"));
}
[Test]
public void TryParse_WithErrorMessage_InvalidSyntax_ReturnsFalseWithMessage()
{
// Arrange
var sql = "(10 + 20";
// Act
var success = _parser.TryParse(sql, out var result, out var errorMessage);
// Assert
Assert.That(success, Is.False);
Assert.That(result, Is.Null);
Assert.That(errorMessage, Is.Not.Null);
Assert.That(errorMessage, Does.Contain("Invalid syntax"));
}
[Test]
public void TryParse_WithErrorMessage_UnsupportedToken_ReturnsFalseWithMessage()
{
// Arrange
var sql = "UNKNOWN(100)";
// Act
var success = _parser.TryParse(sql, out var result, out var errorMessage);
// Assert
Assert.That(success, Is.False);
Assert.That(result, Is.Null);
Assert.That(errorMessage, Is.Not.Null);
Assert.That(errorMessage, Does.Contain("not recognized"));
}
#endregion
#region Order of Operations Tests
[Test]
public void Parse_MultiplicationBeforeAddition_CorrectPrecedence()
{
// Arrange
var sql = "2 + 3 * 4"; // Should be 2 + (3 * 4) = 14, not (2 + 3) * 4 = 20
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
// Note: We can't easily verify the calculation result without evaluating the expression tree
// But we can verify it parses without error
}
[Test]
public void Parse_DivisionBeforeSubtraction_CorrectPrecedence()
{
// Arrange
var sql = "20 - 10 / 2"; // Should be 20 - (10 / 2) = 15, not (20 - 10) / 2 = 5
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
}
[Test]
public void Parse_ParenthesesOverridePrecedence_CorrectPrecedence()
{
// Arrange
var sql = "(2 + 3) * 4"; // Should be (2 + 3) * 4 = 20
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
}
[Test]
public void Parse_MultipleOperationsLeftToRight_CorrectAssociativity()
{
// Arrange
var sql = "10 - 5 - 2"; // Should be (10 - 5) - 2 = 3, not 10 - (5 - 2) = 7
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
}
#endregion
#region Comment and Whitespace Normalization Tests
[Test]
public void Parse_SingleLineCommentAtEnd_IgnoresComment()
{
// Arrange
var sql = "10 + 20 -- This should be ignored";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
}
[Test]
public void Parse_SingleLineCommentInMiddle_IgnoresComment()
{
// Arrange
var sql = @"10 + -- comment here
20";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
}
[Test]
public void Parse_MultiLineCommentInMiddle_IgnoresComment()
{
// Arrange
var sql = "10 /* this is a\n multi-line\n comment */ + 20";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
}
[Test]
public void Parse_MultipleComments_IgnoresAllComments()
{
// Arrange
var sql = "10 /* comment 1 */ + /* comment 2 */ 20 -- comment 3";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
}
[Test]
public void Parse_CommentLikeTextInString_PreservesString()
{
// Arrange - Although this parser might not handle strings, testing the comment removal logic
var sql = "10 + 20"; // Simple case without strings as parser may not support them
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
}
[Test]
public void Parse_TabsAndSpaces_NormalizesCorrectly()
{
// Arrange
var sql = "10\t+\t 20";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
}
[Test]
public void Parse_MultipleNewlines_NormalizesCorrectly()
{
// Arrange
var sql = "10\n\n\n+\n\n20";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
}
#endregion
#region Edge Cases and Complex Scenarios
[Test]
public void Parse_VeryLongExpression_ParsesSuccessfully()
{
// Arrange
var sql = "1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
}
[Test]
public void Parse_DeeplyNestedParentheses_ParsesSuccessfully()
{
// Arrange
var sql = "((((10 + 5) * 2) - 3) / 4)";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
}
[Test]
public void Parse_MixedColumnsNumbersAndFunctions_ParsesSuccessfully()
{
// Arrange
var sql = "SUM(3_REVENUE) + 1_DEPARTMENT_ID * 100";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
}
[Test]
public void Parse_FunctionInsideParentheses_ParsesSuccessfully()
{
// Arrange
var sql = "(SUM(3_REVENUE) + 100) * 2";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
}
[Test]
public void Parse_LargeDecimalNumber_ParsesSuccessfully()
{
// Arrange
var sql = "123456789.987654321";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<NumberLiteralExpression>());
}
[Test]
public void Parse_ZeroValue_ParsesSuccessfully()
{
// Arrange
var sql = "0";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<NumberLiteralExpression>());
}
[Test]
public void Parse_NegativeNumber_WithSubtraction_ParsesSuccessfully()
{
// Arrange
var sql = "0 - 5";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
}
[Test]
public void Parse_ConsecutiveOperators_ThrowsFormatException()
{
// Arrange
var sql = "10 + + 20";
// Act & Assert
Assert.Throws<FormatException>(() => _parser.Parse(sql));
}
[Test]
public void Parse_TrailingOperator_ThrowsFormatException()
{
// Arrange
var sql = "10 + 20 +";
// Act & Assert
Assert.Throws<FormatException>(() => _parser.Parse(sql));
}
[Test]
public void Parse_LeadingOperator_ThrowsFormatException()
{
// Arrange
var sql = "+ 10 + 20";
// Act & Assert
Assert.Throws<FormatException>(() => _parser.Parse(sql));
}
#endregion
#region Function-Specific Tests
[Test]
public void Parse_SumFunctionLowercase_ParsesSuccessfully()
{
// Arrange
var sql = "sum(3_REVENUE)";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<SumFunction>());
}
[Test]
public void Parse_AvgFunctionMixedCase_ParsesSuccessfully()
{
// Arrange
var sql = "AvG(3_REVENUE)";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<AverageFunction>());
}
[Test]
public void Parse_MultipleFunctions_ParsesSuccessfully()
{
// Arrange
var sql = "SUM(3_REVENUE) + AVG(3_REVENUE)";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
}
[Test]
public void Parse_FunctionWithNumberArgument_ParsesSuccessfully()
{
// Arrange
var sql = "SUM(100)";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<SumFunction>());
}
[Test]
public void Parse_FunctionWithComplexExpression_ParsesSuccessfully()
{
// Arrange
var sql = "SUM(3_REVENUE * 2)";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<SumFunction>());
}
#endregion
#region Column Expression Tests
[Test]
public void Parse_VariousColumnIds_ParsesSuccessfully()
{
// Arrange & Act & Assert
var testCases = new[] { "1_DEPARTMENT_ID", "2_NAME", "3_REVENUE", "4_DISCHARGE_DATE", "586883_FIXED_COST", "586664_VARIABLE_COST" };
foreach (var sql in testCases)
{
var result = _parser.Parse(sql);
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<RegisteredTableColumnExpression>());
}
}
[Test]
public void Parse_ColumnInArithmeticExpression_ParsesSuccessfully()
{
// Arrange
var sql = "(3_REVENUE - 586883_FIXED_COST) / 586664_VARIABLE_COST";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
}
[Test]
public void Parse_ColumnWithFunction_ParsesSuccessfully()
{
// Arrange
var sql = "SUM(3_REVENUE) - AVG(586883_FIXED_COST)";
// Act
var result = _parser.Parse(sql);
// Assert
Assert.That(result, Is.Not.Null);
}
#endregion
}