gosbee is a Go SQL Builder — a powerful SQL AST (Abstract Syntax Tree) library inspired by Ruby's Arel.
Build SQL queries programmatically using composable, type-safe Go code. Instead of concatenating strings, you construct a tree of nodes that is only converted to SQL at the last moment by a database-specific visitor. This gives you semantic understanding of your queries, dialect-agnostic query building, and the ability to transform queries through middleware before SQL generation.
- 🌳 AST-based query building — queries are trees, not strings
- 🗄️ Multi-dialect support — PostgreSQL, MySQL, SQLite via the Visitor pattern
- 🔗 Composable — subqueries, complex JOINs, CTEs, and set operations
- 🔌 Plugin system — transform the AST with middleware (access control, soft-delete, multi-tenancy)
- ⚡ Late binding — SQL is only generated when you call
ToSQL() - 🛡️ Parameterised queries — built-in SQL injection protection
- 💻 Interactive REPL — explore and test queries against live databases
- ✅ 100% Ruby Arel feature parity — all core SQL features supported
go get github.com/bawdo/gosbeegosbee supports two import styles to suit different preferences:
Use the convenience package for a cleaner import and shorter function names:
import "github.com/bawdo/gosbee"
users := gosbee.NewTable("users")
query := gosbee.NewSelect(users).
Select(users.Col("id"), users.Col("name")).
Where(users.Col("active").Eq(gosbee.BindParam(true)))
visitor := gosbee.NewPostgresVisitor()
sql, params, _ := query.ToSQL(visitor)Import subpackages directly for full control and access to advanced features:
import (
"github.com/bawdo/gosbee/managers"
"github.com/bawdo/gosbee/nodes"
"github.com/bawdo/gosbee/visitors"
)
users := nodes.NewTable("users")
query := managers.NewSelectManager(users).
Select(users.Col("id"), users.Col("name")).
Where(users.Col("active").Eq(nodes.BindParam(true)))
visitor := visitors.NewPostgresVisitor()
sql, params, _ := query.ToSQL(visitor)You can also mix both approaches — use the convenience package for common operations and import subpackages for advanced features like window functions or custom node types.
package main
import (
"context"
"fmt"
"log"
"github.com/bawdo/gosbee"
"github.com/jackc/pgx/v5"
)
func main() {
// Connect to PostgreSQL
conn, err := pgx.Connect(context.Background(), "postgres://user:pass@localhost/dbname")
if err != nil {
log.Fatal(err)
}
defer conn.Close(context.Background())
// Define tables
users := gosbee.NewTable("users")
posts := gosbee.NewTable("posts")
// Build a query with parameterised values
query := gosbee.NewSelect(users).
Select(users.Col("id"), users.Col("name"), posts.Col("title")).
Join(posts).On(users.Col("id").Eq(posts.Col("user_id"))).
Where(users.Col("active").Eq(gosbee.BindParam(true))).
Order(posts.Col("created_at").Desc()).
Limit(gosbee.BindParam(10))
// Generate SQL for PostgreSQL (params enabled by default)
visitor := gosbee.NewPostgresVisitor()
sql, params, err := query.ToSQL(visitor)
if err != nil {
panic(err)
}
fmt.Println(sql)
// SELECT "users"."id", "users"."name", "posts"."title"
// FROM "users"
// INNER JOIN "posts" ON "users"."id" = "posts"."user_id"
// WHERE "users"."active" = $1
// ORDER BY "posts"."created_at" DESC
// LIMIT $2
fmt.Println(params) // []any{true, 10}
// Execute the query with parameters (safe from SQL injection)
rows, err := conn.Query(context.Background(), sql, params...)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// Process results
for rows.Next() {
var id int
var name, title string
if err := rows.Scan(&id, &name, &title); err != nil {
log.Fatal(err)
}
fmt.Printf("User %d: %s - Post: %s\n", id, name, title)
}
}Protect against SQL injection with parameterised queries:
package main
import (
"context"
"fmt"
"log"
"github.com/bawdo/gosbee"
"github.com/jackc/pgx/v5"
)
func main() {
// Connect to PostgreSQL
conn, err := pgx.Connect(context.Background(), "postgres://user:pass@localhost/dbname")
if err != nil {
log.Fatal(err)
}
defer conn.Close(context.Background())
users := gosbee.NewTable("users")
// Build parameterised query using BindParam
query := gosbee.NewSelect(users).
Select(users.Col("id"), users.Col("name"), users.Col("age")).
Where(users.Col("name").Eq(gosbee.BindParam("Alice"))).
Where(users.Col("age").Gt(gosbee.BindParam(18)))
// Parameterisation is enabled by default
visitor := gosbee.NewPostgresVisitor()
sql, params, err := query.ToSQL(visitor)
if err != nil {
log.Fatal(err)
}
fmt.Println(sql)
// SELECT "users"."id", "users"."name", "users"."age"
// FROM "users"
// WHERE "users"."name" = $1 AND "users"."age" > $2
fmt.Println(params) // []any{"Alice", 18}
// Execute with parameters (safe from SQL injection)
rows, err := conn.Query(context.Background(), sql, params...)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// Process results
for rows.Next() {
var id, age int
var name string
if err := rows.Scan(&id, &name, &age); err != nil {
log.Fatal(err)
}
fmt.Printf("User %d: %s (age %d)\n", id, name, age)
}
}// Disable parameterisation (literals instead of placeholders)
visitor := gosbee.NewPostgresVisitor(gosbee.WithoutParams())
sql, _, err := query.ToSQL(visitor)
// SELECT "users"."id", "users"."name", "users"."age"
// FROM "users"
// WHERE "users"."name" = 'Alice' AND "users"."age" > 18
// (Note: No $1, $2 placeholders - values are inlined)Transform queries with plugins before SQL generation:
import (
"github.com/bawdo/gosbee"
"github.com/bawdo/gosbee/plugins/softdelete"
)
users := gosbee.NewTable("users")
query := gosbee.NewSelect(users).
Select(users.Star()).
Use(softdelete.New())
visitor := gosbee.NewPostgresVisitor()
sql, _, _ := query.ToSQL(visitor)
// SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULLgosbee follows a layered architecture inspired by Ruby's Arel:
| Layer | Package | Purpose |
|---|---|---|
| Nodes | nodes/ |
AST building blocks — tables, attributes, predicates, literals |
| Managers | managers/ |
Fluent DSL for composing queries (SELECT, INSERT, UPDATE, DELETE) |
| Visitors | visitors/ |
Render the AST into dialect-specific SQL |
| Plugins | plugins/ |
Transform the AST before SQL generation (optional) |
Manager → [Plugin 1] → [Plugin 2] → ... → Visitor → SQL string
Transform Transform Accept
This architecture allows you to:
- Build queries once, render for different databases
- Transform queries with middleware (access control, logging, etc.)
- Compose complex queries from smaller pieces
- Introspect and manipulate queries programmatically
gosbee supports the full range of modern/ANSI SQL features:
- SELECT, INSERT, UPDATE, DELETE
- Projections (SELECT columns)
- WHERE conditions with predicates (=, !=, >, <, LIKE, IN, BETWEEN, etc.)
- JOINs (INNER, LEFT/RIGHT/FULL OUTER, CROSS, LATERAL)
- GROUP BY / HAVING
- ORDER BY with NULLS FIRST/LAST
- LIMIT / OFFSET
- DISTINCT / DISTINCT ON
- Window functions (ROW_NUMBER, RANK, LAG, LEAD, etc.) with frames
- Common Table Expressions (WITH / WITH RECURSIVE)
- Set operations (UNION, INTERSECT, EXCEPT)
- Subqueries and table aliases
- Aggregate functions (COUNT, SUM, AVG, MIN, MAX)
- Named functions (COALESCE, CAST, LOWER, UPPER, etc.)
- CASE expressions (searched and simple)
- Advanced grouping (CUBE, ROLLUP, GROUPING SETS)
- EXISTS / NOT EXISTS
- Query comments and optimizer hints
- Locking clauses (FOR UPDATE, FOR SHARE, SKIP LOCKED)
- Multi-row INSERT
- INSERT FROM SELECT
- UPSERT (ON CONFLICT DO NOTHING / DO UPDATE)
- RETURNING clause (PostgreSQL, SQLite)
Built-in support for three major databases:
| Dialect | Visitor | Identifier Quoting | Placeholders |
|---|---|---|---|
| PostgreSQL | NewPostgresVisitor() |
"table"."column" |
$1, $2, $3 |
| MySQL | NewMySQLVisitor() |
`table`.`column` |
?, ?, ? |
| SQLite | NewSQLiteVisitor() |
"table"."column" |
?, ?, ? |
Dialect-specific features (DISTINCT ON, LATERAL JOIN, RETURNING, etc.) are handled automatically by the visitors.
See the Visitor Dialects guide for details.
gosbee includes an interactive shell for exploring queries:
# Install
go install github.com/bawdo/gosbee/cmd/repl@latest
# Run
export GOSBEE_ENGINE=postgres
export DATABASE_URL="postgres://user:pass@localhost/mydb"
replThe REPL provides:
- Tab completion for tables and columns
- Live query execution
- Plugin support
- DOT/Graphviz visualisation
- Expression evaluation
- Command history
See the REPL guide for the full feature set.
- Getting Started — comprehensive API guide with examples
- Visitor Dialects — SQL dialect selection and parameterisation
- Using Plugins — registering and using plugins
- Contributing Guide — how to contribute to the project
- Architecture — core design patterns and visitor pattern
- Writing Plugins — plugin development guide
- Plugin System — transformer architecture
- Soft Delete Plugin — soft-delete filtering (proof of concept)
- OPA Plugin — Open Policy Agent integration (proof of concept)
This project is heavily inspired by Ruby's Arel library, which pioneered the SQL AST builder pattern. Arel was created by Bryan Helmkamp and has been the foundation of ActiveRecord's query interface since Rails 3.0.
gosbee aims to bring Arel's elegant design to the Go ecosystem with:
- Idiomatic Go patterns and conventions
- Strong typing throughout
- Zero external dependencies in the core library
- Extended plugin architecture for middleware
We're grateful to the Arel maintainers and the Ruby community for proving out this approach to SQL query building.
gosbee has achieved 100% feature parity with Ruby Arel's core SQL features. The library is under active development with ~1096 tests covering all major functionality.
Current focus areas:
- Performance optimisation
- Additional dialect support
- Production-ready plugin implementations
- Extended REPL features
We welcome contributions! Whether you're fixing a bug, improving documentation, or adding a new feature, please see our Contributing Guide for guidelines.
Common ways to contribute:
- 🐛 Report bugs or suggest features via issues
- 📖 Improve documentation and examples
- 🔌 Write new plugins for common use cases
- 🗄️ Add support for additional SQL dialects
- ✅ Add tests for edge cases
This project is licensed under the MIT License - see the LICENSE file for details.
Copyright (c) 2026 Keith Bawden
- Arel — The Ruby library that inspired this project
- squirrel — Another Go SQL builder (string-based, not AST-based)
- goqu — SQL builder with dialect support
- Repository: https://github.com/bawdo/gosbee
- Documentation: docs/guide/getting-started.md
- Issues: https://github.com/bawdo/gosbee/issues
