SonarQube Analysis / sonarqube (pull_request) Successful in 2m50s
Commit 5202d93 made the SqlServer-namespaced Markdown generator methods
static, which left the LinqToSql / PostgreSql / Snowflake wrapper
classes' instance methods delegating to nothing but a static call.
SonarQube re-flagged those 10 wrapper methods as CA1822 on the next
scan.
This sweep:
- Makes all 10 wrapper instance methods `static` (`dotnet format` driven).
- Makes `Markdown.LinqToSql.QueryBreakdownGenerator.GenerateCombinedDiagram`
static preemptively — it composes two static helpers and would otherwise
be the next-iteration cascade flag.
- Removes the now-dead `_baseGenerator` field and its initializing
constructor from all six dialect wrappers (LinqToSql / PostgreSql /
Snowflake × QueryBreakdownGenerator + SqlStatementGenerator). The
classes keep their implicit parameterless constructor so `new
Snowflake.QueryBreakdownGenerator()` still compiles.
- Updates the one test call site (`GenerateCombinedDiagram`) the fixer
didn't catch to use type-name form.
BREAKING CHANGE: External NuGet consumers calling
`instance.Generate*Diagram(...)` on `Markdown.LinqToSql.*`,
`Markdown.PostgreSql.*`, or `Markdown.Snowflake.*` generators must
switch to type-name form, e.g. `Markdown.Snowflake.SqlStatementGenerator.GenerateSequenceDiagram(...)`.
The class types and parameterless constructors remain — only the call
syntax for these methods changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
252 lines
8.4 KiB
C#
252 lines
8.4 KiB
C#
using Strata.SqlTools.Breakdowns.Snowflake;
|
|
using Strata.SqlTools.Markdown.Snowflake;
|
|
|
|
namespace Strata.SqlTools.Markdown.Tests.Snowflake;
|
|
|
|
[TestFixture]
|
|
public class QueryBreakdownGeneratorTests
|
|
{
|
|
private QueryBreakdownGenerator _generator = null!;
|
|
|
|
[SetUp]
|
|
public void Setup()
|
|
{
|
|
_generator = new QueryBreakdownGenerator();
|
|
}
|
|
|
|
[Test]
|
|
public void GenerateMermaidDiagram_SimpleSnowflakeQuery_GeneratesValidMermaid()
|
|
{
|
|
// Arrange - Using Snowflake syntax with uppercase identifiers
|
|
var query = new QueryBreakdown("USER_ID, USER_NAME", "USERS");
|
|
|
|
// Act
|
|
var result = QueryBreakdownGenerator.GenerateMermaidDiagram(query, "Simple User Query");
|
|
|
|
// Assert
|
|
Assert.That(result, Does.Contain("```mermaid"));
|
|
Assert.That(result, Does.Contain("flowchart TD"));
|
|
Assert.That(result, Does.Contain("### Simple User Query"));
|
|
Assert.That(result, Does.Contain("SELECT"));
|
|
Assert.That(result, Does.Contain("FROM"));
|
|
Assert.That(result, Does.Contain("```"));
|
|
}
|
|
|
|
[Test]
|
|
public void GenerateMermaidDiagram_WithSnowflakeColonParameters_HandlesParameters()
|
|
{
|
|
// Arrange - Snowflake uses :parameter syntax
|
|
var query = QueryBreakdown.Parse(@"
|
|
SELECT USER_ID, USER_NAME, STATUS
|
|
FROM USERS
|
|
WHERE USER_ID = :userId
|
|
AND STATUS = :status
|
|
", isMicrosoftSql: false);
|
|
|
|
// Act
|
|
var result = QueryBreakdownGenerator.GenerateMermaidDiagram(query);
|
|
|
|
// Assert
|
|
Assert.That(result, Does.Contain("WHERE"));
|
|
Assert.That(result, Does.Contain("```mermaid"));
|
|
Assert.That(result, Does.Contain("flowchart TD"));
|
|
}
|
|
|
|
[Test]
|
|
public void GenerateMermaidDiagram_WithSnowflakeCTE_ShowsWithClause()
|
|
{
|
|
// Arrange - Snowflake CTE with uppercase naming
|
|
var query = QueryBreakdown.Parse(@"
|
|
WITH ACTIVE_USERS AS (
|
|
SELECT USER_ID, USER_NAME
|
|
FROM USERS
|
|
WHERE STATUS = 'ACTIVE'
|
|
)
|
|
SELECT U.USER_ID, U.USER_NAME, COUNT(O.ORDER_ID) AS ORDER_COUNT
|
|
FROM ACTIVE_USERS U
|
|
JOIN ORDERS O ON U.USER_ID = O.USER_ID
|
|
GROUP BY U.USER_ID, U.USER_NAME
|
|
", isMicrosoftSql: false);
|
|
|
|
// Act
|
|
var result = QueryBreakdownGenerator.GenerateMermaidDiagram(query, "Users with Orders");
|
|
|
|
// Assert
|
|
Assert.That(result, Does.Contain("WITH Clause"));
|
|
Assert.That(result, Does.Contain("ACTIVE_USERS"));
|
|
Assert.That(result, Does.Contain("GROUP BY"));
|
|
}
|
|
|
|
[Test]
|
|
public void GenerateMermaidDiagram_WithSnowflakeQualify_HandlesQualifyClause()
|
|
{
|
|
// Arrange - Snowflake QUALIFY clause (window function filtering)
|
|
var query = QueryBreakdown.Parse(@"
|
|
SELECT
|
|
CUSTOMER_ID,
|
|
ORDER_DATE,
|
|
ORDER_AMOUNT,
|
|
ROW_NUMBER() OVER (PARTITION BY CUSTOMER_ID ORDER BY ORDER_DATE DESC) AS RN
|
|
FROM ORDERS
|
|
QUALIFY RN = 1
|
|
", isMicrosoftSql: false);
|
|
|
|
// Act
|
|
var result = QueryBreakdownGenerator.GenerateMermaidDiagram(query, "Latest Orders per Customer");
|
|
|
|
// Assert
|
|
Assert.That(result, Does.Contain("SELECT"));
|
|
Assert.That(result, Does.Contain("FROM"));
|
|
Assert.That(result, Does.Contain("```mermaid"));
|
|
}
|
|
|
|
[Test]
|
|
public void GenerateMermaidDiagram_WithDoubleQuotedIdentifiers_HandlesSnowflakeQuoting()
|
|
{
|
|
// Arrange - Snowflake uses "identifier" for case-sensitive names
|
|
var query = QueryBreakdown.Parse(@"
|
|
SELECT ""userId"", ""userName"", ""emailAddress""
|
|
FROM ""Users""
|
|
WHERE ""status"" = 'ACTIVE'
|
|
", isMicrosoftSql: false);
|
|
|
|
// Act
|
|
var result = QueryBreakdownGenerator.GenerateMermaidDiagram(query);
|
|
|
|
// Assert
|
|
Assert.That(result, Does.Contain("SELECT"));
|
|
Assert.That(result, Does.Contain("WHERE"));
|
|
Assert.That(result, Does.Contain("FROM"));
|
|
}
|
|
|
|
[Test]
|
|
public void GenerateMermaidDiagram_ComplexSnowflakeQuery_GeneratesCompleteDiagram()
|
|
{
|
|
// Arrange - Complex Snowflake query with multiple clauses
|
|
var query = QueryBreakdown.Parse(@"
|
|
WITH SALES_DATA AS (
|
|
SELECT
|
|
REGION,
|
|
PRODUCT_ID,
|
|
SUM(SALES_AMOUNT) AS TOTAL_SALES,
|
|
COUNT(*) AS ORDER_COUNT
|
|
FROM SALES
|
|
WHERE SALE_DATE >= :startDate AND SALE_DATE <= :endDate
|
|
GROUP BY REGION, PRODUCT_ID
|
|
)
|
|
SELECT
|
|
SD.REGION,
|
|
P.PRODUCT_NAME,
|
|
SD.TOTAL_SALES,
|
|
SD.ORDER_COUNT
|
|
FROM SALES_DATA SD
|
|
JOIN PRODUCTS P ON SD.PRODUCT_ID = P.PRODUCT_ID
|
|
WHERE SD.TOTAL_SALES > :minSales
|
|
ORDER BY SD.TOTAL_SALES DESC
|
|
", isMicrosoftSql: false);
|
|
|
|
// Act
|
|
var result = QueryBreakdownGenerator.GenerateMermaidDiagram(query, "Regional Sales Analysis");
|
|
|
|
// Assert
|
|
Assert.That(result, Does.Contain("### Regional Sales Analysis"));
|
|
Assert.That(result, Does.Contain("WITH Clause"));
|
|
Assert.That(result, Does.Contain("SALES_DATA"));
|
|
Assert.That(result, Does.Contain("SELECT"));
|
|
Assert.That(result, Does.Contain("FROM"));
|
|
Assert.That(result, Does.Contain("WHERE"));
|
|
Assert.That(result, Does.Contain("ORDER BY"));
|
|
Assert.That(result, Does.Contain("flowchart TD"));
|
|
}
|
|
|
|
[Test]
|
|
public void GenerateMermaidDiagram_WithGroupByAndHaving_IncludesBothClauses()
|
|
{
|
|
// Arrange
|
|
var query = QueryBreakdown.Parse(@"
|
|
SELECT CATEGORY, COUNT(*) AS PRODUCT_COUNT, AVG(PRICE) AS AVG_PRICE
|
|
FROM PRODUCTS
|
|
GROUP BY CATEGORY
|
|
HAVING COUNT(*) > 10 AND AVG(PRICE) < 100
|
|
", isMicrosoftSql: false);
|
|
|
|
// Act
|
|
var result = QueryBreakdownGenerator.GenerateMermaidDiagram(query);
|
|
|
|
// Assert
|
|
Assert.That(result, Does.Contain("GROUP BY"));
|
|
Assert.That(result, Does.Contain("HAVING"));
|
|
}
|
|
|
|
[Test]
|
|
public void GenerateMermaidDiagram_NullTitle_GeneratesWithoutTitle()
|
|
{
|
|
// Arrange
|
|
var query = new QueryBreakdown("ID", "ORDERS");
|
|
|
|
// Act
|
|
var result = QueryBreakdownGenerator.GenerateMermaidDiagram(query, null);
|
|
|
|
// Assert
|
|
Assert.That(result, Does.Not.Contain("###"));
|
|
Assert.That(result, Does.Contain("```mermaid"));
|
|
Assert.That(result, Does.Contain("flowchart TD"));
|
|
}
|
|
|
|
[Test]
|
|
public void GenerateMermaidDiagram_EmptyTitle_GeneratesWithoutTitle()
|
|
{
|
|
// Arrange
|
|
var query = new QueryBreakdown("ORDER_ID", "ORDERS");
|
|
|
|
// Act
|
|
var result = QueryBreakdownGenerator.GenerateMermaidDiagram(query, "");
|
|
|
|
// Assert
|
|
Assert.That(result, Does.Not.Contain("###"));
|
|
Assert.That(result, Does.Contain("SELECT"));
|
|
}
|
|
|
|
[Test]
|
|
public void GenerateMermaidDiagram_SnowflakeTypeCasting_PreservesSyntax()
|
|
{
|
|
// Arrange - Snowflake :: casting syntax
|
|
var query = QueryBreakdown.Parse(@"
|
|
SELECT
|
|
ORDER_ID::VARCHAR AS ORDER_CODE,
|
|
AMOUNT::DECIMAL(10,2) AS FORMATTED_AMOUNT,
|
|
CREATE_DATE::TIMESTAMP AS CREATED_AT
|
|
FROM ORDERS
|
|
", isMicrosoftSql: false);
|
|
|
|
// Act
|
|
var result = QueryBreakdownGenerator.GenerateMermaidDiagram(query, "Type Cast Example");
|
|
|
|
// Assert
|
|
Assert.That(result, Does.Contain("SELECT"));
|
|
Assert.That(result, Does.Contain("FROM"));
|
|
Assert.That(result, Does.Contain("### Type Cast Example"));
|
|
}
|
|
|
|
[Test]
|
|
public void GenerateMermaidDiagram_SnowflakeJsonPath_HandlesJsonNotation()
|
|
{
|
|
// Arrange - Snowflake JSON path notation (: for path access)
|
|
var query = QueryBreakdown.Parse(@"
|
|
SELECT
|
|
JSON_DATA:name::STRING AS NAME,
|
|
JSON_DATA:address.city::STRING AS CITY,
|
|
JSON_DATA:age::NUMBER AS AGE
|
|
FROM USER_JSON
|
|
", isMicrosoftSql: false);
|
|
|
|
// Act
|
|
var result = QueryBreakdownGenerator.GenerateMermaidDiagram(query, "JSON Data");
|
|
|
|
// Assert
|
|
Assert.That(result, Does.Contain("SELECT"));
|
|
Assert.That(result, Does.Contain("FROM"));
|
|
Assert.That(result, Does.Contain("```mermaid"));
|
|
}
|
|
}
|