From e877ac3e616d453dce85df19047da3d52617b86f Mon Sep 17 00:00:00 2001
From: Garet Halliday <me@garet.holiday>
Date: Mon, 16 Oct 2023 15:28:30 -0500
Subject: [PATCH] a

---
 cmd/pggat/pgbouncer.go                       |  3 +-
 lib/gat/app.go                               |  5 +-
 lib/gat/gatcaddyfile/gattype.go              |  5 +-
 lib/gat/gatcaddyfile/handler.go              |  5 +-
 lib/gat/gatcaddyfile/pooler.go               | 14 +++--
 lib/gat/handlers/discovery/config.go         |  5 +-
 lib/gat/handlers/pgbouncer/module.go         |  3 +-
 lib/gat/handlers/pool/pools/basic/config.go  | 61 ++++++++++++++++++++
 lib/gat/handlers/pool/pools/basic/factory.go | 40 +++++++++++++
 lib/gat/handlers/pool/pools/basic/pool.go    | 49 ++++++++++++++++
 lib/util/dur/duration.go                     | 32 ----------
 11 files changed, 169 insertions(+), 53 deletions(-)
 create mode 100644 lib/gat/handlers/pool/pools/basic/config.go
 create mode 100644 lib/gat/handlers/pool/pools/basic/factory.go
 create mode 100644 lib/gat/handlers/pool/pools/basic/pool.go
 delete mode 100644 lib/util/dur/duration.go

