using Strata.SqlTools.SqlBreakdown.Enums.SQL; using Strata.SqlTools.SqlBreakdown.Exceptions; using StatementReader = Strata.SqlTools.Statements.PostgreSql.StatementReader; namespace Strata.SqlTools.SqlBreakdown.Tests.PostgreSql; [TestFixture] public class StatementReaderTests { [Test] public void StatementReader_ParsesCalculationString() { // Arrange var calculationString = "(1 + 2) / SUM(column_id) + SUM(other_column) - SUM(another_column) * 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 - PostgreSQL-specific double-quoted identifiers (case-sensitive) 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)); }); } [Test] public void StatementReader_ParsesDoubleQuotedIdentifiersWithCase() { // Arrange - PostgreSQL preserves case in double-quoted identifiers var calculationString = "\"MyColumn\" + \"AnotherColumn\""; var sqlReader = new StatementReader(calculationString); var tokens = new List<(TokenType Type, string Value)>(); // Act while (sqlReader.Read()) { tokens.Add((sqlReader.TokenType, sqlReader.TokenValue)); } Assert.Multiple(() => { // Assert Assert.That(tokens[0].Type, Is.EqualTo(TokenType.ColumnIdentifier)); Assert.That(tokens[0].Value, Is.EqualTo("MyColumn")); Assert.That(tokens[2].Type, Is.EqualTo(TokenType.ColumnIdentifier)); Assert.That(tokens[2].Value, Is.EqualTo("AnotherColumn")); }); } [Test] public void StatementReader_ParsesPositionalParameters() { // Arrange - PostgreSQL positional parameters ($1, $2, etc.) var calculationString = "column_id = $1 AND status = $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, Is.Not.Empty); var paramTokens = tokens.Where(t => t.Type == TokenType.Parameter).ToList(); Assert.That(paramTokens, Has.Count.EqualTo(2)); Assert.Multiple(() => { Assert.That(paramTokens[0].Value, Is.EqualTo("$1")); Assert.That(paramTokens[1].Value, Is.EqualTo("$2")); }); } [Test] public void StatementReader_ParsesUnderscorePrefixedIdentifiers() { // Arrange - PostgreSQL allows underscores to start identifiers var calculationString = "_internal_column + _private_field"; var sqlReader = new StatementReader(calculationString); var tokens = new List<(TokenType Type, string Value)>(); // Act while (sqlReader.Read()) { tokens.Add((sqlReader.TokenType, sqlReader.TokenValue)); } // Assert var identifierTokens = tokens.Where(t => t.Type == TokenType.ColumnIdentifier).ToList(); Assert.That(identifierTokens.Count, Is.GreaterThanOrEqualTo(2)); } [Test] public void StatementReader_ParsesComplexExpression() { // Arrange - Complex PostgreSQL expression var calculationString = "(\"Price\" * quantity) - discount WHERE user_id = $1"; 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); var identifierCount = tokens.Count(t => t.Type == TokenType.ColumnIdentifier); Assert.That(identifierCount, Is.GreaterThan(0)); } [Test] public void StatementReader_ThrowsOnUnclosedDoubleQuote() { // Arrange var calculationString = "\"UNCLOSED_IDENTIFIER + 1"; var sqlReader = new StatementReader(calculationString); // Act & Assert Assert.Throws(() => { while (sqlReader.Read()) { } }); } [Test] public void StatementReader_ParsesWhitespace() { // Arrange var calculationString = " column_1 + column_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, Is.Not.Empty); } [Test] public void StatementReader_ParsesOperators() { // Arrange var calculationString = "a = b"; 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); } [Test] public void StatementReader_ParsesStrings() { // Arrange var calculationString = "column = 'string value' AND other = 'another string'"; var sqlReader = new StatementReader(calculationString); var tokens = new List<(TokenType Type, string Value)>(); // Act while (sqlReader.Read()) { tokens.Add((sqlReader.TokenType, sqlReader.TokenValue)); } // Assert var stringTokens = tokens.Where(t => t.Type == TokenType.String).ToList(); Assert.That(stringTokens.Count, Is.GreaterThanOrEqualTo(2)); } [Test] public void StatementReader_ParsesNumbers() { // Arrange var calculationString = "42 + 3.14 - 100"; var sqlReader = new StatementReader(calculationString); var tokens = new List<(TokenType Type, string Value)>(); // Act while (sqlReader.Read()) { tokens.Add((sqlReader.TokenType, sqlReader.TokenValue)); } // Assert var numberTokens = tokens.Where(t => t.Type == TokenType.Number).ToList(); Assert.That(numberTokens, Is.Not.Empty); } }