Documentation
¶
Overview ¶
Package softdelete provides a Transformer that automatically injects "column IS NULL" conditions into SELECT queries, filtering out soft-deleted rows.
By default it appends WHERE "deleted_at" IS NULL for every table referenced in the FROM and JOIN clauses. Both the column name and the set of tables can be customised via options.
Basic usage ¶
sd := softdelete.New() query := managers.NewSelectManager(table) query.Use(sd) // SELECT * FROM "users" WHERE "users"."deleted_at" IS NULL
Custom column ¶
sd := softdelete.New(softdelete.WithColumn("removed_at"))
// ... WHERE "users"."removed_at" IS NULL
Restrict to specific tables ¶
When a query joins multiple tables but only some use soft-delete:
sd := softdelete.New(softdelete.WithTables("users"))
// Only "users" gets the IS NULL condition; other joined tables are unchanged.
Per-table columns ¶
Different tables may use different column names for soft-delete:
sd := softdelete.New(
softdelete.WithTableColumn("users", "deleted_at"),
softdelete.WithTableColumn("posts", "removed_at"),
)
// users gets "deleted_at" IS NULL; posts gets "removed_at" IS NULL
Nested queries and raw SQL ¶
Via a manager, the plugin applies inside subqueries, CTE bodies and set-operation branches. A name that refers to a CTE is not a table and is skipped. Queries containing raw SQL (SqlLiteral, StringJoin) are refused with plugins.ErrUnpoliceable unless WithAllowRawSQL is given.
REPL usage ¶
gosbee> plugin softdelete gosbee> plugin softdelete removed_at gosbee> plugin softdelete removed_at on users posts gosbee> plugin softdelete users.deleted_at, posts.removed_at gosbee> plugin off softdelete gosbee> plugins
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Option ¶
type Option func(*SoftDelete)
Option configures a SoftDelete transformer.
func WithAllowRawSQL ¶ added in v0.2.0
func WithAllowRawSQL() Option
WithAllowRawSQL lets queries containing raw SQL fragments (SqlLiteral, StringJoin) through. By default the plugin refuses them with an error wrapping plugins.ErrUnpoliceable, because it cannot tell whether a raw fragment reads from a table it should be filtering. Only enable this when every raw fragment is trusted not to bypass soft-delete.
func WithColumn ¶
WithColumn sets the soft-delete column name. Default is "deleted_at".
func WithTableColumn ¶
WithTableColumn sets a per-table column override. The table is automatically added to the whitelist, restricting the plugin's scope.
Example ¶
Tables that name their soft-delete column differently get a column each. A table with no entry is left alone.
package main
import (
"fmt"
"github.com/bawdo/gosbee/managers"
"github.com/bawdo/gosbee/nodes"
"github.com/bawdo/gosbee/plugins/softdelete"
"github.com/bawdo/gosbee/visitors"
)
func printSQL(sql string, params []any, err error) {
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(sql)
fmt.Println(params)
}
func main() {
users := nodes.NewTable("users")
posts := nodes.NewTable("posts")
tags := nodes.NewTable("tags")
query := managers.NewSelectManager(users).
Select(users.Col("name"), posts.Col("title"), tags.Col("label")).
Join(posts).On(posts.Col("user_id").Eq(users.Col("id"))).
Join(tags).On(tags.Col("post_id").Eq(posts.Col("id"))).
Use(softdelete.New(
softdelete.WithTableColumn("users", "deleted_at"),
softdelete.WithTableColumn("posts", "removed_at"),
))
printSQL(query.ToSQL(visitors.NewPostgresVisitor()))
}
Output: SELECT "users"."name", "posts"."title", "tags"."label" FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" INNER JOIN "tags" ON "tags"."post_id" = "posts"."id" WHERE "users"."deleted_at" IS NULL AND "posts"."removed_at" IS NULL []
func WithTables ¶
WithTables restricts the plugin to only the named tables. By default, the plugin applies to every table in the query.
type SoftDelete ¶
type SoftDelete struct {
plugins.BaseTransformer
Column string
Columns map[string]string // per-table column overrides (table name → column name)
// contains filtered or unexported fields
}
SoftDelete is a Transformer that appends IS NULL conditions for a soft-delete column on every referenced table (or a configured subset).
func New ¶
func New(opts ...Option) *SoftDelete
New creates a SoftDelete transformer with the given options.
Example ¶
By default every table in FROM and the joins gets a deleted_at IS NULL condition.
package main
import (
"fmt"
"github.com/bawdo/gosbee/managers"
"github.com/bawdo/gosbee/nodes"
"github.com/bawdo/gosbee/plugins/softdelete"
"github.com/bawdo/gosbee/visitors"
)
func printSQL(sql string, params []any, err error) {
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(sql)
fmt.Println(params)
}
func main() {
users := nodes.NewTable("users")
posts := nodes.NewTable("posts")
query := managers.NewSelectManager(users).
Select(users.Col("name"), posts.Col("title")).
Join(posts).On(posts.Col("user_id").Eq(users.Col("id"))).
Use(softdelete.New())
printSQL(query.ToSQL(visitors.NewPostgresVisitor()))
}
Output: SELECT "users"."name", "posts"."title" FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" WHERE "users"."deleted_at" IS NULL AND "posts"."deleted_at" IS NULL []
func (*SoftDelete) RefusesRawSQL ¶ added in v0.2.0
func (sd *SoftDelete) RefusesRawSQL() bool
RefusesRawSQL reports whether the walker should refuse raw SQL fragments. It is true unless WithAllowRawSQL was given.
func (*SoftDelete) TransformSelect ¶
func (sd *SoftDelete) TransformSelect(core *nodes.SelectCore) (*nodes.SelectCore, error)
TransformSelect appends "column IS NULL" to the WHERE clause for each matching table referenced in the query (FROM and JOINs), and in every query nested in it, via plugins.ApplySelect. A direct call outside a manager gets the same scope and raw SQL refusal as the plugin walker. The input is not modified.
func (*SoftDelete) TransformSelectScoped ¶ added in v0.2.0
func (sd *SoftDelete) TransformSelectScoped(core *nodes.SelectCore, scope plugins.Scope) (*nodes.SelectCore, error)
TransformSelectScoped is TransformSelect aware of the walker's scope: names that refer to a CTE, and derived-table aliases, are not treated as tables.