diff --git a/cmd/pggat/pgbouncer.go b/cmd/pggat/pgbouncer.go
index 52dcf980..f6974738 100644
--- a/cmd/pggat/pgbouncer.go
+++ b/cmd/pggat/pgbouncer.go
@@ -11,7 +11,6 @@ import (
 
 	"gfx.cafe/gfx/pggat/lib/gat"
 	"gfx.cafe/gfx/pggat/lib/gat/handlers/pgbouncer"
-	"gfx.cafe/gfx/pggat/lib/util/dur"
 )
 
 func init() {
@@ -37,7 +36,7 @@ func runPgbouncer(flags caddycmd.Flags) (int, error) {
 	}
 
 	var pggat gat.Config
-	pggat.StatLogPeriod = dur.Duration(time.Second)
+	pggat.StatLogPeriod = caddy.Duration(time.Second)
 
 	var server gat.ServerConfig
 	server.Listen = config.Listen()
diff --git a/lib/gat/app.go b/lib/gat/app.go
index 2bb1a58f..b9126960 100644
--- a/lib/gat/app.go
+++ b/lib/gat/app.go
@@ -7,11 +7,10 @@ import (
 	"go.uber.org/zap"
 
 	"gfx.cafe/gfx/pggat/lib/gat/metrics"
-	"gfx.cafe/gfx/pggat/lib/util/dur"
 )
 
 type Config struct {
-	StatLogPeriod dur.Duration   `json:"stat_log_period,omitempty"`
+	StatLogPeriod caddy.Duration `json:"stat_log_period,omitempty"`
 	Servers       []ServerConfig `json:"servers,omitempty"`
 }
 
@@ -56,7 +55,7 @@ func (T *App) Provision(ctx caddy.Context) error {
 }
 
 func (T *App) statLogLoop() {
-	t := time.NewTicker(T.StatLogPeriod.Duration())
+	t := time.NewTicker(time.Duration(T.StatLogPeriod))
 	defer t.Stop()
 
 	var stats metrics.Server
diff --git a/lib/gat/gatcaddyfile/gattype.go b/lib/gat/gatcaddyfile/gattype.go
index c1eb3ea8..6e2e5073 100644
--- a/lib/gat/gatcaddyfile/gattype.go
+++ b/lib/gat/gatcaddyfile/gattype.go
@@ -12,7 +12,6 @@ import (
 	"gfx.cafe/gfx/pggat/lib/gat"
 	"gfx.cafe/gfx/pggat/lib/gat/matchers"
 	"gfx.cafe/gfx/pggat/lib/gat/ssl/servers/self_signed"
-	"gfx.cafe/gfx/pggat/lib/util/dur"
 )
 
 func init() {
@@ -27,7 +26,7 @@ func (ServerType) Setup(blocks []caddyfile.ServerBlock, m map[string]any) (*cadd
 
 	app := gat.App{
 		Config: gat.Config{
-			StatLogPeriod: dur.Duration(1 * time.Minute),
+			StatLogPeriod: caddy.Duration(1 * time.Minute),
 		},
 	}
 
@@ -51,7 +50,7 @@ func (ServerType) Setup(blocks []caddyfile.ServerBlock, m map[string]any) (*cadd
 						return nil, nil, d.WrapErr(err)
 					}
 
-					app.StatLogPeriod = dur.Duration(period)
+					app.StatLogPeriod = caddy.Duration(period)
 				default:
 					return nil, nil, d.SyntaxErr("global options")
 				}
diff --git a/lib/gat/gatcaddyfile/handler.go b/lib/gat/gatcaddyfile/handler.go
index 27b31788..06649368 100644
--- a/lib/gat/gatcaddyfile/handler.go
+++ b/lib/gat/gatcaddyfile/handler.go
@@ -24,7 +24,6 @@ import (
 	"gfx.cafe/gfx/pggat/lib/gat/handlers/rewrite_password"
 	"gfx.cafe/gfx/pggat/lib/gat/handlers/rewrite_user"
 	"gfx.cafe/gfx/pggat/lib/gat/ssl/clients/insecure_skip_verify"
-	"gfx.cafe/gfx/pggat/lib/util/dur"
 	"gfx.cafe/gfx/pggat/lib/util/strutil"
 )
 
@@ -132,7 +131,7 @@ func init() {
 	RegisterDirective(Handler, "discovery", func(d *caddyfile.Dispenser, warnings *[]caddyconfig.Warning) (caddy.Module, error) {
 		module := discovery.Module{
 			Config: discovery.Config{
-				ReconcilePeriod: dur.Duration(5 * time.Minute),
+				ReconcilePeriod: caddy.Duration(5 * time.Minute),
 				Pooler: JSONModuleObject(
 					&rob.Factory{
 						ManagementConfig: defaultPoolManagementConfig,
@@ -185,7 +184,7 @@ func init() {
 					if err != nil {
 						return nil, d.WrapErr(err)
 					}
-					module.ReconcilePeriod = dur.Duration(val)
+					module.ReconcilePeriod = caddy.Duration(val)
 				case "discoverer":
 					if !d.NextArg() {
 						return nil, d.ArgErr()
diff --git a/lib/gat/gatcaddyfile/pooler.go b/lib/gat/gatcaddyfile/pooler.go
index bbaad5b9..b7f5bae8 100644
--- a/lib/gat/gatcaddyfile/pooler.go
+++ b/lib/gat/gatcaddyfile/pooler.go
@@ -9,13 +9,15 @@ import (
 
 	"gfx.cafe/gfx/pggat/lib/gat/pool"
 	"gfx.cafe/gfx/pggat/lib/util/dur"
+
+	"gfx.cafe/gfx/pggat/lib/gat/handlers/pool/poolers/lifo"
 	"gfx.cafe/gfx/pggat/lib/util/strutil"
 )
 
 var defaultPoolManagementConfig = pool.ManagementConfig{
-	ServerIdleTimeout:          dur.Duration(5 * time.Minute),
-	ServerReconnectInitialTime: dur.Duration(5 * time.Second),
-	ServerReconnectMaxTime:     dur.Duration(1 * time.Minute),
+	ServerIdleTimeout:          caddy.Duration(5 * time.Minute),
+	ServerReconnectInitialTime: caddy.Duration(5 * time.Second),
+	ServerReconnectMaxTime:     caddy.Duration(1 * time.Minute),
 	TrackedParameters: []strutil.CIString{
 		strutil.MakeCIString("client_encoding"),
 		strutil.MakeCIString("datestyle"),
@@ -50,7 +52,7 @@ func unmarshalPoolConfig(d *caddyfile.Dispenser) (pool.ManagementConfig, error)
 			if err != nil {
 				return config, d.WrapErr(err)
 			}
-			config.ServerIdleTimeout = dur.Duration(val)
+			config.ServerIdleTimeout = caddy.Duration(val)
 		case "reconnect":
 			if !d.NextArg() {
 				return config, d.ArgErr()
@@ -69,8 +71,8 @@ func unmarshalPoolConfig(d *caddyfile.Dispenser) (pool.ManagementConfig, error)
 				}
 			}
 
-			config.ServerReconnectInitialTime = dur.Duration(initialTime)
-			config.ServerReconnectMaxTime = dur.Duration(maxTime)
+			config.ServerReconnectInitialTime = caddy.Duration(initialTime)
+			config.ServerReconnectMaxTime = caddy.Duration(maxTime)
 		case "track":
 			if !d.NextArg() {
 				return config, d.ArgErr()
diff --git a/lib/gat/handlers/discovery/config.go b/lib/gat/handlers/discovery/config.go
index a8ff6304..056fa5a5 100644
--- a/lib/gat/handlers/discovery/config.go
+++ b/lib/gat/handlers/discovery/config.go
@@ -3,13 +3,14 @@ package discovery
 import (
 	"encoding/json"
 
+	"github.com/caddyserver/caddy/v2"
+
 	"gfx.cafe/gfx/pggat/lib/bouncer"
-	"gfx.cafe/gfx/pggat/lib/util/dur"
 )
 
 type Config struct {
 	// ReconcilePeriod is how often the module should check for changes. 0 = disable
-	ReconcilePeriod dur.Duration `json:"reconcile_period"`
+	ReconcilePeriod caddy.Duration `json:"reconcile_period"`
 
 	Discoverer json.RawMessage `json:"discoverer" caddy:"namespace=pggat.handlers.discovery.discoverers inline_key=discoverer"`
 
diff --git a/lib/gat/handlers/pgbouncer/module.go b/lib/gat/handlers/pgbouncer/module.go
index 36ba561e..0861af93 100644
--- a/lib/gat/handlers/pgbouncer/module.go
+++ b/lib/gat/handlers/pgbouncer/module.go
@@ -19,7 +19,6 @@ import (
 	"gfx.cafe/gfx/pggat/lib/fed"
 	"gfx.cafe/gfx/pggat/lib/gat/handlers/pool"
 	"gfx.cafe/gfx/pggat/lib/perror"
-	"gfx.cafe/gfx/pggat/lib/util/dur"
 	"gfx.cafe/gfx/pggat/lib/util/flip"
 	"gfx.cafe/gfx/pggat/lib/util/slices"
 
@@ -187,7 +186,7 @@ func (T *Module) tryCreate(user, database string) (poolAndCredentials, bool) {
 		strutil.MakeCIString("application_name"),
 	}, T.Config.PgBouncer.TrackExtraParameters...)
 
-	serverLoginRetry := dur.Duration(T.Config.PgBouncer.ServerLoginRetry * float64(time.Second))
+	serverLoginRetry := caddy.Duration(T.Config.PgBouncer.ServerLoginRetry * float64(time.Second))
 
 	var p poolAndCredentials
 	p.creds = creds
diff --git a/lib/gat/handlers/pool/pools/basic/config.go b/lib/gat/handlers/pool/pools/basic/config.go
new file mode 100644
index 00000000..493159d9
--- /dev/null
+++ b/lib/gat/handlers/pool/pools/basic/config.go
@@ -0,0 +1,61 @@
+package basic
+
+import (
+	"encoding/json"
+
+	"github.com/caddyserver/caddy/v2"
+	"go.uber.org/zap"
+
+	"gfx.cafe/gfx/pggat/lib/gat/handlers/pool"
+	"gfx.cafe/gfx/pggat/lib/util/strutil"
+)
+
+type ParameterStatusSync string
+
+const (
+	// ParameterStatusSyncNone does not attempt to sync parameter status.
+	ParameterStatusSyncNone ParameterStatusSync = ""
+	// ParameterStatusSyncInitial assumes both client and server have their initial status before syncing.
+	// Use in session pooling for lower latency
+	ParameterStatusSyncInitial = "initial"
+	// ParameterStatusSyncDynamic will track parameter status and ensure they are synced correctly.
+	// Use in transaction pooling
+	ParameterStatusSyncDynamic = "dynamic"
+)
+
+type Config struct {
+	RawPoolerFactory json.RawMessage `json:"pooler" caddy:"namespace=pggat.handlers.pool.poolers inline_key=pooler"`
+
+	PoolerFactory pool.PoolerFactory `json:"-"`
+
+	// ReleaseAfterTransaction toggles whether servers should be released and re acquired after each transaction.
+	// Use false for lower latency
+	// Use true for better balancing
+	ReleaseAfterTransaction bool `json:"release_after_transaction,omitempty"`
+
+	// ParameterStatusSync is the parameter syncing mode
+	ParameterStatusSync ParameterStatusSync `json:"parameter_status_sync,omitempty"`
+
+	// ExtendedQuerySync controls whether prepared statements and portals should be tracked and synced before use.
+	// Use false for lower latency
+	// Use true for transaction pooling
+	ExtendedQuerySync bool `json:"extended_query_sync,omitempty"`
+
+	ServerResetQuery string `json:"server_reset_query,omitempty"`
+
+	// ServerIdleTimeout defines how long a server may be idle before it is disconnected
+	ServerIdleTimeout caddy.Duration `json:"server_idle_timeout,omitempty"`
+
+	// ServerReconnectInitialTime defines how long to wait initially before attempting a server reconnect
+	// 0 = disable, don't retry
+	ServerReconnectInitialTime caddy.Duration `json:"server_reconnect_initial_time,omitempty"`
+
+	// ServerReconnectMaxTime defines the max amount of time to wait before attempting a server reconnect
+	// 0 = disable, back off infinitely
+	ServerReconnectMaxTime caddy.Duration `json:"server_reconnect_max_time,omitempty"`
+
+	// TrackedParameters are parameters which should be synced by updating the server, not the client.
+	TrackedParameters []strutil.CIString `json:"tracked_parameters,omitempty"`
+
+	Logger *zap.Logger `json:"-"`
+}
diff --git a/lib/gat/handlers/pool/pools/basic/factory.go b/lib/gat/handlers/pool/pools/basic/factory.go
new file mode 100644
index 00000000..2c03827a
--- /dev/null
+++ b/lib/gat/handlers/pool/pools/basic/factory.go
@@ -0,0 +1,40 @@
+package basic
+
+import (
+	"github.com/caddyserver/caddy/v2"
+
+	"gfx.cafe/gfx/pggat/lib/gat/handlers/pool"
+)
+
+type Factory struct {
+	Config
+}
+
+func (T *Factory) CaddyModule() caddy.ModuleInfo {
+	return caddy.ModuleInfo{
+		ID: "pggat.handlers.pool.pools.basic",
+		New: func() caddy.Module {
+			return new(Factory)
+		},
+	}
+}
+
+func (T *Factory) Provision(ctx caddy.Context) error {
+	T.Logger = ctx.Logger()
+
+	raw, err := ctx.LoadModule(T, "RawPoolerFactory")
+	if err != nil {
+		return err
+	}
+
+	T.PoolerFactory = raw.(pool.PoolerFactory)
+	return nil
+}
+
+func (T *Factory) NewPool() pool.Pool {
+	return NewPool(T.Config)
+}
+
+var _ pool.PoolFactory = (*Factory)(nil)
+var _ caddy.Module = (*Factory)(nil)
+var _ caddy.Provisioner = (*Factory)(nil)
diff --git a/lib/gat/handlers/pool/pools/basic/pool.go b/lib/gat/handlers/pool/pools/basic/pool.go
new file mode 100644
index 00000000..fde1ae0d
--- /dev/null
+++ b/lib/gat/handlers/pool/pools/basic/pool.go
@@ -0,0 +1,49 @@
+package basic
+
+import (
+	"gfx.cafe/gfx/pggat/lib/fed"
+	"gfx.cafe/gfx/pggat/lib/gat/handlers/pool"
+	"gfx.cafe/gfx/pggat/lib/gat/metrics"
+)
+
+type Pool struct {
+	config Config
+}
+
+func NewPool(config Config) *Pool {
+	return &Pool{
+		config: config,
+	}
+}
+
+func (T *Pool) AddRecipe(name string, recipe *pool.Recipe) {
+	// TODO implement me
+	panic("implement me")
+}
+
+func (T *Pool) RemoveRecipe(name string) {
+	// TODO implement me
+	panic("implement me")
+}
+
+func (T *Pool) Serve(conn *fed.Conn) error {
+	// TODO implement me
+	panic("implement me")
+}
+
+func (T *Pool) Cancel(key fed.BackendKey) {
+	// TODO implement me
+	panic("implement me")
+}
+
+func (T *Pool) ReadMetrics(m *metrics.Pool) {
+	// TODO implement me
+	panic("implement me")
+}
+
+func (T *Pool) Close() {
+	// TODO implement me
+	panic("implement me")
+}
+
+var _ pool.Pool = (*Pool)(nil)
diff --git a/lib/util/dur/duration.go b/lib/util/dur/duration.go
deleted file mode 100644
index b2847c62..00000000
--- a/lib/util/dur/duration.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package dur
-
-import (
-	"encoding/json"
-	"time"
-)
-
-type Duration time.Duration
-
-func (T *Duration) Duration() time.Duration {
-	return time.Duration(*T)
-}
-
-func (T *Duration) UnmarshalJSON(bytes []byte) error {
-	// try as string
-	var str string
-	if err := json.Unmarshal(bytes, &str); err == nil {
-		*(*time.Duration)(T), err = time.ParseDuration(str)
-		return err
-	}
-
-	// try num
-	var num int64
-	if err := json.Unmarshal(bytes, &num); err != nil {
-		return err
-	}
-	*T = Duration(num)
-
-	return nil
-}
-
-var _ json.Unmarshaler = (*Duration)(nil)
-- 
GitLab