Query System
Overview
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
gpa.Join("profiles", "profiles.user_id", "users.id")
gpa.InnerJoin("orders", "orders.user_id", "users.id")
gpa.LeftJoin("comments", "comments.user_id", "users.id")
gpa.RightJoin(...)
gpa.FullJoin(...)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.OrderBy(field, direction)
qb.Limit(n)
qb.Offset(n)
qb.Select(fields...) // Same as Fields()
qb.Join(table, on, ...)
qb.InnerJoin(table, on)
qb.LeftJoin(table, on)
qb.GroupBy(fields...)
qb.Having(field, operator, value)
qb.Distinct()
qb.Lock(lockType)
qb.Preload(relations...)
// Build the Query object without executing
query := qb.Build()Subqueries
gpa.ExistsSubQuery("SELECT 1 FROM orders WHERE orders.user_id = users.id")
gpa.InSubQuery("user_id", "SELECT id FROM users WHERE active = true")
gpa.CorrelatedSubQuery("SELECT AVG(score) FROM reviews WHERE reviews.product_id = products.id")