Query System
GPA provides two ways to build queries: option-based (functional) and builder-based (fluent). Both produce the same Query object and support the same set of conditions.
Conditions
Conditions are the building blocks of queries:
// Basic conditions
gpa.Where("age", gpa.OpGreaterThan, 18)
gpa.Where("status", gpa.OpEqual, "active")
gpa.Where("name", gpa.OpLike, "%john%")
// Available operators
gpa.OpEqual // =
gpa.OpNotEqual // !=
gpa.OpGreaterThan // >
gpa.OpGreaterThanOrEqual // >=
gpa.OpLessThan // <
gpa.OpLessThanOrEqual // <=
gpa.OpLike // LIKE
gpa.OpNotLike // NOT LIKE
gpa.OpIn // IN
gpa.OpNotIn // NOT IN
gpa.OpBetween // BETWEEN
gpa.OpIsNull // IS NULL
gpa.OpIsNotNull // IS NOT NULLConvenience Builders
gpa.Where("age", gpa.OpGreaterThan, 18)
gpa.WhereIn("status", "active", "pending")
gpa.WhereLike("name", "%john%")
gpa.WhereNull("deleted_at")
gpa.WhereNotNull("email")Composite Conditions
// AND condition
gpa.And(
gpa.Where("age", gpa.OpGreaterThan, 18),
gpa.Where("status", gpa.OpEqual, "active"),
)
// OR condition
gpa.Or(
gpa.Where("role", gpa.OpEqual, "admin"),
gpa.Where("role", gpa.OpEqual, "moderator"),
)Query Options (Functional API)
Pass options directly to repository methods:
users, err := userRepo.Query(ctx,
gpa.Where("age", gpa.OpGreaterThan, 18),
gpa.Where("status", gpa.OpEqual, "active"),
gpa.OrderBy("name", gpa.OrderAsc),
gpa.Limit(10),
gpa.Offset(20),
gpa.Fields("id", "name", "email"),
gpa.Preload("Profile", "Posts"),
gpa.Distinct(),
)Sort and Pagination
gpa.OrderBy("created_at", gpa.OrderDesc)
gpa.OrderBy("name", gpa.OrderAsc)
gpa.Limit(10)
gpa.Offset(20)Field Selection
gpa.Fields("id", "name", "email")
// Same as:
gpa.Select("id", "name", "email")Joins
// Convenience helpers take the table and a join condition
gpa.InnerJoin("orders", "orders.user_id = users.id")
gpa.LeftJoin("comments", "comments.user_id = users.id")
// gpa.Join takes the join type explicitly, plus an optional alias.
// JoinInner, JoinLeft, JoinRight and JoinFull are the available types.
gpa.Join(gpa.JoinRight, "profiles", "profiles.user_id = users.id")
gpa.Join(gpa.JoinFull, "audits", "audits.user_id = users.id", "a")Grouping and Having
gpa.GroupBy("status")
gpa.Having("count(*)", gpa.OpGreaterThan, 5)Locking
gpa.Lock(gpa.LockForUpdate)
gpa.Lock(gpa.LockShared)Preloading Relationships
gpa.Preload("Profile")
gpa.Preload("Posts.Comments") // Nested eager loadingQuery Builder (Fluent API)
The fluent QueryBuilder provides a chainable alternative:
qb := gpa.NewQueryBuilder[User]()
qb.Where("age", gpa.OpGreaterThan, 18).
Where("active", gpa.OpEqual, true).
OrderBy("name", gpa.OrderAsc).
Limit(10).
Offset(0).
Preload("Profile")
// Execute
users, err := qb.Execute(ctx, userRepo)
user, err := qb.ExecuteOne(ctx, userRepo)
count, err := qb.Count(ctx, userRepo)Full Builder API
qb := gpa.NewQueryBuilder[T]()
qb.Where(field, operator, value)
qb.WhereCondition(condition) // a Condition built elsewhere
qb.OrderBy(field, direction)
qb.Limit(n)
qb.Offset(n)
qb.Select(fields...)
qb.Join(gpa.JoinInner, table, condition)
qb.GroupBy(fields...)
qb.Having(field, operator, value)
qb.Distinct()
qb.Lock(gpa.LockForUpdate)
qb.Preload(relations...)
// Build the Query object without executing
query := qb.Build()There is one Join, taking the join type as its first argument —
gpa.JoinInner, gpa.JoinLeft, gpa.JoinRight or gpa.JoinFull. The
gpa.InnerJoin and gpa.LeftJoin helpers shown earlier are QueryOptions for the
option-style API, not builder methods.
Subqueries
A subquery is a *Query, not a SQL string: it is built with the same builder
and passed in, so the same validation and placeholder handling applies.
highValue := gpa.NewQueryBuilder[Order]().
Select("user_id").
Where("total", gpa.OpGreaterThan, 100).
Build()
gpa.InSubQuery("id", highValue)
gpa.ExistsSubQuery(highValue)
gpa.CorrelatedSubQuery("score", gpa.OpGreaterThan, highValue, "product_id")CorrelatedSubQuery takes four arguments: the outer field, the operator, the
subquery, and the field on the inner query that correlates back to the outer
one.