Files
sql-utilities/docs/SqlUtilities.PostgreSql.md

648 lines
14 KiB
Markdown

# Strata.SqlTools.PostgreSql
**PostgreSQL SQL Query Analysis and Breakdown**
---
## Overview
The `Strata.SqlTools.PostgreSql` package provides comprehensive support for parsing, analyzing, and manipulating PostgreSQL SQL queries. It extends the core `Strata.SqlTools` library with PostgreSQL-specific syntax support, including positional parameters (`$1`, `$2`) and named parameters (`:param`).
### Key Features
-**PostgreSQL Syntax Support** - Full support for PostgreSQL SQL dialect
-**Positional Parameters** - `$1`, `$2`, `$3` parameter syntax
-**Named Parameters** - `:parameter` and `@parameter` syntax
-**Query Breakdown** - Parse SELECT statements into component clauses
-**Query Collections** - Batch analysis with parameter usage reports
-**Statement Parsing** - Token-based SQL parsing with PostgreSQL extensions
-**Expression System** - Type-safe expression trees for query building
-**Mermaid Diagrams** - Visual query structure and flow diagrams
---
## Installation
```bash
dotnet add package Strata.SqlTools.PostgreSql
```
**Dependencies:**
- `Strata.SqlTools` (core functionality)
- .NET 8.0+
---
## Quick Start
### Basic Query Breakdown
```csharp
using Strata.SqlTools.Breakdowns.PostgreSql;
string sql = @"
SELECT id, name, email, age
FROM users
WHERE age > $1
AND is_active = $2
ORDER BY name ASC
";
var breakdown = new QueryBreakdown(sql);
Console.WriteLine($"SELECT: {breakdown.SelectClause}");
Console.WriteLine($"FROM: {breakdown.FromClause}");
Console.WriteLine($"WHERE: {breakdown.WhereClause}");
Console.WriteLine($"ORDER BY: {breakdown.OrderByClause}");
// Access parameters
var parameters = breakdown.GetParameters();
foreach (var param in parameters)
{
Console.WriteLine($"Parameter: {param.Name}");
}
```
**Output:**
```
SELECT: id, name, email, age
FROM: users
WHERE: age > $1 AND is_active = $2
ORDER BY: name ASC
Parameter: $1
Parameter: $2
```
---
## PostgreSQL-Specific Features
### Positional Parameters ($n)
PostgreSQL uses `$1`, `$2`, etc. for positional parameters:
```csharp
string sql = @"
SELECT * FROM orders
WHERE customer_id = $1
AND order_date > $2
AND status = $3
";
var breakdown = new QueryBreakdown(sql);
// Add parameter values
breakdown.AddParameter("$1", 12345);
breakdown.AddParameter("$2", DateTime.Now.AddDays(-30));
breakdown.AddParameter("$3", "Pending");
// Get SQL with parameters
string fullSql = breakdown.GetSql();
```
### Named Parameters (:param or @param)
PostgreSQL also supports named parameters:
```csharp
string sql = @"
SELECT * FROM products
WHERE price > :min_price
AND category = :category
AND in_stock = @stock_flag
";
var breakdown = new QueryBreakdown(sql);
breakdown.AddParameter(":min_price", 99.99m);
breakdown.AddParameter(":category", "Electronics");
breakdown.AddParameter("@stock_flag", true);
```
### Parameter Dictionary
Get all parameters as a dictionary:
```csharp
var breakdown = new QueryBreakdown(sql);
breakdown.AddParameter("$1", 100);
breakdown.AddParameter("$2", "Active");
var paramDict = breakdown.GetParameterDictionary();
foreach (var (name, value) in paramDict)
{
Console.WriteLine($"{name} = {value}");
}
// Output:
// $1 = 100
// $2 = Active
```
---
## QueryBreakdownCollection
Analyze multiple queries and generate comprehensive reports.
### Basic Usage
```csharp
using Strata.SqlTools.Breakdowns.PostgreSql;
var collection = new QueryBreakdownCollection();
// Add multiple queries
collection.Add(new QueryBreakdown(@"
SELECT id, name FROM users WHERE age > $1
"));
collection.Add(new QueryBreakdown(@"
SELECT * FROM orders WHERE user_id = $1 AND status = $2
"));
collection.Add(new QueryBreakdown(@"
SELECT product_name, price FROM products WHERE category = :category
"));
// Get summaries
var summaries = collection.GetQuerySummaries();
foreach (var summary in summaries)
{
Console.WriteLine(summary);
}
```
### Parameter Usage Report
The `GetParameterUsageReport()` method provides detailed information about parameter usage across all queries:
```csharp
var report = collection.GetParameterUsageReport();
Console.WriteLine($"Total Queries: {report.TotalQueries}");
Console.WriteLine($"Total Parameters: {report.TotalParameters}");
Console.WriteLine($"Unique Parameters: {report.UniqueParameterNames.Count}");
Console.WriteLine("\nPositional Parameters:");
foreach (var (param, count) in report.PositionalParameterUsage)
{
Console.WriteLine($" {param}: used {count} times");
}
Console.WriteLine("\nNamed Parameters:");
foreach (var (param, count) in report.NamedParameterUsage)
{
Console.WriteLine($" {param}: used {count} times");
}
```
**Example Output:**
```
Total Queries: 3
Total Parameters: 4
Unique Parameters: 3
Positional Parameters:
$1: used 2 times
$2: used 1 times
Named Parameters:
:category: used 1 times
```
### Collection Analysis Methods
```csharp
var collection = new QueryBreakdownCollection();
// ... add queries ...
// Get total selected columns across all queries
int totalColumns = collection.GetTotalSelectedColumns();
// Get all unique table references
var tables = collection.GetUniqueTableReferences();
Console.WriteLine($"Tables: {string.Join(", ", tables)}");
// Get query summaries
var summaries = collection.GetQuerySummaries();
```
---
## Markdown Visualization
The `Strata.SqlTools.Markdown` package includes PostgreSQL-specific generators.
### Installation
```bash
dotnet add package Strata.SqlTools.Markdown
```
### QueryBreakdownGenerator
Generate Mermaid diagrams for individual queries:
```csharp
using Strata.SqlTools.Markdown.PostgreSql;
var breakdown = new QueryBreakdown(@"
SELECT u.id, u.name, o.total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.age > $1
ORDER BY o.total DESC
");
var generator = new QueryBreakdownGenerator();
// Generate flowchart diagram
string diagram = generator.GenerateMermaidDiagram(breakdown, "User Orders Query");
```
**Example Output:**
```mermaid
flowchart TD
Start([Start]) --> Select[SELECT u.id, u.name, o.total]
Select --> From[FROM users u]
From --> Join[JOIN orders o]
Join --> Where[WHERE u.age > $1]
Where --> OrderBy[ORDER BY o.total DESC]
OrderBy --> End([End])
```
### SqlStatementGenerator
Generate sequence and ER diagrams:
```csharp
var sqlGenerator = new SqlStatementGenerator();
// Sequence diagram showing query execution
string sequenceDiagram = sqlGenerator.GenerateSequenceDiagram(
breakdown,
"Query Execution Flow"
);
// Entity-relationship diagram
string erDiagram = sqlGenerator.GenerateEntityRelationshipDiagram(
breakdown,
"Database Schema"
);
```
### QueryBreakdownCollectionGenerator
Generate visualizations for collections of queries:
```csharp
using Strata.SqlTools.Markdown.PostgreSql;
var collection = new QueryBreakdownCollection();
// ... add queries ...
var collectionGenerator = new QueryBreakdownCollectionGenerator();
// Generate summary with all query diagrams
string summary = collectionGenerator.GenerateCollectionSummary(
collection,
"Database Queries"
);
// Generate parameter usage visualization
string paramDiagram = collectionGenerator.GenerateParameterUsageDiagram(
collection,
"Parameter Analysis"
);
// Generate table reference diagram
string tableDiagram = collectionGenerator.GenerateTableReferenceDiagram(
collection,
"Table Dependencies"
);
```
---
## Statement Parsing
### StatementParser
Utilities for normalizing and cleaning SQL statements:
```csharp
using Strata.SqlTools.Statements.PostgreSql;
string sql = @"
-- This is a comment
SELECT /* inline comment */ id, name
FROM users
WHERE age > 21;
";
// Remove comments
string cleaned = StatementParser.RemoveComments(sql);
// Normalize whitespace
string normalized = StatementParser.NormalizeWhitespace(sql);
```
### StatementReader
Token-based SQL parsing:
```csharp
using Strata.SqlTools.Statements.PostgreSql;
using Strata.SqlTools.Enums.SQL;
var reader = new StatementReader(sql);
while (reader.Read())
{
Console.WriteLine($"Token: {reader.TokenType}, Value: '{reader.TokenValue}'");
}
```
**Example Output:**
```
Token: Keyword, Value: 'SELECT'
Token: Identifier, Value: 'id'
Token: Symbol, Value: ','
Token: Identifier, Value: 'name'
Token: Keyword, Value: 'FROM'
Token: Identifier, Value: 'users'
...
```
---
## Advanced Usage
### Building Queries Programmatically
```csharp
var breakdown = new QueryBreakdown("*", "users");
// Add WHERE clauses
breakdown.AddWhereClause("age > $1");
breakdown.AddWhereClause("is_active = $2", "AND");
// Add ORDER BY
breakdown.OrderByClause = "name ASC, created_date DESC";
// Add GROUP BY
breakdown.GroupByClause = "department";
breakdown.HavingClause = "COUNT(*) > 5";
// Add parameters
breakdown.AddParameter("$1", 21);
breakdown.AddParameter("$2", true);
// Generate SQL
string sql = breakdown.GetSql();
Console.WriteLine(sql);
```
**Output:**
```sql
SELECT *
FROM users
WHERE age > $1 AND is_active = $2
GROUP BY department
HAVING COUNT(*) > 5
ORDER BY name ASC, created_date DESC
```
### Cloning and Modifying Queries
```csharp
var original = new QueryBreakdown(@"
SELECT * FROM users WHERE age > $1
");
// Clone the query
var clone = (QueryBreakdown)original.Clone();
// Modify the clone
clone.AddWhereClause("email IS NOT NULL", "AND");
clone.SelectClause = "id, name, email";
// Original remains unchanged
Console.WriteLine(original.GetSql());
Console.WriteLine(clone.GetSql());
```
### Merging Queries
```csharp
var query1 = new QueryBreakdown("id, name", "users");
query1.AddWhereClause("age > $1");
var query2 = new QueryBreakdown("*", "users");
query2.AddWhereClause("is_active = $1");
// Merge query2 into query1
query1.Merge(query2);
// Result includes WHERE clauses from both
Console.WriteLine(query1.GetSql());
```
---
## Common Table Expressions (CTEs)
PostgreSQL supports WITH clauses:
```csharp
var mainQuery = new QueryBreakdown("*", "filtered_users");
// Define a CTE
var cteQuery = new QueryBreakdown("id, name, age", "users");
cteQuery.AddWhereClause("age >= $1");
// Add CTE to main query
mainQuery.AddWithClause("filtered_users", cteQuery);
// Generate SQL
string sql = mainQuery.GetSql();
Console.WriteLine(sql);
```
**Output:**
```sql
WITH filtered_users AS (
SELECT id, name, age
FROM users
WHERE age >= $1
)
SELECT *
FROM filtered_users
```
---
## Parameter Best Practices
### 1. Use Positional Parameters for Simple Queries
```csharp
// Good: Simple, sequential positional parameters
var query = new QueryBreakdown(@"
SELECT * FROM users
WHERE age > $1 AND department = $2
");
query.AddParameter("$1", 21);
query.AddParameter("$2", "Engineering");
```
### 2. Use Named Parameters for Complex Queries
```csharp
// Good: Named parameters for clarity
var query = new QueryBreakdown(@"
SELECT * FROM orders
WHERE customer_id = :customer_id
AND order_date BETWEEN :start_date AND :end_date
AND status = :status
");
query.AddParameter(":customer_id", customerId);
query.AddParameter(":start_date", startDate);
query.AddParameter(":end_date", endDate);
query.AddParameter(":status", "Pending");
```
### 3. Validate Parameter Count
```csharp
var breakdown = new QueryBreakdown(sql);
var parameters = breakdown.GetParameters();
// Ensure all parameters have values
foreach (var param in parameters)
{
if (!breakdown.GetParameterDictionary().ContainsKey(param.Name))
{
throw new InvalidOperationException($"Missing value for parameter: {param.Name}");
}
}
```
---
## Testing
### Unit Testing with PostgreSQL Queries
```csharp
[Test]
public void QueryBreakdown_PostgreSqlSyntax_ParsesCorrectly()
{
var sql = @"
SELECT id, name
FROM users
WHERE age > $1
AND status = $2
";
var breakdown = new QueryBreakdown(sql);
Assert.That(breakdown.SelectClause, Is.EqualTo("id, name"));
Assert.That(breakdown.FromClause, Is.EqualTo("users"));
Assert.That(breakdown.WhereClause, Does.Contain("$1"));
Assert.That(breakdown.WhereClause, Does.Contain("$2"));
}
[Test]
public void ParameterUsageReport_MultipleQueries_CountsCorrectly()
{
var collection = new QueryBreakdownCollection();
collection.Add(new QueryBreakdown("SELECT * FROM users WHERE id = $1"));
collection.Add(new QueryBreakdown("SELECT * FROM orders WHERE user_id = $1"));
var report = collection.GetParameterUsageReport();
Assert.That(report.TotalQueries, Is.EqualTo(2));
Assert.That(report.PositionalParameterUsage["$1"], Is.EqualTo(2));
}
```
---
## PostgreSQL-Specific SQL Features
### Array Support
```csharp
var breakdown = new QueryBreakdown(@"
SELECT * FROM users
WHERE tags && $1::text[]
");
breakdown.AddParameter("$1", new[] { "admin", "moderator" });
```
### JSON/JSONB Operators
```csharp
var breakdown = new QueryBreakdown(@"
SELECT data->'name' as name
FROM documents
WHERE data @> $1::jsonb
");
breakdown.AddParameter("$1", "{\"status\": \"active\"}");
```
### RETURNING Clause
```csharp
// INSERT with RETURNING
var breakdown = new QueryBreakdown(@"
INSERT INTO users (name, email)
VALUES ($1, $2)
RETURNING id, created_at
");
breakdown.AddParameter("$1", "John Doe");
breakdown.AddParameter("$2", "john@example.com");
```
---
## Related Documentation
- [SqlUtilities.Core.md](SqlUtilities.Core.md) - Core library documentation
- [SqlUtilities.SqlServer.md](SqlUtilities.SqlServer.md) - SQL Server comparison
- [SqlUtilities.Snowflake.md](SqlUtilities.Snowflake.md) - Snowflake comparison
---
## API Reference
### Key Classes
| Class | Purpose |
|-------|---------|
| `QueryBreakdown` | Parse and manipulate PostgreSQL SELECT queries |
| `QueryBreakdownCollection` | Manage collections of queries with analysis |
| `StatementParser` | SQL parsing utilities |
| `StatementReader` | Token-based SQL reader |
| `StatementExpressionParser` | Parse SQL into expression trees |
### Namespaces
- `Strata.SqlTools.Breakdowns.PostgreSql` - Query breakdown classes
- `Strata.SqlTools.Statements.PostgreSql` - Statement parsing
- `Strata.SqlTools.Visitors.PostgreSql` - SQL visitor patterns
- `Strata.SqlTools.ExpressionFactory.PostgreSql` - Expression factories
- `Strata.SqlTools.Markdown.PostgreSql` - Markdown generators
---
**Version**: 1.0.0
**Last Updated**: February 2026
**Package**: Strata.SqlTools.PostgreSql