I love the SQL Builder pattern
I really enjoy writing SQL builders. By that I mean that instead of writing a SQL query, I write a little struct along with a builder pattern that allows me to build up the query. This gives me a proper API documenting the query's intent, as well as a way to do schema migrations in the future while keeping the power of raw SQL.
This is three times more so these days, where most of this code can be TAB-completed with GitHub copilot. I wrote the following example in about 2 minutes.
type UserQuery struct {
filterEnabled bool
filterValue string
getFullInfo bool
}
func NewUserQuery() *UserQuery {
return &UserQuery{
filterEnabled: false,
filterValue: "",
getFullInfo: false,
}
}
func (uq *UserQuery) Filter(filterValue string) *UserQuery {
uq.filterEnabled = true
uq.filterValue = filterValue
return uq
}
func (uq *UserQuery) FullInfo() *UserQuery {
uq.getFullInfo = true
return uq
}
func (uq *UserQuery) Build() (string, []interface{}) {
binds := make([]interface{}, 0)
selects := []string{"id", "name", "email"}
wheres := []string{"1=1"}
if uq.filterEnabled {
wheres = append(wheres, "name LIKE ?")
binds = append(binds, uq.filterValue)
}
if uq.getFullInfo {
selects = append(selects, "phone", "address")
}
return fmt.Sprintf(
"SELECT %s FROM users WHERE %s",
strings.Join(selects, ", "),
strings.Join(wheres, " AND ")), binds
}
func main() {
q := NewUserQuery().Filter("Manuel%").FullInfo()
query, binds := q.Build()
db.Query(query, binds...)
}
One thing I love doing with this pattern is keeping performance metrics and logs of executed queries, which is easily done and configurable because I can add all kinds of things to the UserQuery
class itself.