20f5f43fb3
- Use acp-go-sdk for all ACP communication in agentctl - Mark agent as completed when Prompt() returns successfully - Update task state to COMPLETED when agent completes - Update task state to FAILED when agent fails and retries exhausted - Enhanced E2E test to verify agent actually creates files - E2E test now validates full flow: task creation -> agent work -> file creation -> task completion
153 lines
4.0 KiB
Go
153 lines
4.0 KiB
Go
// Package database provides PostgreSQL connection pooling and database operations.
|
|
package database
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgconn"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"github.com/kandev/kandev/internal/common/config"
|
|
)
|
|
|
|
// DB wraps a pgxpool.Pool and provides helper methods for database operations.
|
|
type DB struct {
|
|
pool *pgxpool.Pool
|
|
}
|
|
|
|
// NewDB creates a new database connection pool using the provided configuration.
|
|
// It builds the connection string from config, configures pool settings,
|
|
// establishes the connection, and verifies it with a ping.
|
|
func NewDB(ctx context.Context, cfg config.DatabaseConfig) (*DB, error) {
|
|
connString := cfg.DSN()
|
|
|
|
poolConfig, err := pgxpool.ParseConfig(connString)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse database config: %w", err)
|
|
}
|
|
|
|
// Configure pool settings
|
|
poolConfig.MaxConns = int32(cfg.MaxConns)
|
|
poolConfig.MinConns = int32(cfg.MinConns)
|
|
|
|
// Set reasonable connection timeouts
|
|
poolConfig.ConnConfig.ConnectTimeout = 10 * time.Second
|
|
|
|
// Create the connection pool
|
|
pool, err := pgxpool.NewWithConfig(ctx, poolConfig)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create connection pool: %w", err)
|
|
}
|
|
|
|
// Verify the connection
|
|
if err := pool.Ping(ctx); err != nil {
|
|
pool.Close()
|
|
return nil, fmt.Errorf("failed to ping database: %w", err)
|
|
}
|
|
|
|
return &DB{pool: pool}, nil
|
|
}
|
|
|
|
// Pool returns the underlying pgxpool.Pool.
|
|
func (db *DB) Pool() *pgxpool.Pool {
|
|
return db.pool
|
|
}
|
|
|
|
// Close closes the connection pool.
|
|
func (db *DB) Close() {
|
|
if db.pool != nil {
|
|
db.pool.Close()
|
|
}
|
|
}
|
|
|
|
// Ping verifies the database connection is still alive.
|
|
func (db *DB) Ping(ctx context.Context) error {
|
|
return db.pool.Ping(ctx)
|
|
}
|
|
|
|
// Exec executes a query that doesn't return rows.
|
|
func (db *DB) Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error) {
|
|
return db.pool.Exec(ctx, sql, args...)
|
|
}
|
|
|
|
// Query executes a query that returns rows.
|
|
func (db *DB) Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error) {
|
|
return db.pool.Query(ctx, sql, args...)
|
|
}
|
|
|
|
// QueryRow executes a query that returns at most one row.
|
|
func (db *DB) QueryRow(ctx context.Context, sql string, args ...any) pgx.Row {
|
|
return db.pool.QueryRow(ctx, sql, args...)
|
|
}
|
|
|
|
// BeginTx starts a transaction with the given options.
|
|
func (db *DB) BeginTx(ctx context.Context, opts pgx.TxOptions) (pgx.Tx, error) {
|
|
return db.pool.BeginTx(ctx, opts)
|
|
}
|
|
|
|
// WithTx executes the given function within a transaction.
|
|
// If the function returns an error, the transaction is rolled back.
|
|
// If the function succeeds, the transaction is committed.
|
|
func (db *DB) WithTx(ctx context.Context, fn func(tx pgx.Tx) error) error {
|
|
tx, err := db.pool.Begin(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
}
|
|
|
|
defer func() {
|
|
if p := recover(); p != nil {
|
|
// Rollback on panic
|
|
_ = tx.Rollback(ctx)
|
|
panic(p)
|
|
}
|
|
}()
|
|
|
|
if err := fn(tx); err != nil {
|
|
if rbErr := tx.Rollback(ctx); rbErr != nil {
|
|
return fmt.Errorf("tx failed: %w, rollback failed: %v", err, rbErr)
|
|
}
|
|
return err
|
|
}
|
|
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return fmt.Errorf("failed to commit transaction: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// WithTxOptions executes the given function within a transaction with custom options.
|
|
// If the function returns an error, the transaction is rolled back.
|
|
// If the function succeeds, the transaction is committed.
|
|
func (db *DB) WithTxOptions(ctx context.Context, opts pgx.TxOptions, fn func(tx pgx.Tx) error) error {
|
|
tx, err := db.pool.BeginTx(ctx, opts)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
}
|
|
|
|
defer func() {
|
|
if p := recover(); p != nil {
|
|
// Rollback on panic
|
|
_ = tx.Rollback(ctx)
|
|
panic(p)
|
|
}
|
|
}()
|
|
|
|
if err := fn(tx); err != nil {
|
|
if rbErr := tx.Rollback(ctx); rbErr != nil {
|
|
return fmt.Errorf("tx failed: %w, rollback failed: %v", err, rbErr)
|
|
}
|
|
return err
|
|
}
|
|
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return fmt.Errorf("failed to commit transaction: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|