This guide covers registering and using AST transformer plugins. For an introduction to gosbee, see the Getting Started guide.
Plugins transform the AST before a visitor renders it into SQL. They implement
the Transformer interface and are registered on managers with Use().
Call Use() on any manager — SELECT, INSERT, UPDATE, or DELETE:
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())Multiple plugins can be chained. They are applied in registration order:
query := gosbee.NewSelect(users).
Select(users.Star()).
Use(softdelete.New()).
Use(myCustomPlugin)Plugins run automatically when you call ToSQL(). The original AST is cloned
before transformation, so the source manager is never mutated.
The softdelete plugin automatically adds WHERE deleted_at IS NULL
conditions to queries, ensuring soft-deleted rows are excluded without manual
filtering.
import "github.com/bawdo/gosbee/plugins/softdelete"
// Default: filters on "deleted_at" column for all tables
sd := softdelete.New()
// Custom column name
sd = softdelete.New(softdelete.WithColumn("removed_at"))
// Restrict to specific tables only
sd = softdelete.New(softdelete.WithTables("users", "posts"))
// Per-table column mapping
sd = softdelete.New(
softdelete.WithTableColumn("users", "deleted_at"),
softdelete.WithTableColumn("posts", "removed_at"),
)The plugin applies to all DML operations:
// SELECT — adds WHERE "users"."deleted_at" IS NULL
gosbee.NewSelect(users).Use(sd)
// UPDATE — adds WHERE "users"."deleted_at" IS NULL
gosbee.NewUpdate(users).Set(users.Col("status"), gosbee.BindParam("inactive")).Use(sd)
// DELETE — adds WHERE "users"."deleted_at" IS NULL
gosbee.NewDelete(users).Use(sd)When a query involves joins, the soft-delete condition is added for each joined
table (unless restricted with WithTables).
The opa plugin injects access-control conditions based on policies evaluated
at query time. It supports both local policy functions and a remote OPA server.
import (
"github.com/bawdo/gosbee"
"github.com/bawdo/gosbee/nodes"
"github.com/bawdo/gosbee/plugins/opa"
)
policy := func(tableName string) ([]nodes.Node, error) {
if tableName == "secrets" {
return nil, errors.New("access denied to secrets table")
}
if tableName == "users" {
t := gosbee.NewTable(tableName)
return []nodes.Node{t.Col("tenant_id").Eq(gosbee.BindParam(42))}, nil
}
return nil, nil // no restrictions
}
users := gosbee.NewTable("users")
query := gosbee.NewSelect(users).
Select(users.Star()).
Use(opa.New(policy))
// SELECT "users".* FROM "users"
// WHERE "users"."tenant_id" = 42import (
"github.com/bawdo/gosbee"
"github.com/bawdo/gosbee/plugins/opa"
)
opaPlugin := opa.NewFromServer(
"http://localhost:8181", // OPA server URL
"data.authz.allow", // policy path
)
users := gosbee.NewTable("users")
query := gosbee.NewSelect(users).
Select(users.Star()).
Use(opaPlugin)To write your own plugin, implement the Transformer interface from the
plugins package:
import "github.com/bawdo/gosbee/plugins"
type Transformer interface {
TransformSelect(core *nodes.SelectCore) (*nodes.SelectCore, error)
TransformInsert(stmt *nodes.InsertStatement) (*nodes.InsertStatement, error)
TransformUpdate(stmt *nodes.UpdateStatement) (*nodes.UpdateStatement, error)
TransformDelete(stmt *nodes.DeleteStatement) (*nodes.DeleteStatement, error)
}Embed plugins.BaseTransformer to get no-op defaults for methods you don't
need:
type AuditLogger struct {
plugins.BaseTransformer
}
func (a *AuditLogger) TransformSelect(core *nodes.SelectCore) (*nodes.SelectCore, error) {
// Add a query comment for audit logging
core.Comment = "audit: query by service-x"
return core, nil
}Register it like any other plugin:
query := managers.NewSelectManager(users).
Use(&AuditLogger{})If a transformer returns a non-nil error, ToSQL() will propagate it and no SQL
is generated. This is useful for policy enforcement:
func (p *MyPolicy) TransformSelect(core *nodes.SelectCore) (*nodes.SelectCore, error) {
if containsForbiddenTable(core) {
return nil, errors.New("access denied")
}
return core, nil
}- Getting Started — building queries with the managers API.
- Visitor Dialects — switching between PostgreSQL, MySQL, and SQLite.