Documentation
¶
Index ¶
- func ConfigureDB(db *sql.DB)
- type CopyRow
- type GoalRepository
- type PostgresGoalRepository
- func (r *PostgresGoalRepository) BatchUpsertGoalActive(ctx context.Context, progresses []*domain.UserGoalProgress) error
- func (r *PostgresGoalRepository) BatchUpsertProgress(ctx context.Context, updates []*domain.UserGoalProgress) error
- func (r *PostgresGoalRepository) BatchUpsertProgressWithCOPY(ctx context.Context, rows []CopyRow) error
- func (r *PostgresGoalRepository) BeginTx(ctx context.Context) (TxRepository, error)
- func (r *PostgresGoalRepository) BulkInsert(ctx context.Context, progresses []*domain.UserGoalProgress) error
- func (r *PostgresGoalRepository) BulkInsertWithCOPY(ctx context.Context, progresses []*domain.UserGoalProgress) error
- func (r *PostgresGoalRepository) GetActiveGoals(ctx context.Context, userID string) ([]*domain.UserGoalProgress, error)
- func (r *PostgresGoalRepository) GetChallengeProgress(ctx context.Context, userID, challengeID string, activeOnly bool) ([]*domain.UserGoalProgress, error)
- func (r *PostgresGoalRepository) GetGoalsByIDs(ctx context.Context, userID string, goalIDs []string) ([]*domain.UserGoalProgress, error)
- func (r *PostgresGoalRepository) GetProgress(ctx context.Context, userID, goalID string) (*domain.UserGoalProgress, error)
- func (r *PostgresGoalRepository) GetUserGoalCount(ctx context.Context, userID string) (int, error)
- func (r *PostgresGoalRepository) GetUserProgress(ctx context.Context, userID string, activeOnly bool) ([]*domain.UserGoalProgress, error)
- func (r *PostgresGoalRepository) MarkAsClaimed(ctx context.Context, userID, goalID string) error
- func (r *PostgresGoalRepository) UpsertGoalActive(ctx context.Context, progress *domain.UserGoalProgress) error
- func (r *PostgresGoalRepository) UpsertProgress(ctx context.Context, progress *domain.UserGoalProgress) error
- type PostgresTxRepository
- func (r *PostgresTxRepository) BatchUpsertGoalActive(ctx context.Context, progresses []*domain.UserGoalProgress) error
- func (r *PostgresTxRepository) BatchUpsertProgress(ctx context.Context, updates []*domain.UserGoalProgress) error
- func (r *PostgresTxRepository) BatchUpsertProgressWithCOPY(ctx context.Context, rows []CopyRow) error
- func (r *PostgresTxRepository) BeginTx(ctx context.Context) (TxRepository, error)
- func (r *PostgresTxRepository) BulkInsert(ctx context.Context, progresses []*domain.UserGoalProgress) error
- func (r *PostgresTxRepository) BulkInsertWithCOPY(ctx context.Context, progresses []*domain.UserGoalProgress) error
- func (r *PostgresTxRepository) Commit() error
- func (r *PostgresTxRepository) GetActiveGoals(ctx context.Context, userID string) ([]*domain.UserGoalProgress, error)
- func (r *PostgresTxRepository) GetChallengeProgress(ctx context.Context, userID, challengeID string, activeOnly bool) ([]*domain.UserGoalProgress, error)
- func (r *PostgresTxRepository) GetGoalsByIDs(ctx context.Context, userID string, goalIDs []string) ([]*domain.UserGoalProgress, error)
- func (r *PostgresTxRepository) GetProgress(ctx context.Context, userID, goalID string) (*domain.UserGoalProgress, error)
- func (r *PostgresTxRepository) GetProgressForUpdate(ctx context.Context, userID, goalID string) (*domain.UserGoalProgress, error)
- func (r *PostgresTxRepository) GetUserGoalCount(ctx context.Context, userID string) (int, error)
- func (r *PostgresTxRepository) GetUserProgress(ctx context.Context, userID string, activeOnly bool) ([]*domain.UserGoalProgress, error)
- func (r *PostgresTxRepository) MarkAsClaimed(ctx context.Context, userID, goalID string) error
- func (r *PostgresTxRepository) Rollback() error
- func (r *PostgresTxRepository) UpsertGoalActive(ctx context.Context, progress *domain.UserGoalProgress) error
- func (r *PostgresTxRepository) UpsertProgress(ctx context.Context, progress *domain.UserGoalProgress) error
- type TxRepository
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ConfigureDB ¶
ConfigureDB configures database connection pool settings.
Types ¶
type CopyRow ¶ added in v0.11.0
type CopyRow struct {
UserID string // User ID
GoalID string // Goal ID
ChallengeID string // Challenge ID
Namespace string // Namespace
Progress *int // Absolute stat value (nil for login/increment events)
ProgressMode string // "absolute" or "relative"
IncValue int // Increment delta (used when Progress is nil)
TargetValue int // Target value for SQL-side completion check
// M5 Phase 5: Rotation metadata for SQL CASE rotation logic.
// Zero values are safe — all rotation SQL branches guard on rotation_boundary IS NOT NULL.
RotationBoundary *time.Time // Last rotation boundary (nil = no rotation)
NewExpiresAt *time.Time // Next expiry timestamp (nil = no rotation)
AllowReselection bool // Allow claimed goals to reset on rotation
ResetProgress bool // Reset progress on rotation (default true in config)
}
CopyRow represents a single row for the unified COPY flush path (M5 Phase 2). Carries both absolute and increment events through a single temp table and COPY protocol. Status/completion is computed in SQL CASE expressions, not in Go code.
type GoalRepository ¶
type GoalRepository interface {
// GetProgress retrieves a single user's progress for a specific goal.
// Returns nil if no progress record exists (lazy initialization).
GetProgress(ctx context.Context, userID, goalID string) (*domain.UserGoalProgress, error)
// GetUserProgress retrieves all goal progress records for a specific user.
// Returns empty slice if user has no progress records.
// M3 Phase 4: activeOnly parameter filters to only is_active = true goals.
GetUserProgress(ctx context.Context, userID string, activeOnly bool) ([]*domain.UserGoalProgress, error)
// GetChallengeProgress retrieves all goal progress for a user within a specific challenge.
// Returns empty slice if user has no progress for this challenge.
// M3 Phase 4: activeOnly parameter filters to only is_active = true goals.
GetChallengeProgress(ctx context.Context, userID, challengeID string, activeOnly bool) ([]*domain.UserGoalProgress, error)
// UpsertProgress creates or updates a single goal progress record.
// Uses INSERT ... ON CONFLICT (user_id, goal_id) DO UPDATE.
// Does NOT update if status is 'claimed' (protection against overwrites).
UpsertProgress(ctx context.Context, progress *domain.UserGoalProgress) error
// BatchUpsertProgress performs batch upsert for multiple progress records in a single query.
// Does NOT update records where status is 'claimed'.
//
// DEPRECATED: Use BatchUpsertProgressWithCOPY for better performance (5-10x faster).
// This method is kept for backwards compatibility and testing.
BatchUpsertProgress(ctx context.Context, updates []*domain.UserGoalProgress) error
// BatchUpsertProgressWithCOPY performs batch upsert using PostgreSQL COPY protocol (M5 Phase 2: Unified COPY Path).
// Accepts CopyRow slices carrying both absolute and increment events through a single flush path.
// Status and completion are computed in SQL CASE expressions, not in Go code.
//
// USAGE: Use this for production workloads requiring high throughput (500+ EPS).
// Performance: 10-20ms for 1,000 records.
BatchUpsertProgressWithCOPY(ctx context.Context, rows []CopyRow) error
// MarkAsClaimed updates a goal's status to 'claimed' and sets claimed_at timestamp.
// Used after successfully granting rewards via AGS Platform Service.
// Returns error if goal is not in 'completed' status or already claimed.
MarkAsClaimed(ctx context.Context, userID, goalID string) error
// BeginTx starts a database transaction and returns a transactional repository.
// Used for claim flow to ensure atomicity (check status + mark claimed + verify).
BeginTx(ctx context.Context) (TxRepository, error)
// GetGoalsByIDs retrieves goal progress records for a user across multiple goal IDs.
// Returns empty slice if none of the goals have progress records.
// Used by initialization endpoint to check which default goals already exist.
GetGoalsByIDs(ctx context.Context, userID string, goalIDs []string) ([]*domain.UserGoalProgress, error)
// BulkInsert creates multiple goal progress records in a single parameterized INSERT query.
// Uses INSERT ... ON CONFLICT DO NOTHING for idempotency.
// Used by initialization endpoint to create default goal assignments.
BulkInsert(ctx context.Context, progresses []*domain.UserGoalProgress) error
// BulkInsertWithCOPY creates multiple goal progress records using PostgreSQL COPY protocol.
BulkInsertWithCOPY(ctx context.Context, progresses []*domain.UserGoalProgress) error
// UpsertGoalActive creates or updates a goal's is_active status.
UpsertGoalActive(ctx context.Context, progress *domain.UserGoalProgress) error
// BatchUpsertGoalActive activates multiple goals in a single database operation.
BatchUpsertGoalActive(ctx context.Context, progresses []*domain.UserGoalProgress) error
// GetUserGoalCount returns the total number of goals for a user (active + inactive).
GetUserGoalCount(ctx context.Context, userID string) (int, error)
// GetActiveGoals retrieves only active goal progress records for a user.
GetActiveGoals(ctx context.Context, userID string) ([]*domain.UserGoalProgress, error)
}
GoalRepository defines the interface for managing user goal progress in the database. This interface abstracts database operations to allow for testing and different implementations.
type PostgresGoalRepository ¶
type PostgresGoalRepository struct {
// contains filtered or unexported fields
}
PostgresGoalRepository implements GoalRepository interface using PostgreSQL.
func NewPostgresGoalRepository ¶
func NewPostgresGoalRepository(db *sql.DB) *PostgresGoalRepository
NewPostgresGoalRepository creates a new PostgreSQL-backed goal repository.
func (*PostgresGoalRepository) BatchUpsertGoalActive ¶ added in v0.10.0
func (r *PostgresGoalRepository) BatchUpsertGoalActive(ctx context.Context, progresses []*domain.UserGoalProgress) error
BatchUpsertGoalActive updates is_active status for multiple goals in a single database operation (M4). This is a performance optimization for batch and random goal selection features.
Supports both activation (is_active=true) and deactivation (is_active=false) through the same method. This flexibility is required for M4's replace mode, where existing goals are deactivated before selecting new ones.
Implementation uses UNNEST to map each goal to its specific is_active value: 1. UPDATE existing rows: SET is_active = data.is_active FROM (UNNEST(goal_ids, is_active_vals)) 2. INSERT missing rows: INSERT ... ON CONFLICT DO UPDATE SET is_active = EXCLUDED.is_active
Performance: ~10ms for 10 goals (vs ~20-50ms with individual UpsertGoalActive loop)
func (*PostgresGoalRepository) BatchUpsertProgress ¶
func (r *PostgresGoalRepository) BatchUpsertProgress(ctx context.Context, updates []*domain.UserGoalProgress) error
BatchUpsertProgress performs batch upsert for multiple progress records in a single query. This is the key optimization for buffered event processing (1,000,000x query reduction).
DEPRECATED: Use BatchUpsertProgressWithCOPY for better performance (5-10x faster). This method is kept for backwards compatibility and testing.
M3: Added is_active = true check in WHERE clause for assignment control. Only updates assigned goals (is_active = true), skipping unassigned goals.
func (*PostgresGoalRepository) BatchUpsertProgressWithCOPY ¶ added in v0.2.0
func (r *PostgresGoalRepository) BatchUpsertProgressWithCOPY(ctx context.Context, rows []CopyRow) error
BatchUpsertProgressWithCOPY performs batch upsert using PostgreSQL COPY protocol (M5 Phase 2: Unified COPY Path). Accepts CopyRow slices carrying both absolute and increment events through a single flush path. Status and completion are computed in SQL CASE expressions, not in Go code.
Implementation: 1. Creates temp table with progress_mode, inc_value, target_value columns 2. Uses COPY FROM STDIN to bulk load data (bypasses query parser) 3. UPDATE-only merge with SQL-side status computation 4. Maintains claimed protection and is_active check
func (*PostgresGoalRepository) BeginTx ¶
func (r *PostgresGoalRepository) BeginTx(ctx context.Context) (TxRepository, error)
BeginTx starts a database transaction and returns a transactional repository.
func (*PostgresGoalRepository) BulkInsert ¶ added in v0.3.0
func (r *PostgresGoalRepository) BulkInsert(ctx context.Context, progresses []*domain.UserGoalProgress) error
BulkInsert creates multiple goal progress records in a single query.
DEPRECATED: Use BulkInsertWithCOPY for better performance (3-5x faster). This method is kept for backwards compatibility and testing.
func (*PostgresGoalRepository) BulkInsertWithCOPY ¶ added in v0.9.3
func (r *PostgresGoalRepository) BulkInsertWithCOPY(ctx context.Context, progresses []*domain.UserGoalProgress) error
BulkInsertWithCOPY creates multiple goal progress records using PostgreSQL COPY protocol.
⚠️ WARNING: DO NOT USE FOR SMALL BATCHES (< 1000 records)
Benchmark Results (2025-11-11):
- 10 records: COPY is 2.3x SLOWER (5.10ms vs 2.20ms) - use BulkInsert() instead
- 100 records: COPY is 1.2x SLOWER (10.43ms vs 8.63ms) - use BulkInsert() instead
- 1000+ records: COPY starts showing benefits (~40ms for 1000 records)
Why COPY is slower for small batches:
- Transaction overhead (BEGIN/COMMIT required)
- Temp table creation overhead (CREATE TEMP TABLE)
- Two-step process (COPY to temp, INSERT from temp)
- 4.4x higher memory usage (81KB vs 18KB for 10 records)
Use cases for BulkInsertWithCOPY:
✅ Bulk data migrations (1000+ records) ✅ Background jobs processing large batches ✅ Admin operations importing data ❌ Initialize endpoint (10-20 records) - use BulkInsert() instead ❌ Event-driven updates (1-10 records) - use single inserts
Implementation: 1. Creates temporary table (session-local, auto-dropped) 2. Uses COPY FROM STDIN to bulk load data (bypasses query parser) 3. Inserts from temp table to main table with ON CONFLICT DO NOTHING
func (*PostgresGoalRepository) GetActiveGoals ¶ added in v0.9.0
func (r *PostgresGoalRepository) GetActiveGoals(ctx context.Context, userID string) ([]*domain.UserGoalProgress, error)
GetActiveGoals retrieves only active goal progress records for a user.
func (*PostgresGoalRepository) GetChallengeProgress ¶
func (r *PostgresGoalRepository) GetChallengeProgress(ctx context.Context, userID, challengeID string, activeOnly bool) ([]*domain.UserGoalProgress, error)
GetChallengeProgress retrieves all goal progress for a user within a specific challenge. M3 Phase 4: activeOnly parameter filters to only is_active = true goals.
func (*PostgresGoalRepository) GetGoalsByIDs ¶ added in v0.3.0
func (r *PostgresGoalRepository) GetGoalsByIDs(ctx context.Context, userID string, goalIDs []string) ([]*domain.UserGoalProgress, error)
GetGoalsByIDs retrieves goal progress records for a user across multiple goal IDs.
func (*PostgresGoalRepository) GetProgress ¶
func (r *PostgresGoalRepository) GetProgress(ctx context.Context, userID, goalID string) (*domain.UserGoalProgress, error)
GetProgress retrieves a single user's progress for a specific goal.
func (*PostgresGoalRepository) GetUserGoalCount ¶ added in v0.9.0
GetUserGoalCount returns the total number of goals for a user (active + inactive).
func (*PostgresGoalRepository) GetUserProgress ¶
func (r *PostgresGoalRepository) GetUserProgress(ctx context.Context, userID string, activeOnly bool) ([]*domain.UserGoalProgress, error)
GetUserProgress retrieves all goal progress records for a specific user. M3 Phase 4: activeOnly parameter filters to only is_active = true goals.
func (*PostgresGoalRepository) MarkAsClaimed ¶
func (r *PostgresGoalRepository) MarkAsClaimed(ctx context.Context, userID, goalID string) error
MarkAsClaimed updates a goal's status to 'claimed' and sets claimed_at timestamp.
func (*PostgresGoalRepository) UpsertGoalActive ¶ added in v0.3.0
func (r *PostgresGoalRepository) UpsertGoalActive(ctx context.Context, progress *domain.UserGoalProgress) error
UpsertGoalActive creates or updates a goal's is_active status.
func (*PostgresGoalRepository) UpsertProgress ¶
func (r *PostgresGoalRepository) UpsertProgress(ctx context.Context, progress *domain.UserGoalProgress) error
UpsertProgress creates or updates a single goal progress record.
type PostgresTxRepository ¶
type PostgresTxRepository struct {
// contains filtered or unexported fields
}
PostgresTxRepository implements TxRepository interface for transactional operations.
func (*PostgresTxRepository) BatchUpsertGoalActive ¶ added in v0.10.0
func (r *PostgresTxRepository) BatchUpsertGoalActive(ctx context.Context, progresses []*domain.UserGoalProgress) error
BatchUpsertGoalActive updates is_active status for multiple goals in a single database operation within a transaction (M4). Transaction version of BatchUpsertGoalActive - uses r.tx instead of r.db.
Supports both activation (is_active=true) and deactivation (is_active=false) through the same method. This flexibility is required for M4's replace mode, where existing goals are deactivated before selecting new ones.
Implementation uses UNNEST to map each goal to its specific is_active value: 1. UPDATE existing rows: SET is_active = data.is_active FROM (UNNEST(goal_ids, is_active_vals)) 2. INSERT missing rows: INSERT ... ON CONFLICT DO UPDATE SET is_active = EXCLUDED.is_active
Performance: ~10ms for 10 goals (vs ~20-50ms with individual UpsertGoalActive loop)
func (*PostgresTxRepository) BatchUpsertProgress ¶
func (r *PostgresTxRepository) BatchUpsertProgress(ctx context.Context, updates []*domain.UserGoalProgress) error
BatchUpsertProgress batch upserts within a transaction. DEPRECATED: Use BatchUpsertProgressWithCOPY for better performance.
func (*PostgresTxRepository) BatchUpsertProgressWithCOPY ¶ added in v0.2.0
func (r *PostgresTxRepository) BatchUpsertProgressWithCOPY(ctx context.Context, rows []CopyRow) error
BatchUpsertProgressWithCOPY performs batch upsert using COPY protocol within a transaction (M5 Phase 2).
func (*PostgresTxRepository) BeginTx ¶
func (r *PostgresTxRepository) BeginTx(ctx context.Context) (TxRepository, error)
BeginTx is not supported within a transaction.
func (*PostgresTxRepository) BulkInsert ¶ added in v0.3.0
func (r *PostgresTxRepository) BulkInsert(ctx context.Context, progresses []*domain.UserGoalProgress) error
BulkInsert creates multiple goal progress records within a transaction.
DEPRECATED: Use BulkInsertWithCOPY for better performance (3-5x faster). This method is kept for backwards compatibility and testing.
func (*PostgresTxRepository) BulkInsertWithCOPY ¶ added in v0.9.3
func (r *PostgresTxRepository) BulkInsertWithCOPY(ctx context.Context, progresses []*domain.UserGoalProgress) error
BulkInsertWithCOPY creates multiple goal progress records using COPY protocol within a transaction.
⚠️ WARNING: DO NOT USE FOR SMALL BATCHES (< 1000 records)
Benchmark Results (2025-11-11):
- 10 records: COPY is 2.3x SLOWER than parameterized INSERT
- 100 records: COPY is 1.2x SLOWER than parameterized INSERT
- 1000+ records: COPY starts showing benefits
See PostgresGoalRepository.BulkInsertWithCOPY for detailed benchmark results and usage guidelines. For small batches (< 1000 records), use BulkInsert() instead.
func (*PostgresTxRepository) Commit ¶
func (r *PostgresTxRepository) Commit() error
Commit commits the transaction.
func (*PostgresTxRepository) GetActiveGoals ¶ added in v0.9.0
func (r *PostgresTxRepository) GetActiveGoals(ctx context.Context, userID string) ([]*domain.UserGoalProgress, error)
GetActiveGoals retrieves only active goal progress records for a user within a transaction.
func (*PostgresTxRepository) GetChallengeProgress ¶
func (r *PostgresTxRepository) GetChallengeProgress(ctx context.Context, userID, challengeID string, activeOnly bool) ([]*domain.UserGoalProgress, error)
GetChallengeProgress retrieves challenge progress within a transaction. M3 Phase 4: activeOnly parameter filters to only is_active = true goals.
func (*PostgresTxRepository) GetGoalsByIDs ¶ added in v0.3.0
func (r *PostgresTxRepository) GetGoalsByIDs(ctx context.Context, userID string, goalIDs []string) ([]*domain.UserGoalProgress, error)
GetGoalsByIDs retrieves goal progress records within a transaction.
func (*PostgresTxRepository) GetProgress ¶
func (r *PostgresTxRepository) GetProgress(ctx context.Context, userID, goalID string) (*domain.UserGoalProgress, error)
GetProgress retrieves progress within a transaction.
func (*PostgresTxRepository) GetProgressForUpdate ¶
func (r *PostgresTxRepository) GetProgressForUpdate(ctx context.Context, userID, goalID string) (*domain.UserGoalProgress, error)
GetProgressForUpdate retrieves progress with SELECT ... FOR UPDATE (row-level lock).
func (*PostgresTxRepository) GetUserGoalCount ¶ added in v0.9.0
GetUserGoalCount returns the total number of goals for a user (active + inactive) within a transaction.
func (*PostgresTxRepository) GetUserProgress ¶
func (r *PostgresTxRepository) GetUserProgress(ctx context.Context, userID string, activeOnly bool) ([]*domain.UserGoalProgress, error)
GetUserProgress retrieves all user progress within a transaction. M3 Phase 4: activeOnly parameter filters to only is_active = true goals.
func (*PostgresTxRepository) MarkAsClaimed ¶
func (r *PostgresTxRepository) MarkAsClaimed(ctx context.Context, userID, goalID string) error
MarkAsClaimed marks a goal as claimed within a transaction.
func (*PostgresTxRepository) Rollback ¶
func (r *PostgresTxRepository) Rollback() error
Rollback rolls back the transaction.
func (*PostgresTxRepository) UpsertGoalActive ¶ added in v0.3.0
func (r *PostgresTxRepository) UpsertGoalActive(ctx context.Context, progress *domain.UserGoalProgress) error
UpsertGoalActive creates or updates a goal's is_active status within a transaction.
func (*PostgresTxRepository) UpsertProgress ¶
func (r *PostgresTxRepository) UpsertProgress(ctx context.Context, progress *domain.UserGoalProgress) error
UpsertProgress upserts progress within a transaction.
type TxRepository ¶
type TxRepository interface {
GoalRepository
// GetProgressForUpdate retrieves progress with SELECT ... FOR UPDATE (row-level lock).
// This prevents concurrent claim attempts for the same goal.
GetProgressForUpdate(ctx context.Context, userID, goalID string) (*domain.UserGoalProgress, error)
// Commit commits the transaction.
Commit() error
// Rollback rolls back the transaction.
Rollback() error
}
TxRepository represents a transactional repository that supports commit/rollback. This ensures the claim flow is atomic (prevents double claims via row-level locking).