Strata.SqlTools.PostgreSQL
A PostgreSQL dialect-specific implementation of the QueryBreakdown SQL parsing and generation framework. This project extends the core SQL Tools functionality with PostgreSQL-native syntax support, including positional parameters ($1, $2, etc.), double-quoted identifiers, LIMIT/OFFSET clauses, and RETURNING clauses.
Overview
Strata.SqlTools.PostgreSQL extends the SQL Tools framework to provide PostgreSQL-specific functionality while maintaining compatibility with the core QueryBreakdown patterns used throughout the sql-utilities ecosystem. It's built on top of the SqlServer implementation and follows the same architectural patterns as the Snowflake dialect module.
Features
- Positional Parameters: Native support for PostgreSQL positional parameters ($1, $2, ..., $N)
- Double-Quoted Identifiers: Case-sensitive identifier handling using PostgreSQL's double-quote syntax
- LIMIT and OFFSET: Full support for PostgreSQL's LIMIT/OFFSET pagination syntax
- RETURNING Clause: DML statement result retrieval via RETURNING
- CTE Support: Common Table Expressions (WITH clause) for recursive and non-recursive queries
- Parameter Normalization: Automatic conversion of @name and :name parameter styles to positional format
- Batch Operations: Multi-statement batch processing with semicolon separation
Installation
Add the package to your project:
dotnet add package Strata.SqlTools.PostgreSQL
Or via NuGet Package Manager:
Install-Package Strata.SqlTools.PostgreSQL
Quick Start
Basic Query Parsing
using Strata.SqlTools.Breakdowns.PostgreSql;
// Parse an existing PostgreSQL query
var sql = "SELECT id, name FROM users WHERE status = $1 ORDER BY name DESC LIMIT 10";
var queryBreakdown = QueryBreakdown.Parse(sql, isMicrosoftSql: false);
// Access individual clauses
Console.WriteLine($"Select: {queryBreakdown.SelectClause.Clause}");
Console.WriteLine($"From: {queryBreakdown.FromClause.Clause}");
Console.WriteLine($"Where: {queryBreakdown.WhereClause.Clause}");
Console.WriteLine($"Limit: {queryBreakdown.LimitClause.Clause}");
Building Queries Programmatically
var query = new QueryBreakdown("id, name, email", "users");
query.WhereClause.Clause = "status = $1 AND created_at > $2";
query.OrderByClause.Clause = "created_at DESC";
query.LimitClause.Clause = "50";
query.OffsetClause.Clause = "0";
// Add parameters by name (automatically converted to positional $1, $2, etc.)
query.AddParameter("status", "active");
query.AddParameter("startDate", new DateTime(2025, 1, 1));
// Generate PostgreSQL SQL
var generatedSql = query.GetSql();
Console.WriteLine(generatedSql);
Working with CTEs (Common Table Expressions)
// Create main query
var mainQuery = new QueryBreakdown("*", "recent_users");
// Create CTE
var cteQuery = new QueryBreakdown(
"id, name, created_at",
"users"
);
cteQuery.WhereClause.Clause = "created_at > NOW() - INTERVAL '30 days'";
cteQuery.OrderByClause.Clause = "created_at DESC";
// Add CTE to main query
mainQuery.AddWithClause("recent_users", cteQuery);
// Generate SQL
var sql = mainQuery.GetSql();
Batch Statement Processing
var collection = new QueryBreakdownCollection();
// Add multiple queries to batch
var query1 = new QueryBreakdown("id, name", "users");
query1.WhereClause.Clause = "active = true";
collection.Add(query1);
var query2 = new QueryBreakdown("id, amount", "orders");
query2.OrderByClause.Clause = "created_at DESC";
query2.LimitClause.Clause = "100";
collection.Add(query2);
// Generate batch SQL with semicolon separation
var batchSql = collection.GetPostgreSqlBatch();
// Result: "SELECT \"id\", \"name\" FROM \"users\" WHERE active = true; SELECT \"id\", \"amount\" FROM \"orders\" ORDER BY created_at DESC LIMIT 100;"
Parameter Handling
PostgreSQL uses positional parameters ($1, $2, etc.) instead of named parameters. The PostgreSQL dialect automatically converts named parameters to positional format:
var query = new QueryBreakdown("id, name", "users");
// Add parameters by name
query.AddParameter("userId", 123);
query.AddParameter("status", "active");
// Parameters are tracked internally with both formats
// For compatibility: query.Parameters["$1"] exists for execution
// For readability: query.Parameters["@userId"] existed during construction
Identifiers and Case Sensitivity
PostgreSQL treats unquoted identifiers as case-insensitive (converts to lowercase), but double-quoted identifiers are case-sensitive:
// Unquoted - case insensitive
var query1 = new QueryBreakdown("ID, NAME", "USERS");
// Results in: SELECT "id", "name" FROM "users"
// Double-quoted - case sensitive
var query2 = new QueryBreakdown("\"UserId\", \"UserName\"", "\"UserTable\"");
// Results in: SELECT "UserId", "UserName" FROM "UserTable"
LIMIT and OFFSET
Use LIMIT for row count restrictions and OFFSET for pagination:
var query = new QueryBreakdown("id, name", "users");
query.OrderByClause.Clause = "id ASC";
query.LimitClause.Clause = "25";
query.OffsetClause.Clause = "100";
var sql = query.GetSql();
// Results in: SELECT "id", "name" FROM "users" ORDER BY "id" ASC LIMIT 25 OFFSET 100
RETURNING Clause
Use RETURNING with DML statements (INSERT, UPDATE, DELETE) to retrieve affected rows:
var query = new QueryBreakdown("id", "users");
query.ReturningClause.Clause = "id, name, email";
// Note: RETURNING is context-specific and works with INSERT/UPDATE/DELETE constructs
Identifiers with Special Characters
PostgreSQL requires double-quoting for identifiers with spaces or special characters:
var query = new QueryBreakdown("\"Order ID\", \"Customer Name\"", "\"Sales Data\"");
query.WhereClause.Clause = "\"Order Status\" = $1";
var sql = query.GetSql();
// Results in: SELECT "Order ID", "Customer Name" FROM "Sales Data" WHERE "Order Status" = $1
Architecture
The PostgreSQL implementation follows the same architecture as other SQL Tools dialect modules:
- QueryBreakdown: Main class for parsing and generating PostgreSQL SQL
- QueryBreakdownCollection: Batch processing for multiple queries
- CommandVisitor: Converts SQL expressions to PostgreSQL-specific strings
- StatementParser: PostgreSQL-specific SQL parsing logic
- StatementExpressionParser: Expression-level parsing
- StatementReader: Token-level SQL reading with PostgreSQL syntax rules
- ExpressionFactory: Abstract factory for building filter expressions
Conversion from Other Dialects
When migrating from SQL Server (@parameter syntax) to PostgreSQL ($N syntax):
// SQL Server style
var sqlServerQueryBreakdown = QueryBreakdown.Parse(
"SELECT id FROM users WHERE status = @status",
isMicrosoftSql: true
);
// PostgreSQL automatically normalizes to positional parameters
var postgreSqlQuery = QueryBreakdown.Parse(
"SELECT id FROM users WHERE status = $1",
isMicrosoftSql: false
);
Testing
The project includes comprehensive test coverage:
- QueryBreakdownTests: Core parsing and SQL generation
- QueryBreakdownCollectionTests: Batch processing functionality
- StatementReaderTests: Token-level parsing
- StatementExpressionParserTests: Expression parsing
Run tests with:
dotnet test Strata.SqlTools.PostgreSql.Tests
Dependencies
- .NET 8.0 or later: Required for async/await and modern C# features
- Strata.SqlTools (Core): Base SQL Tools framework
- Strata.SqlTools.SqlServer: Base dialect implementation inheritance
Compatibility
- PostgreSQL 10.0 and later
- Supports all standard SQL and PostgreSQL-specific syntax
- Compatible with Entity Framework Core 8.0+ for data access integration
Performance Considerations
- Statement parsing is optimized for typical query sizes
- Parameter tracking uses Dictionary<string, object> for O(1) lookups
- Batch operations use StringBuilder for efficient string concatenation
- Expression parsing uses lazy evaluation where possible
Known Limitations
- Recursive CTEs require explicit RECURSIVE keyword (must be added manually or via clause)
- Custom PostgreSQL types (@type syntax) are not explicitly handled
- Window functions with OVER clause may require manual formatting
- Schema-qualified table names (schema.table) are treated as single identifiers
Contributing
Contributions are welcome! Please ensure:
- All tests pass
- Code follows the existing architectural patterns
- New features include corresponding test cases
- Documentation is updated
License
See LICENSE.txt in the repository root.
See Also
- Strata.SqlTools - Core SQL Tools framework
- Strata.SqlTools.SqlServer - SQL Server dialect
- Strata.SqlTools.Snowflake - Snowflake dialect
- QueryBreakdown Usage - Framework documentation