workshop
The Build-A-Where Workshop is a tiny package that helps you write conditional clauses for your database queries.
The inspiration for this package is all of the gems that one might find out there in the Ruby world for database-agnostic query building.
Huh?
For example, instead of exposing a bunch of PostgreSQL-specific nonsense like this:
db.WithContext(ctx).First("name = ? AND age = ?", woozle.Name, woozle.Age)
You might do something like this:
myTransformer := func(groups []workshop.Group) []any {
// do stuff that converts the groups of clauses to the arguments one would
// use in your database's prepare statements, returned as an array
}
// Assuming that both workshop and workshop/op have been imported ...
db.WithContext(ctx).First(
workshop.
Where("name", op.Equal, woozle.Name).
And("age", op.Equal, woozle.Age).
Transform(myTransformer)...,
)
Installation
go get -u codeberg.org/build-a-where/workshop
Core Concepts
Condition
A Condition is a single aspect of a typical WHERE
-type clause. It is generally a field name, an operator, and a value.
Group
A Group is a list of Conditions. Conceptually, there is a natural AND
between each of them, and there is a natural OR
between any two groups.
Filter
A Filter is a builder that allows one to programmatically create full clauses. While you're welcome to create your own raw *workshop.Filter
reference, the best way to create a filter is with baw.Where()
.
A transformer is a function that takes a collection of Groups and spits out a collection of any
that represents the arguments one would use to set up a prepared statement in one's database of choice.
You largely shouldn't have to write your own transformer, but would most typically use a transformer provided by the build-a-where community.
Full Usage Example
package main
import (
"fmt"
"strings"
"codeberg.org/build-a-where/workshop"
"codeberg.org/build-a-where/workshop/op"
)
func main() {
query := workshop.
Where("id", op.Equal, 1).
And("name", op.Like, "John%").
Or("age", op.GreaterThan, 18).
And("age", op.LessThan, 30).
Or("age", op.In, []int{1, 2, 3}).
And("age", op.NotIn, []int{4, 5, 6})
final := make([]string, 0)
for _, item := range query.Transform(transformer) {
final = append(final, fmt.Sprintf("%v", item))
}
fmt.Println(strings.Join(final, ", "))
}
func transformer(groups []workshop.Group) []any {
output := make([]any, 0)
og := make([]string, 0)
values := make([]any, 0)
for _, group := range groups {
g := make([]string, 0)
for _, condition := range group {
g = append(g, fmt.Sprintf("%s %s ?", condition.Column, renderOp(condition.Op)))
values = append(values, condition.Value)
}
og = append(og, fmt.Sprintf("(%s)", strings.Join(g, " AND ")))
}
output = append(output, fmt.Sprintf(`"%s"`, strings.Join(og, " OR ")))
output = append(output, values...)
return output
}
func renderOp(o op.Op) string {
switch o {
case op.Equal:
return "="
case op.NotEqual:
return "<>"
case op.GreaterThan:
return ">"
case op.LessThan:
return "<"
case op.GreaterOrEqual:
return ">="
case op.LessOrEqual:
return "<="
case op.Like:
return "LIKE"
case op.In:
return "IN"
case op.NotIn:
return "NOT IN"
case op.Null:
return "IS NULL"
case op.NotNull:
return "IS NOT NULL"
default:
return ""
}
}
History
- v1.0.0 - The workshop's open!