Skip to main content

Composing queries

Builder methods always return a new query, so one query can be the starting point for others, and a function can hand back part of one.

Queries are values​

adults := db.Users.Where(ant.Gt(&U.Age, 18)).OrderBy(&U.Name)

alice, err := adults.Where(ant.Eq(&U.Name, "Alice")).All(ctx)
carol, err := adults.Where(ant.Eq(&U.Name, "Carol")).All(ctx)

// adults is unchanged, and can run again.
everyone, err := adults.All(ctx)

Conditional parts​

When only adds to a query if a condition holds at run time, so a search form doesn't need a chain of ifs.

func search(ctx context.Context, name string, adultsOnly bool) ([]*User, error) {
return db.Users.
OrderBy(&U.Name).
When(name != "", func(q *ant.Query[User]) *ant.Query[User] {
return q.Where(ant.Eq(&U.Name, name))
}).
When(adultsOnly, func(q *ant.Query[User]) *ant.Query[User] {
return q.Where(ant.Gte(&U.Age, 18))
}).
All(ctx)
}

Scopes​

A scope is a named, reusable piece of a query. It's just a function that takes a query and returns one.

func Adults(q *ant.Query[User]) *ant.Query[User] {
return q.Where(ant.Gte(&U.Age, 18))
}

func OlderFirst(q *ant.Query[User]) *ant.Query[User] {
return q.OrderByDesc(&U.Age)
}

users, err := db.Users.Scope(Adults, OlderFirst).All(ctx)

For a scope with parameters, write a function that returns one.

func OlderThan(age int) func(*ant.Query[User]) *ant.Query[User] {
return func(q *ant.Query[User]) *ant.Query[User] {
return q.Where(ant.Gt(&U.Age, age))
}
}

users, err := db.Users.Scope(OlderThan(30)).All(ctx)

You apply scopes by hand. For a condition that should apply to every query of a model, use a global filter instead.