Added support for Postgres polling sources
This commit is contained in:
@@ -4,18 +4,15 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.maximumdirect.net/ejr/feedkit/config"
|
||||
"gitea.maximumdirect.net/ejr/feedkit/event"
|
||||
_ "github.com/lib/pq"
|
||||
pgconn "gitea.maximumdirect.net/ejr/feedkit/internal/postgres"
|
||||
)
|
||||
|
||||
const postgresInitTimeout = 5 * time.Second
|
||||
|
||||
type postgresTx interface {
|
||||
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
|
||||
Commit() error
|
||||
@@ -73,8 +70,8 @@ func (w *sqlTxWrapper) Rollback() error {
|
||||
return w.tx.Rollback()
|
||||
}
|
||||
|
||||
var openPostgresDB = func(dsn string) (postgresDB, error) {
|
||||
db, err := sql.Open("postgres", dsn)
|
||||
var openPostgresDB = func(ctx context.Context, cfg pgconn.ConnConfig) (postgresDB, error) {
|
||||
db, err := pgconn.Open(ctx, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -111,12 +108,11 @@ func NewPostgresSinkFromConfig(cfg config.SinkConfig, schemaDef PostgresSchema)
|
||||
return nil, fmt.Errorf("postgres sink %q: compile schema: %w", cfg.Name, err)
|
||||
}
|
||||
|
||||
dsn, err := buildPostgresDSN(uri, username, password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("postgres sink %q: build dsn: %w", cfg.Name, err)
|
||||
}
|
||||
|
||||
db, err := openPostgresDB(dsn)
|
||||
db, err := openPostgresDB(context.Background(), pgconn.ConnConfig{
|
||||
URI: uri,
|
||||
Username: username,
|
||||
Password: password,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("postgres sink %q: open db: %w", cfg.Name, err)
|
||||
}
|
||||
@@ -264,13 +260,9 @@ func (p *PostgresSink) PruneAllOlderThan(ctx context.Context, cutoff time.Time)
|
||||
}
|
||||
|
||||
func (p *PostgresSink) initialize() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), postgresInitTimeout)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := p.db.PingContext(ctx); err != nil {
|
||||
return fmt.Errorf("postgres sink %q: ping db: %w", p.name, err)
|
||||
}
|
||||
|
||||
for _, tableName := range p.schema.tableOrder {
|
||||
tbl := p.schema.tables[tableName]
|
||||
|
||||
@@ -302,21 +294,6 @@ func (p *PostgresSink) lookupTable(table string) (postgresTableCompiled, error)
|
||||
return tbl, nil
|
||||
}
|
||||
|
||||
func buildPostgresDSN(uri, username, password string) (string, error) {
|
||||
u, err := url.Parse(strings.TrimSpace(uri))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid uri: %w", err)
|
||||
}
|
||||
if u.Scheme == "" {
|
||||
return "", fmt.Errorf("invalid uri: missing scheme")
|
||||
}
|
||||
if u.Host == "" {
|
||||
return "", fmt.Errorf("invalid uri: missing host")
|
||||
}
|
||||
u.User = url.UserPassword(username, password)
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
func parsePostgresPruneWindow(cfg config.SinkConfig) (time.Duration, error) {
|
||||
raw, ok := cfg.Params["prune"]
|
||||
if !ok || raw == nil {
|
||||
|
||||
@@ -4,13 +4,13 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.maximumdirect.net/ejr/feedkit/config"
|
||||
"gitea.maximumdirect.net/ejr/feedkit/event"
|
||||
pgconn "gitea.maximumdirect.net/ejr/feedkit/internal/postgres"
|
||||
)
|
||||
|
||||
type fakeResult struct {
|
||||
@@ -223,10 +223,13 @@ func TestPostgresFactoryBuildsMultipleSinksWithSameSchema(t *testing.T) {
|
||||
withPostgresTestState(t)
|
||||
|
||||
dbs := []*fakeDB{{}, {}}
|
||||
var gotDSNs []string
|
||||
openPostgresDB = func(dsn string) (postgresDB, error) {
|
||||
gotDSNs = append(gotDSNs, dsn)
|
||||
db := dbs[len(gotDSNs)-1]
|
||||
var gotCfgs []pgconn.ConnConfig
|
||||
openPostgresDB = func(ctx context.Context, cfg pgconn.ConnConfig) (postgresDB, error) {
|
||||
gotCfgs = append(gotCfgs, cfg)
|
||||
db := dbs[len(gotCfgs)-1]
|
||||
if err := db.PingContext(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
@@ -252,8 +255,11 @@ func TestPostgresFactoryBuildsMultipleSinksWithSameSchema(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if len(gotDSNs) != 2 {
|
||||
t.Fatalf("len(gotDSNs) = %d, want 2", len(gotDSNs))
|
||||
if len(gotCfgs) != 2 {
|
||||
t.Fatalf("len(gotCfgs) = %d, want 2", len(gotCfgs))
|
||||
}
|
||||
if gotCfgs[0].Username != "user" || gotCfgs[0].Password != "pass" {
|
||||
t.Fatalf("first ConnConfig = %+v", gotCfgs[0])
|
||||
}
|
||||
for i, db := range dbs {
|
||||
if db.pingCalls != 1 {
|
||||
@@ -327,9 +333,12 @@ func TestNewPostgresSinkFromConfigEagerInit(t *testing.T) {
|
||||
withPostgresTestState(t)
|
||||
|
||||
db := &fakeDB{}
|
||||
var gotDSN string
|
||||
openPostgresDB = func(dsn string) (postgresDB, error) {
|
||||
gotDSN = dsn
|
||||
var gotCfg pgconn.ConnConfig
|
||||
openPostgresDB = func(ctx context.Context, cfg pgconn.ConnConfig) (postgresDB, error) {
|
||||
gotCfg = cfg
|
||||
if err := db.PingContext(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
@@ -362,16 +371,14 @@ func TestNewPostgresSinkFromConfigEagerInit(t *testing.T) {
|
||||
t.Fatalf("unexpected create index query: %s", db.execCalls[1].query)
|
||||
}
|
||||
|
||||
u, err := url.Parse(gotDSN)
|
||||
if err != nil {
|
||||
t.Fatalf("parse dsn: %v", err)
|
||||
if gotCfg.URI != "postgres://db.example.local:5432/feedkit?sslmode=disable" {
|
||||
t.Fatalf("URI = %q", gotCfg.URI)
|
||||
}
|
||||
if u.User == nil || u.User.Username() != "app_user" {
|
||||
t.Fatalf("dsn missing username: %q", gotDSN)
|
||||
if gotCfg.Username != "app_user" {
|
||||
t.Fatalf("Username = %q, want app_user", gotCfg.Username)
|
||||
}
|
||||
pass, ok := u.User.Password()
|
||||
if !ok || pass != "app_pass" {
|
||||
t.Fatalf("dsn missing password: %q", gotDSN)
|
||||
if gotCfg.Password != "app_pass" {
|
||||
t.Fatalf("Password = %q, want app_pass", gotCfg.Password)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,7 +386,7 @@ func TestNewPostgresSinkFromConfigInitFailureClosesDB(t *testing.T) {
|
||||
withPostgresTestState(t)
|
||||
|
||||
db := &fakeDB{execErrOnCall: 1, execErr: errors.New("ddl failed")}
|
||||
openPostgresDB = func(_ string) (postgresDB, error) {
|
||||
openPostgresDB = func(_ context.Context, _ pgconn.ConnConfig) (postgresDB, error) {
|
||||
return db, nil
|
||||
}
|
||||
|
||||
@@ -415,7 +422,7 @@ func TestNewPostgresSinkFromConfigPruneParamAccepted(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
withPostgresTestState(t)
|
||||
|
||||
openPostgresDB = func(_ string) (postgresDB, error) {
|
||||
openPostgresDB = func(_ context.Context, _ pgconn.ConnConfig) (postgresDB, error) {
|
||||
return &fakeDB{}, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user