A modern, type-safe query builder for Go π
Combining the ergonomics of GORM, the type-safety of SQLC, and the performance of raw SQL
- π― Type-Safe: Compile-time type checking with Go generics
- β‘ Minimal Reflection Overhead: Type information cached after first use, subsequent queries use cached metadata
- π High Performance: Zero-allocation string building with
strings.Builder - ποΈ Multi-Database: PostgreSQL, MySQL, SQLite support
- π§ Full SQL Support: SELECT, INSERT, UPDATE, DELETE, JOIN, Transactions
- β±οΈ Context Support: Built-in timeout and cancellation support
- π‘οΈ SQL Injection Prevention: Parameterized queries and operator whitelisting
- π¦ Zero Dependencies: Uses only standard library (except database drivers)
- π¨ Beautiful SQL Debugging: Formatted query logging with timing and parameter substitution
- ποΈ Query Preview: See generated SQL without executing queries
- π Query Composition: Reusable query fragments for DRY code
- π Subquery Support: Powerful WHERE conditions with subqueries
- β‘ EXISTS Queries: Efficient existence checks
go get github.com/alicanli1995/sqlbladeπ¦ Package Documentation: pkg.go.dev/github.com/alicanli1995/sqlblade
package main
import (
"context"
"database/sql"
"time"
_ "github.com/lib/pq" // PostgreSQL driver
"github.com/alicanli1995/sqlblade/sqlblade"
"github.com/alicanli1995/sqlblade/sqlblade/dialect"
)
type User struct {
ID int `db:"id"`
Email string `db:"email"`
Name string `db:"name"`
Age int `db:"age"`
CreatedAt time.Time `db:"created_at"`
}
func main() {
db, _ := sql.Open("postgres", "postgres://user:pass@localhost/dbname")
ctx := context.Background()
// Simple SELECT query
users, err := sqlblade.Query[User](db).
Where("age", ">", 18).
Where("status", "=", "active").
OrderBy("created_at", dialect.DESC).
Limit(10).
Execute(ctx)
// INSERT
result, err := sqlblade.Insert(db, user).Execute(ctx)
// UPDATE
result, err := sqlblade.Update[User](db).
Set("status", "inactive").
Where("id", "=", userID).
Execute(ctx)
// Transaction
err := sqlblade.WithTransaction(db, func(tx *sql.Tx) error {
_, err := sqlblade.InsertTx(tx, user).Execute(ctx)
return err
})See examples/ directory for complete examples:
- Basic Examples - Complete examples including SELECT, INSERT, UPDATE, DELETE, JOIN, Transactions, Query Preview, Fragments, Subqueries, and EXISTS
- MySQL Examples
- PostgreSQL Examples
- Debug Features - Advanced features demonstration with SQL debugging
SQLBlade is ideal for:
- High-performance applications requiring fast INSERT and COUNT operations
- Memory-constrained environments where low memory footprint is critical
- Type-safe codebases where compile-time type checking is preferred
- Microservices needing lightweight dependencies (zero external deps)
- Applications with frequent aggregate queries (COUNT, SUM, AVG, etc.)
- Projects transitioning from raw SQL wanting better ergonomics without performance loss
- API servers handling high-throughput data operations
- Heavy SELECT workloads where GORM's prepared statement cache provides significant advantage
- Very complex ORM features needed (automatic migrations, relations, etc.) - consider GORM
- Raw SQL is preferred - stdlib or sqlx might be simpler
- Dynamic query building with unpredictable patterns (cache benefits diminish)
-
REST APIs with CRUD operations
// Fast INSERT, UPDATE, COUNT with type safety users, _ := sqlblade.Query[User](db).Where("active", "=", true).Execute(ctx)
-
Data processing pipelines
// Efficient batch operations with low memory overhead sqlblade.InsertBatch(db, users).Execute(ctx)
-
Analytics and reporting
// Fast aggregate queries count, _ := sqlblade.Query[Order](db).Where("date", ">", startDate).Count(ctx)
-
High-concurrency services
// Low memory footprint reduces GC pressure sqlblade.PreparedStatementCache(db) // Enable for repeated queries
Performance Profile:
- π INSERT: Fastest among all libraries
- π COUNT: Fastest among all libraries
- β Memory: Lowest memory usage
- β Allocations: Fewest allocations
- β‘ SELECT: Competitive (GORM faster but uses 3x more memory)
| Database | Driver | Status |
|---|---|---|
| PostgreSQL | github.com/lib/pq |
β Full Support |
| MySQL | github.com/go-sql-driver/mysql |
β Full Support |
| SQLite | github.com/mattn/go-sqlite3 |
β Full Support |
Query[T](db)- Create a SELECT query builderWhere(column, operator, value)- Add WHERE condition (AND)OrWhere(column, operator, value)- Add WHERE condition (OR)Join(table, condition)- INNER JOINLeftJoin(table, condition)- LEFT JOINSelect(columns...)- Specify columns to selectOrderBy(column, direction)- Add ORDER BY clauseLimit(n)/Offset(n)- Set LIMIT and OFFSETExecute(ctx)- Execute query and return resultsCount(ctx)/Sum(ctx, col)/Avg(ctx, col)/Min(ctx, col)/Max(ctx, col)- Aggregate functions
Insert(db, value)/InsertBatch(db, values)- INSERT operationsUpdate[T](db)- UPDATE operationsDelete[T](db)- DELETE operationsReturning(columns...)- Specify RETURNING columns (PostgreSQL)
WithTransaction(db, fn)- Execute operations in a transactionWithTransactionContext(ctx, db, fn)- Transaction with context
Raw[T](db, query, args...)- Execute raw SQL queries
EnableDebug()- Enable beautiful SQL query loggingConfigureDebug(func)- Configure debug settingsPreview()- Preview SQL without executingSQL()/SQLWithArgs()- Get generated SQL stringPrettyPrint()- Print formatted query
NewQueryFragment()- Create reusable query fragmentsApply(fragment)- Apply fragment to query builderNewSubquery(builder)- Create subquery from builderWhereSubquery()/OrWhereSubquery()- Use subqueries in WHEREExists()/NotExists()- Check existence efficiently
SQLBlade includes a beautiful, formatted SQL logger that makes debugging queries a joy:
// Enable debugging globally
sqlblade.EnableDebug()
// Configure debugger (optional)
sqlblade.ConfigureDebug(func(qd *sqlblade.QueryDebugger) {
qd.ShowArgs(true) // Show query parameters
qd.ShowTiming(true) // Show execution time
qd.SetSlowQueryThreshold(50 * time.Millisecond) // Warn on slow queries
qd.IndentSQL(true) // Pretty format SQL
})
// Now all queries are automatically logged!
users, _ := sqlblade.Query[User](db).
Where("age", ">", 18).
Execute(ctx)Output:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SQL Query Debug - 2024-01-15 10:30:45.123
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Operation: SELECT
Table: users
Duration: 2.34ms
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SQL:
SELECT * FROM users WHERE age > $1
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Parameters:
$1 = 18 (int)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Preview generated SQL without executing:
query := sqlblade.Query[User](db).
Where("email", "LIKE", "%@example.com%").
Join("profiles", "profiles.user_id = users.id")
// See the SQL
fmt.Println(query.Preview().SQL())
// Output: SELECT * FROM users JOIN profiles ON profiles.user_id = users.id WHERE email LIKE $1
// See SQL with substituted arguments
fmt.Println(query.Preview().SQLWithArgs())
// Output: SELECT * FROM users JOIN profiles ON profiles.user_id = users.id WHERE email LIKE '%@example.com%'
// Pretty print
query.Preview().PrettyPrint()Create reusable query fragments to avoid repetition:
// Create a reusable fragment
activeUsersFragment := sqlblade.NewQueryFragment().
Where("status", "=", "active").
Where("email_verified", "=", true).
OrderBy("created_at", dialect.DESC)
// Apply to multiple queries
recentActive, _ := sqlblade.Query[User](db).
Apply(activeUsersFragment).
Limit(10).
Execute(ctx)
allActive, _ := sqlblade.Query[User](db).
Apply(activeUsersFragment).
Execute(ctx)Use subqueries in WHERE clauses for powerful queries:
// Find users who have placed orders
usersWithOrders, _ := sqlblade.Query[User](db).
WhereSubquery("id", "IN", sqlblade.NewSubquery(
sqlblade.Query[struct {
UserID int `db:"user_id"`
}](db).
Select("user_id").
Where("status", "=", "completed"),
)).
Execute(ctx)Efficiently check existence:
// Check if any active users exist
hasActiveUsers, _ := sqlblade.Query[User](db).
Where("status", "=", "active").
Limit(1).
Exists(ctx)
if hasActiveUsers {
fmt.Println("There are active users!")
}
// Or use NotExists
hasNoAdmins, _ := sqlblade.Query[User](db).
Where("role", "=", "admin").
NotExists(ctx)SQLBlade uses Go generics to provide compile-time type safety:
// β
Type-safe - compile-time checking
users, err := sqlblade.Query[User](db).Execute(ctx)
// β Compile error if User type doesn't exist
users, err := sqlblade.Query[NonExistentType](db).Execute(ctx)All queries use parameterized statements with operator whitelisting:
// β
Safe - parameterized query
users, err := sqlblade.Query[User](db).
Where("email", "=", userInput).
Execute(ctx)
// β
Safe - operators are whitelisted
users, err := sqlblade.Query[User](db).
Where("age", ">", 18).
Execute(ctx)SQLBlade is designed for performance with minimal overhead:
- Zero-allocation string building using
strings.Builderwith pre-allocated capacity - Efficient struct scanning with reflection caching and column mapping cache
- Prepared statement caching for optimal query performance
- Minimal reflection overhead with aggressive caching
- Optimized memory allocation patterns
Performance Optimizations:
-
Prepared Statement Cache: Reuses prepared statements for identical queries
// Enable prepared statement cache (recommended for production) sqlblade.PreparedStatementCache(db)
-
Column Mapping Cache: Caches column-to-field mappings for faster scanning
-
Pre-allocated Buffers: SQL string builders use pre-allocated capacity
-
Optimized Reflection: Struct info cached, column maps cached
-
Efficient Memory Patterns: Pre-allocated slices where possible
The benchmark suite includes comparisons with GORM, sqlx, and stdlib. To run all benchmarks:
cd benchmarks
./run_benchmarks.shThis will automatically:
- Start PostgreSQL via Docker Compose
- Set up test data
- Run all benchmark tests (SQLBlade, GORM, sqlx, stdlib)
- Display performance results
Individual benchmark commands:
# Run all benchmarks
go test -bench=. -benchmem -benchtime=3s .
# Compare SQLBlade with GORM
go test -bench="BenchmarkSQLBlade|BenchmarkGORM" -benchmem -benchtime=3s .
# Compare SQLBlade with sqlx
go test -bench="BenchmarkSQLBlade|BenchmarkSQLX" -benchmem -benchtime=3s .
# Compare SQLBlade with stdlib
go test -bench="BenchmarkSQLBlade|BenchmarkStdlib" -benchmem -benchtime=3s .
# Run specific operation benchmarks (e.g., SELECT)
go test -bench=".*Select" -benchmem -benchtime=3s .
# Generate comparison report
go test -bench=. -benchmem -benchtime=5s . | tee benchmark_results.txtSQLBlade aims to be:
- Faster than GORM: No reflection overhead in query building, efficient struct scanning
- Comparable to sqlx: Similar performance with better type safety
- Close to stdlib: Minimal abstraction overhead
Key differences from other libraries:
| Feature | SQLBlade | GORM | sqlx | stdlib |
|---|---|---|---|---|
| Type Safety | β Compile-time | β Runtime | β Manual | |
| Performance | β‘ High | π Slower | β‘ High | β‘ Highest |
| API Ergonomics | β Excellent | β Excellent | β Verbose | |
| Reflection Usage | β‘ Minimal | π΄ Heavy | β‘ Minimal | β None |
| Dependencies | β Zero | π΄ Many | β Zero |
The benchmark suite compares SQLBlade with GORM, sqlx, and stdlib:
SELECT Operations:
BenchmarkSQLBlade_Select 7832 449324 ns/op 3811 B/op 82 allocs/op
BenchmarkGORM_Select 7041 460975 ns/op 7254 B/op 156 allocs/op
BenchmarkSQLX_Select 4440 935076 ns/op 4248 B/op 92 allocs/op
BenchmarkStdlib_Select 4149 929181 ns/op 3728 B/op 77 allocs/op
Complex SELECT:
BenchmarkSQLBlade_SelectComplex 6044 532582 ns/op 4364 B/op 99 allocs/op
BenchmarkGORM_SelectComplex 8848 506002 ns/op 8199 B/op 177 allocs/op
BenchmarkSQLX_SelectComplex 4233 934804 ns/op 4504 B/op 94 allocs/op
BenchmarkStdlib_SelectComplex 4429 838249 ns/op 3936 B/op 79 allocs/op
INSERT Operations:
BenchmarkSQLBlade_Insert 3766 949162 ns/op 2216 B/op 32 allocs/op
BenchmarkGORM_Insert 2025 1748774 ns/op 7587 B/op 102 allocs/op
BenchmarkSQLX_Insert 4262 788573 ns/op 976 B/op 21 allocs/op
BenchmarkStdlib_Insert 5094 795641 ns/op 543 B/op 12 allocs/op
UPDATE Operations:
BenchmarkSQLBlade_Update 6362 482381 ns/op 1320 B/op 27 allocs/op
BenchmarkGORM_Update 2060 1709774 ns/op 7276 B/op 78 allocs/op
BenchmarkSQLX_Update 4754 868182 ns/op 320 B/op 8 allocs/op
BenchmarkStdlib_Update 5385 785977 ns/op 320 B/op 8 allocs/op
COUNT Operations:
BenchmarkSQLBlade_Count 715 5390355 ns/op 1256 B/op 33 allocs/op
BenchmarkGORM_Count 756 4739776 ns/op 3992 B/op 48 allocs/op
BenchmarkSQLX_Count 631 5584813 ns/op 792 B/op 22 allocs/op
BenchmarkStdlib_Count 600 5872580 ns/op 712 B/op 19 allocs/op
Performance Highlights:
- β ~52% faster than sqlx for SELECT queries (449Β΅s vs 935Β΅s)
- β ~47% less memory than GORM for SELECT (3811 B vs 7254 B)
- β ~47% fewer allocations than GORM (82 allocs vs 156 allocs)
- β ~52% faster than stdlib for SELECT queries (449Β΅s vs 929Β΅s)
- β ~46% faster than GORM for INSERT (949Β΅s vs 1748Β΅s)
- β ~20% faster than sqlx for INSERT (949Β΅s vs 788Β΅s)
- β ~72% faster than GORM for UPDATE (482Β΅s vs 1709Β΅s)
- β ~44% faster than sqlx for UPDATE (482Β΅s vs 868Β΅s)
- β ~39% faster than stdlib for UPDATE (482Β΅s vs 785Β΅s)
- β Consistent low memory footprint across all operations
Performance Notes:
- SQLBlade SELECT is ~2.5% faster than GORM (449Β΅s vs 460Β΅s) while using 47% less memory (3811 B vs 7254 B)
- SQLBlade INSERT is 46% faster than GORM (949Β΅s vs 1748Β΅s) with 71% less memory (2216 B vs 7587 B)
- SQLBlade UPDATE is 72% faster than GORM (482Β΅s vs 1709Β΅s) and 44% faster than sqlx (482Β΅s vs 868Β΅s)
- SELECT performance ranking: SQLBlade (449Β΅s) β GORM (460Β΅s) > stdlib (929Β΅s) β sqlx (935Β΅s), but SQLBlade uses least memory
- COUNT operations show variance (benchmark-dependent), but SQLBlade maintains lowest memory usage
- Prepared statement cache provides benefits for repeated queries after warm-up period
- Optimizations provide ~13% faster SELECT, ~21% faster INSERT, and ~36% faster UPDATE compared to previous benchmarks
Define your models with db tags:
type User struct {
ID int `db:"id"`
Email string `db:"email"`
Name string `db:"name"`
CreatedAt time.Time `db:"created_at"`
}This project is licensed under the MIT License - see the LICENSE file for details.
Made with β€οΈ using Go