using Strata.SqlTools.SqlBreakdown.Enums.SQL; using Strata.SqlTools.SqlBreakdown.Exceptions; using StatementReader = Strata.SqlTools.Statements.Snowflake.StatementReader; namespace Strata.SqlTools.SqlBreakdown.Tests.Snowflake; [TestFixture] public class StatementReaderTests { [Test] public void StatementReader_ParsesCalculationString() { // Arrange var calculationString = "(1 + 2) / SUM([586883]) + SUM([586664]) - SUM([586664_1]) * 2.3"; var sqlReader = new StatementReader(calculationString); var tokens = new List<(TokenType Type, string Value)>(); // Act while (sqlReader.Read()) { tokens.Add((sqlReader.TokenType, sqlReader.TokenValue)); } // Assert Assert.That(tokens, Is.Not.Empty); Assert.Multiple(() => { Assert.That(tokens[0].Type, Is.EqualTo(TokenType.LeftParenthesis)); Assert.That(tokens[1].Type, Is.EqualTo(TokenType.Number)); Assert.That(tokens[1].Value, Is.EqualTo("1")); }); } [Test] public void StatementReader_ParsesDoubleQuotedIdentifiers() { // Arrange - Snowflake-specific double-quoted identifiers var calculationString = "\"COLUMN_1\" + \"COLUMN_2\" * 2"; var sqlReader = new StatementReader(calculationString); var tokens = new List<(TokenType Type, string Value)>(); // Act while (sqlReader.Read()) { tokens.Add((sqlReader.TokenType, sqlReader.TokenValue)); } // Assert Assert.That(tokens, Has.Count.EqualTo(5)); // "COLUMN_1" + "COLUMN_2" * 2 Assert.Multiple(() => { Assert.That(tokens[0].Type, Is.EqualTo(TokenType.ColumnIdentifier)); Assert.That(tokens[0].Value, Is.EqualTo("COLUMN_1")); Assert.That(tokens[1].Type, Is.EqualTo(TokenType.Plus)); Assert.That(tokens[2].Type, Is.EqualTo(TokenType.ColumnIdentifier)); Assert.That(tokens[2].Value, Is.EqualTo("COLUMN_2")); Assert.That(tokens[3].Type, Is.EqualTo(TokenType.Multiply)); Assert.That(tokens[4].Type, Is.EqualTo(TokenType.Number)); Assert.That(tokens[4].Value, Is.EqualTo("2")); }); } [Test] public void StatementReader_ParsesMixedIdentifierTypes() { // Arrange - Mix of double-quoted and square-bracketed identifiers var calculationString = "\"REVENUE\" + [COST] - PROFIT"; var sqlReader = new StatementReader(calculationString); var tokens = new List<(TokenType Type, string Value)>(); // Act while (sqlReader.Read()) { tokens.Add((sqlReader.TokenType, sqlReader.TokenValue)); } // Assert Assert.That(tokens, Has.Count.EqualTo(5)); Assert.Multiple(() => { Assert.That(tokens[0].Type, Is.EqualTo(TokenType.ColumnIdentifier)); Assert.That(tokens[0].Value, Is.EqualTo("REVENUE")); Assert.That(tokens[1].Type, Is.EqualTo(TokenType.Plus)); Assert.That(tokens[2].Type, Is.EqualTo(TokenType.ColumnIdentifier)); Assert.That(tokens[2].Value, Is.EqualTo("COST")); Assert.That(tokens[3].Type, Is.EqualTo(TokenType.Minus)); Assert.That(tokens[4].Type, Is.EqualTo(TokenType.String)); Assert.That(tokens[4].Value, Is.EqualTo("PROFIT")); }); } [Test] public void StatementReader_ParsesFunctionWithDoubleQuotedColumn() { // Arrange var calculationString = "SUM(\"TOTAL_AMOUNT\") / COUNT(\"ID\")"; var sqlReader = new StatementReader(calculationString); var tokens = new List<(TokenType Type, string Value)>(); // Act while (sqlReader.Read()) { tokens.Add((sqlReader.TokenType, sqlReader.TokenValue)); } // Assert Assert.That(tokens, Is.Not.Empty); Assert.Multiple(() => { Assert.That(tokens[0].Type, Is.EqualTo(TokenType.FunctionStart)); Assert.That(tokens[0].Value, Is.EqualTo("SUM")); Assert.That(tokens[1].Type, Is.EqualTo(TokenType.ColumnIdentifier)); Assert.That(tokens[1].Value, Is.EqualTo("TOTAL_AMOUNT")); Assert.That(tokens[2].Type, Is.EqualTo(TokenType.FunctionEnd)); }); } [Test] public void StatementReader_ParsesNumericIdentifiersWithUnderscores() { // Arrange - Column identifiers starting with numbers (e.g., "3_REVENUE") var calculationString = "3_REVENUE + 4_COST - 5_PROFIT"; var sqlReader = new StatementReader(calculationString); var tokens = new List<(TokenType Type, string Value)>(); // Act while (sqlReader.Read()) { tokens.Add((sqlReader.TokenType, sqlReader.TokenValue)); } // Assert Assert.That(tokens, Has.Count.EqualTo(5)); Assert.Multiple(() => { Assert.That(tokens[0].Type, Is.EqualTo(TokenType.ColumnIdentifier)); Assert.That(tokens[0].Value, Is.EqualTo("3_REVENUE")); Assert.That(tokens[1].Type, Is.EqualTo(TokenType.Plus)); Assert.That(tokens[2].Type, Is.EqualTo(TokenType.ColumnIdentifier)); Assert.That(tokens[2].Value, Is.EqualTo("4_COST")); Assert.That(tokens[3].Type, Is.EqualTo(TokenType.Minus)); Assert.That(tokens[4].Type, Is.EqualTo(TokenType.ColumnIdentifier)); Assert.That(tokens[4].Value, Is.EqualTo("5_PROFIT")); }); } [Test] public void StatementReader_ParsesDecimalNumbers() { // Arrange var calculationString = "123.45 + 67.89"; var sqlReader = new StatementReader(calculationString); var tokens = new List<(TokenType Type, string Value)>(); // Act while (sqlReader.Read()) { tokens.Add((sqlReader.TokenType, sqlReader.TokenValue)); } // Assert Assert.That(tokens, Has.Count.EqualTo(3)); Assert.Multiple(() => { Assert.That(tokens[0].Type, Is.EqualTo(TokenType.Number)); Assert.That(tokens[0].Value, Is.EqualTo("123.45")); Assert.That(tokens[1].Type, Is.EqualTo(TokenType.Plus)); Assert.That(tokens[2].Type, Is.EqualTo(TokenType.Number)); Assert.That(tokens[2].Value, Is.EqualTo("67.89")); }); } [Test] public void StatementReader_ParsesUppercaseIdentifiers() { // Arrange - Snowflake conventionally uses uppercase for identifiers var calculationString = "REVENUE + COST"; var sqlReader = new StatementReader(calculationString); var tokens = new List<(TokenType Type, string Value)>(); // Act while (sqlReader.Read()) { tokens.Add((sqlReader.TokenType, sqlReader.TokenValue)); } // Assert Assert.That(tokens, Has.Count.EqualTo(3)); Assert.Multiple(() => { Assert.That(tokens[0].Type, Is.EqualTo(TokenType.String)); Assert.That(tokens[0].Value, Is.EqualTo("REVENUE")); Assert.That(tokens[1].Type, Is.EqualTo(TokenType.Plus)); Assert.That(tokens[2].Type, Is.EqualTo(TokenType.String)); Assert.That(tokens[2].Value, Is.EqualTo("COST")); }); } [Test] public void StatementReader_HandlesEmptyString() { // Arrange & Act & Assert Assert.Throws(() => new StatementReader("")); } [Test] public void StatementReader_ThrowsOnUnclosedDoubleQuote() { // Arrange var calculationString = "\"COLUMN_NAME + 5"; var sqlReader = new StatementReader(calculationString); // Act & Assert var ex = Assert.Throws(() => { while (sqlReader.Read()) { // Read all tokens } }); Assert.That(ex!.Message, Does.Contain("Expected closing double quote")); } [Test] public void StatementReader_ParsesNestedFunctions() { // Arrange var calculationString = "SUM(AVG(\"REVENUE\"))"; var sqlReader = new StatementReader(calculationString); var tokens = new List<(TokenType Type, string Value)>(); // Act while (sqlReader.Read()) { tokens.Add((sqlReader.TokenType, sqlReader.TokenValue)); } // Assert Assert.That(tokens, Is.Not.Empty); Assert.Multiple(() => { Assert.That(tokens[0].Type, Is.EqualTo(TokenType.FunctionStart)); Assert.That(tokens[0].Value, Is.EqualTo("SUM")); Assert.That(tokens[1].Type, Is.EqualTo(TokenType.FunctionStart)); Assert.That(tokens[1].Value, Is.EqualTo("AVG")); Assert.That(tokens[2].Type, Is.EqualTo(TokenType.ColumnIdentifier)); Assert.That(tokens[2].Value, Is.EqualTo("REVENUE")); }); } }