Added support for Postgres polling sources
This commit is contained in:
117
sources/postgres.go
Normal file
117
sources/postgres.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package sources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.maximumdirect.net/ejr/feedkit/config"
|
||||
pgconn "gitea.maximumdirect.net/ejr/feedkit/internal/postgres"
|
||||
)
|
||||
|
||||
const defaultPostgresQueryTimeout = 30 * time.Second
|
||||
|
||||
type postgresQueryDB interface {
|
||||
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
|
||||
}
|
||||
|
||||
var openPostgresQueryDB = func(ctx context.Context, cfg pgconn.ConnConfig) (postgresQueryDB, error) {
|
||||
return pgconn.Open(ctx, cfg)
|
||||
}
|
||||
|
||||
// PostgresQuerySource is a reusable helper for polling Postgres-backed sources.
|
||||
//
|
||||
// It centralizes generic source config parsing and query execution. Concrete
|
||||
// daemon sources remain responsible for SQL semantics, row scanning, cursoring,
|
||||
// and event construction.
|
||||
type PostgresQuerySource struct {
|
||||
Driver string
|
||||
Name string
|
||||
SQL string
|
||||
QueryTimeout time.Duration
|
||||
|
||||
db postgresQueryDB
|
||||
}
|
||||
|
||||
// NewPostgresQuerySource builds a generic Postgres polling helper from
|
||||
// SourceConfig.
|
||||
//
|
||||
// Required params:
|
||||
// - params.uri
|
||||
// - params.username
|
||||
// - params.password
|
||||
// - params.query
|
||||
//
|
||||
// Optional params:
|
||||
// - params.query_timeout (default 30s)
|
||||
func NewPostgresQuerySource(driver string, cfg config.SourceConfig) (*PostgresQuerySource, error) {
|
||||
name := strings.TrimSpace(cfg.Name)
|
||||
if name == "" {
|
||||
return nil, fmt.Errorf("%s: name is required", driver)
|
||||
}
|
||||
if cfg.Params == nil {
|
||||
return nil, fmt.Errorf("%s %q: params are required (need params.uri, params.username, params.password, and params.query)", driver, cfg.Name)
|
||||
}
|
||||
|
||||
uri, ok := cfg.ParamString("uri")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s %q: params.uri is required", driver, cfg.Name)
|
||||
}
|
||||
username, ok := cfg.ParamString("username")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s %q: params.username is required", driver, cfg.Name)
|
||||
}
|
||||
password, ok := cfg.ParamString("password")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s %q: params.password is required", driver, cfg.Name)
|
||||
}
|
||||
query, ok := cfg.ParamString("query")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s %q: params.query is required", driver, cfg.Name)
|
||||
}
|
||||
|
||||
queryTimeout := defaultPostgresQueryTimeout
|
||||
if _, exists := cfg.Params["query_timeout"]; exists {
|
||||
var ok bool
|
||||
queryTimeout, ok = cfg.ParamDuration("query_timeout")
|
||||
if !ok || queryTimeout <= 0 {
|
||||
return nil, fmt.Errorf("source %q: params.query_timeout must be a positive duration", cfg.Name)
|
||||
}
|
||||
}
|
||||
|
||||
db, err := openPostgresQueryDB(context.Background(), pgconn.ConnConfig{
|
||||
URI: uri,
|
||||
Username: username,
|
||||
Password: password,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %q: open db: %w", driver, cfg.Name, err)
|
||||
}
|
||||
|
||||
return &PostgresQuerySource{
|
||||
Driver: driver,
|
||||
Name: name,
|
||||
SQL: query,
|
||||
QueryTimeout: queryTimeout,
|
||||
db: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *PostgresQuerySource) Query(ctx context.Context, args ...any) (*sql.Rows, error) {
|
||||
queryCtx := ctx
|
||||
if s.QueryTimeout > 0 {
|
||||
if deadline, ok := ctx.Deadline(); !ok || time.Until(deadline) > s.QueryTimeout {
|
||||
// We intentionally do not cancel this derived context here because the
|
||||
// returned rows may still be reading from the database.
|
||||
queryCtx, _ = context.WithTimeout(ctx, s.QueryTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
rows, err := s.db.QueryContext(queryCtx, s.SQL, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %q: query: %w", s.Driver, s.Name, err)
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
Reference in New Issue
Block a user