gosbee

package module
v0.0.0-...-4e74fc4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 21, 2026 License: MIT Imports: 3 Imported by: 0

README

gosbee

CI codecov Go Report Card GoDoc

gosbee with plugins

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.

Features

  • 🌳 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

Quick Start

Installation
go get github.com/bawdo/gosbee
Import Styles

gosbee 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)
Explicit imports (Advanced usage)

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.

Basic Usage
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)
    }
}
Parameterised Queries

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)
    }
}

⚠️ WARNING: Disabling parameterisation removes SQL injection protection. Only use for debugging or when all values are trusted. Production code should NEVER use this option.

// 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)
Using Plugins

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 NULL

Architecture

gosbee 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

Supported SQL Features

gosbee supports the full range of modern/ANSI SQL features:

Queries
  • 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
Advanced Features
  • 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)
DML Operations
  • Multi-row INSERT
  • INSERT FROM SELECT
  • UPSERT (ON CONFLICT DO NOTHING / DO UPDATE)
  • RETURNING clause (PostgreSQL, SQLite)

SQL Dialects

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.

Interactive REPL

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"
repl

The 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.

Documentation

For Library Users
For Contributors
Plugin Documentation

Acknowledgements

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.

Project Status

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

Contributing

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

License

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

Documentation

Overview

Package gosbee provides a fluent SQL query builder for Go.

This package re-exports commonly used types and functions from subpackages for convenience. Advanced users can import subpackages directly:

  • github.com/bawdo/gosbee/managers (query builders)
  • github.com/bawdo/gosbee/nodes (AST nodes)
  • github.com/bawdo/gosbee/visitors (SQL generation)
  • github.com/bawdo/gosbee/plugins (query transformers)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Avg

func Avg(expr nodes.Node) *nodes.AggregateNode

Avg creates an AVG(expr) aggregate.

func BindParam

func BindParam(value any) *nodes.BindParamNode

BindParam creates a parameterised placeholder (e.g., $1, ?).

func Count

func Count(expr nodes.Node) *nodes.AggregateNode

Count creates a COUNT(expr) aggregate.

func CountDistinct

func CountDistinct(expr nodes.Node) *nodes.AggregateNode

CountDistinct creates a COUNT(DISTINCT expr) aggregate.

func Literal

func Literal(value any) nodes.Node

Literal creates a SQL literal node (e.g., numbers, strings).

func Max

func Max(expr nodes.Node) *nodes.AggregateNode

Max creates a MAX(expr) aggregate.

func Min

func Min(expr nodes.Node) *nodes.AggregateNode

Min creates a MIN(expr) aggregate.

func NewDelete

func NewDelete(from nodes.Node) *managers.DeleteManager

NewDelete creates a new DeleteManager for deleting from the given table.

func NewInsert

func NewInsert(into nodes.Node) *managers.InsertManager

NewInsert creates a new InsertManager for inserting into the given table.

func NewMySQLVisitor

func NewMySQLVisitor(opts ...visitors.Option) *visitors.MySQLVisitor

NewMySQLVisitor creates a new MySQL visitor.

func NewPostgresVisitor

func NewPostgresVisitor(opts ...visitors.Option) *visitors.PostgresVisitor

NewPostgresVisitor creates a new PostgreSQL visitor.

func NewSQLiteVisitor

func NewSQLiteVisitor(opts ...visitors.Option) *visitors.SQLiteVisitor

NewSQLiteVisitor creates a new SQLite visitor.

func NewSelect

func NewSelect(from nodes.Node) *managers.SelectManager

NewSelect creates a new SelectManager with the given table as FROM.

func NewTable

func NewTable(name string) *nodes.Table

NewTable creates a new table reference.

func NewUpdate

func NewUpdate(table nodes.Node) *managers.UpdateManager

NewUpdate creates a new UpdateManager for updating the given table.

func Star

func Star() *nodes.StarNode

Star creates an unqualified star (*) for SELECT *.

func Sum

func Sum(expr nodes.Node) *nodes.AggregateNode

Sum creates a SUM(expr) aggregate.

func WithParams

func WithParams() visitors.Option

WithParams enables parameterisation mode for visitors.

Note: Parameterisation is now enabled by default. This option is kept for backwards compatibility and has no effect.

func WithoutParams

func WithoutParams() visitors.Option

WithoutParams disables parameterised query mode.

⚠️ WARNING: Disables SQL injection protection. Only use for debugging or when you're certain all values are trusted. Production code should NEVER use this option.

Types

type Attribute

type Attribute = nodes.Attribute

Attribute represents a column reference (e.g., table.column).

type DeleteManager

type DeleteManager = managers.DeleteManager

DeleteManager provides a fluent API for building DELETE queries.

type InsertManager

type InsertManager = managers.InsertManager

InsertManager provides a fluent API for building INSERT queries.

type MySQLVisitor

type MySQLVisitor = visitors.MySQLVisitor

MySQLVisitor generates MySQL-compatible SQL.

type Node

type Node = nodes.Node

Node is the base interface all AST nodes implement.

type PostgresVisitor

type PostgresVisitor = visitors.PostgresVisitor

PostgresVisitor generates PostgreSQL-compatible SQL.

type SQLiteVisitor

type SQLiteVisitor = visitors.SQLiteVisitor

SQLiteVisitor generates SQLite-compatible SQL.

type SelectManager

type SelectManager = managers.SelectManager

SelectManager provides a fluent API for building SELECT queries.

type Table

type Table = nodes.Table

Table represents a SQL table reference.

type UpdateManager

type UpdateManager = managers.UpdateManager

UpdateManager provides a fluent API for building UPDATE queries.

Directories

Path Synopsis
cmd
repl command
REPL binary for interactively building and executing SQL queries.
REPL binary for interactively building and executing SQL queries.
internal
quoting
Package quoting provides shared identifier quoting utilities.
Package quoting provides shared identifier quoting utilities.
testutil
Package testutil provides shared test helpers for the gosbee project.
Package testutil provides shared test helpers for the gosbee project.
Package managers provides high-level fluent APIs for building SQL ASTs.
Package managers provides high-level fluent APIs for building SQL ASTs.
Package nodes defines the AST node types used to represent SQL query elements.
Package nodes defines the AST node types used to represent SQL query elements.
Package plugins defines the Transformer interface for AST middleware.
Package plugins defines the Transformer interface for AST middleware.
opa
Package opa provides a Transformer that enforces Open Policy Agent policies on queries by injecting policy-derived WHERE conditions.
Package opa provides a Transformer that enforces Open Policy Agent policies on queries by injecting policy-derived WHERE conditions.
softdelete
Package softdelete provides a Transformer that automatically injects "column IS NULL" conditions into SELECT queries, filtering out soft-deleted rows.
Package softdelete provides a Transformer that automatically injects "column IS NULL" conditions into SELECT queries, filtering out soft-deleted rows.
Package visitors provides SQL dialect generators that walk the AST.
Package visitors provides SQL dialect generators that walk the AST.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL