package store
import (
"context"
sq "github.com/Masterminds/squirrel"
"github.com/jackc/pgx/v5/pgxpool"
)
type Filters struct {
Status string
Owner string
Limit uint64
}
func Find(ctx context.Context, db *pgxpool.Pool, f Filters) ([]string, error) {
q := sq.Select("id").From("tasks").PlaceholderFormat(sq.Dollar).OrderBy("created_at DESC")
if f.Status != "" {
q = q.Where(sq.Eq{"status": f.Status})
}
if f.Owner != "" {
q = q.Where(sq.Eq{"owner_id": f.Owner})
}
if f.Limit == 0 || f.Limit > 200 {
f.Limit = 50
}
q = q.Limit(f.Limit)
sqlStr, args, err := q.ToSql()
if err != nil {
return nil, err
}
rows, err := db.Query(ctx, sqlStr, args...)
if err != nil {
return nil, err
}
defer rows.Close()
ids := make([]string, 0, f.Limit)
for rows.Next() {
var id string
if err := rows.Scan(&id); err != nil {
return nil, err
}
ids = append(ids, id)
}
return ids, rows.Err()
}
Endpoints with optional filters often devolve into messy SQL string concatenation. I prefer building queries with squirrel so I can conditionally add WHERE clauses while keeping the final query parameterized. The pattern also helps keep ordering stable and limits bounded, which matters for pagination and cacheability. I still rely on the database for correctness (constraints) and performance (indexes), but the builder keeps application code readable and less error-prone. Another benefit is testability: you can unit test the generated SQL for key conditions without mocking the DB. This is particularly useful for admin search endpoints where new filters are added frequently.