diff --git a/opentdf-core-mode.yaml b/opentdf-core-mode.yaml index 98401d19ee..508b73ea2d 100644 --- a/opentdf-core-mode.yaml +++ b/opentdf-core-mode.yaml @@ -17,6 +17,7 @@ logger: # port: 5432 # user: postgres # password: changeme +# statement_timeout_seconds: 30 server: auth: enabled: false @@ -57,4 +58,4 @@ server: maxage: 3600 grpc: reflectionEnabled: true # Default is false - port: 8383 \ No newline at end of file + port: 8383 diff --git a/opentdf-dev.yaml b/opentdf-dev.yaml index 5a90cfa605..e021126959 100644 --- a/opentdf-dev.yaml +++ b/opentdf-dev.yaml @@ -10,6 +10,7 @@ logger: # password: changeme # sslmode: prefer # connect_timeout_seconds: 15 +# statement_timeout_seconds: 30 # pool: # max_connection_count: 4 # min_connection_count: 0 diff --git a/opentdf-example.yaml b/opentdf-example.yaml index 9bc9f90ef2..2c82108bea 100644 --- a/opentdf-example.yaml +++ b/opentdf-example.yaml @@ -10,6 +10,7 @@ db: # password: changeme # sslmode: prefer # connect_timeout_seconds: 15 +# statement_timeout_seconds: 30 # pool: # max_connection_count: 4 # min_connection_count: 0 diff --git a/service/pkg/db/db.go b/service/pkg/db/db.go index 366093a3d5..d3ea7cd04a 100644 --- a/service/pkg/db/db.go +++ b/service/pkg/db/db.go @@ -89,15 +89,16 @@ type PoolConfig struct { } type Config struct { - Host string `mapstructure:"host" json:"host" default:"localhost"` - Port int `mapstructure:"port" json:"port" default:"5432"` - Database string `mapstructure:"database" json:"database" default:"opentdf"` - User string `mapstructure:"user" json:"user" default:"postgres"` - Password string `mapstructure:"password" json:"password" default:"changeme"` - SSLMode string `mapstructure:"sslmode" json:"sslmode" default:"prefer"` - Schema string `mapstructure:"schema" json:"schema" default:"opentdf"` - ConnectTimeout int `mapstructure:"connect_timeout_seconds" json:"connect_timeout_seconds" default:"15"` - Pool PoolConfig `mapstructure:"pool" json:"pool"` + Host string `mapstructure:"host" json:"host" default:"localhost"` + Port int `mapstructure:"port" json:"port" default:"5432"` + Database string `mapstructure:"database" json:"database" default:"opentdf"` + User string `mapstructure:"user" json:"user" default:"postgres"` + Password string `mapstructure:"password" json:"password" default:"changeme"` + SSLMode string `mapstructure:"sslmode" json:"sslmode" default:"prefer"` + Schema string `mapstructure:"schema" json:"schema" default:"opentdf"` + ConnectTimeout int `mapstructure:"connect_timeout_seconds" json:"connect_timeout_seconds" default:"15"` + StatementTimeoutSeconds int `mapstructure:"statement_timeout_seconds" json:"statement_timeout_seconds"` + Pool PoolConfig `mapstructure:"pool" json:"pool"` RunMigrations bool `mapstructure:"runMigrations" json:"runMigrations" default:"true"` MigrationsFS *embed.FS `mapstructure:"-" json:"-"` @@ -114,6 +115,7 @@ func (c Config) LogValue() slog.Value { slog.String("sslmode", c.SSLMode), slog.String("schema", c.Schema), slog.Int("connect_timeout_seconds", c.ConnectTimeout), + slog.Int("statement_timeout_seconds", c.StatementTimeoutSeconds), slog.Group("pool", slog.Int("max_connection_count", int(c.Pool.MaxConns)), slog.Int("min_connection_count", int(c.Pool.MinConns)), @@ -250,6 +252,10 @@ func (c Config) buildConfig() (*pgxpool.Config, error) { parsed.MaxConnIdleTime = time.Duration(c.Pool.MaxConnIdleTime) * time.Second parsed.HealthCheckPeriod = time.Duration(c.Pool.HealthCheckPeriod) * time.Second + if c.StatementTimeoutSeconds > 0 { + parsed.ConnConfig.RuntimeParams["statement_timeout"] = fmt.Sprintf("%ds", c.StatementTimeoutSeconds) + } + // Configure the search_path schema immediately on connection opening parsed.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error { _, err := conn.Exec(ctx, "SET search_path TO "+pgx.Identifier{c.Schema}.Sanitize()) diff --git a/service/pkg/db/db_test.go b/service/pkg/db/db_test.go index 37d0dc4815..1697c8ccf1 100644 --- a/service/pkg/db/db_test.go +++ b/service/pkg/db/db_test.go @@ -9,8 +9,9 @@ import ( func Test_BuildConfig_ConnString(t *testing.T) { tests := []struct { - config *Config - want string + config *Config + want string + wantRuntimeParams map[string]string }{ { config: &Config{ @@ -63,6 +64,36 @@ func Test_BuildConfig_ConnString(t *testing.T) { }, want: "postgres://myuser:mypassword@myhost:1234/mydb?sslmode=require", }, + // Statement timeout should not be added as a runtime parameter unless configured. + { + config: &Config{ + Host: "localhost", + Port: 5432, + Database: "opentdf", + User: "postgres", + Password: "changeme", + SSLMode: "prefer", + }, + want: "postgres://postgres:changeme@localhost:5432/opentdf?sslmode=prefer", + wantRuntimeParams: map[string]string{ + "statement_timeout": "", + }, + }, + { + config: &Config{ + Host: "localhost", + Port: 5432, + Database: "opentdf", + User: "postgres", + Password: "changeme", + SSLMode: "prefer", + StatementTimeoutSeconds: 30, + }, + want: "postgres://postgres:changeme@localhost:5432/opentdf?sslmode=prefer", + wantRuntimeParams: map[string]string{ + "statement_timeout": "30s", + }, + }, } for _, test := range tests { @@ -71,5 +102,13 @@ func Test_BuildConfig_ConnString(t *testing.T) { assert.Equal(t, test.want, cfg.ConnString()) // AfterConnect hook was defined when building assert.NotNil(t, cfg.AfterConnect) + + for key, value := range test.wantRuntimeParams { + if value == "" { + assert.NotContains(t, cfg.ConnConfig.RuntimeParams, key) + continue + } + assert.Equal(t, value, cfg.ConnConfig.RuntimeParams[key]) + } } }