diff --git a/pkg/config/dbmanager.go b/pkg/config/dbmanager.go index c56b8af..06b8167 100644 --- a/pkg/config/dbmanager.go +++ b/pkg/config/dbmanager.go @@ -55,6 +55,10 @@ type DBConnectionConfig struct { SSLMode string `mapstructure:"sslmode"` // disable, require, verify-ca, verify-full Schema string `mapstructure:"schema"` // Default schema + // ApplicationName identifies this client to the server (postgres + // application_name, mssql app name, mongodb appName) + ApplicationName string `mapstructure:"application_name"` + // SQLite specific FilePath string `mapstructure:"filepath"` diff --git a/pkg/dbmanager/README.md b/pkg/dbmanager/README.md index e49f9ea..e741b86 100644 --- a/pkg/dbmanager/README.md +++ b/pkg/dbmanager/README.md @@ -271,6 +271,7 @@ db, _ := mgr.GetDefaultDatabase() | `database` | string | Database name | | `sslmode` | string | SSL mode (postgres/mssql): `disable`, `require`, etc. | | `schema` | string | Default schema (postgres/mssql) | +| `application_name` | string | Client name shown by the server (postgres `application_name`, mssql `app name`, mongodb `appName`); defaults to `ResolveSpec` | | `filepath` | string | File path (sqlite only) | | `auth_source` | string | Auth source (mongodb) | | `replica_set` | string | Replica set name (mongodb) | diff --git a/pkg/dbmanager/config.go b/pkg/dbmanager/config.go index 2986708..1735d48 100644 --- a/pkg/dbmanager/config.go +++ b/pkg/dbmanager/config.go @@ -72,6 +72,9 @@ type ManagerConfig struct { EnableAutoReconnect bool `mapstructure:"enable_auto_reconnect"` } +// DefaultApplicationName is used when a connection does not set ApplicationName. +const DefaultApplicationName = "ResolveSpec" + // ConnectionConfig defines configuration for a single database connection type ConnectionConfig struct { // Name is the unique name of this connection @@ -95,6 +98,10 @@ type ConnectionConfig struct { SSLMode string `mapstructure:"sslmode"` // disable, require, verify-ca, verify-full Schema string `mapstructure:"schema"` // Default schema + // ApplicationName identifies this client to the server (postgres + // application_name, mssql app name, mongodb appName) + ApplicationName string `mapstructure:"application_name"` + // SQLite specific FilePath string `mapstructure:"filepath"` @@ -225,6 +232,10 @@ func (cc *ConnectionConfig) ApplyDefaults(global *ManagerConfig) { cc.ConnMaxIdleTime = &idleTime } + if cc.ApplicationName == "" { + cc.ApplicationName = DefaultApplicationName + } + // Default timeouts if cc.ConnectTimeout == 0 { cc.ConnectTimeout = 10 * time.Second @@ -347,6 +358,9 @@ func (cc *ConnectionConfig) buildPostgresDSN() string { if cc.Schema != "" { q.Set("search_path", cc.Schema) } + if cc.ApplicationName != "" { + q.Set("application_name", cc.ApplicationName) + } u := url.URL{ Scheme: "postgres", @@ -405,6 +419,9 @@ func (cc *ConnectionConfig) buildMSSQLDSN() string { if cc.Schema != "" { q.Set("schema", cc.Schema) } + if cc.ApplicationName != "" { + q.Set("app name", cc.ApplicationName) + } if cc.ConnectTimeout > 0 { sec := strconv.Itoa(int(cc.ConnectTimeout.Seconds())) q.Set("connection timeout", sec) @@ -437,6 +454,9 @@ func (cc *ConnectionConfig) buildMongoDSN() string { if cc.ReadPreference != "" { q.Set("readPreference", cc.ReadPreference) } + if cc.ApplicationName != "" { + q.Set("appName", cc.ApplicationName) + } u := url.URL{ Scheme: "mongodb", @@ -480,6 +500,7 @@ func FromConfig(cfg config.DBManagerConfig) ManagerConfig { Database: connCfg.Database, SSLMode: connCfg.SSLMode, Schema: connCfg.Schema, + ApplicationName: connCfg.ApplicationName, FilePath: connCfg.FilePath, AuthSource: connCfg.AuthSource, ReplicaSet: connCfg.ReplicaSet, @@ -519,6 +540,7 @@ func (cc *ConnectionConfig) GetConnMaxIdleTime() *time.Duration { return cc.Conn func (cc *ConnectionConfig) GetQueryTimeout() time.Duration { return cc.QueryTimeout } func (cc *ConnectionConfig) GetEnableMetrics() bool { return cc.EnableMetrics } func (cc *ConnectionConfig) GetReadPreference() string { return cc.ReadPreference } +func (cc *ConnectionConfig) GetApplicationName() string { return cc.ApplicationName } func (cc *ConnectionConfig) GetRetryAttempts() int { return cc.RetryAttempts } func (cc *ConnectionConfig) GetRetryDelay() time.Duration { return cc.RetryDelay } func (cc *ConnectionConfig) GetRetryMaxDelay() time.Duration { return cc.RetryMaxDelay } diff --git a/pkg/dbmanager/config_dsn_test.go b/pkg/dbmanager/config_dsn_test.go index 72afda7..eabf45d 100644 --- a/pkg/dbmanager/config_dsn_test.go +++ b/pkg/dbmanager/config_dsn_test.go @@ -107,3 +107,33 @@ func TestSQLiteMemoryPoolPinned(t *testing.T) { } } } + +func TestApplicationNameInDSN(t *testing.T) { + cc := ConnectionConfig{Host: "h", Port: 1, Database: "d", ApplicationName: "my app&x=y"} + for name, c := range map[string]struct{ dsn, key string }{ + "postgres": {cc.buildPostgresDSN(), "application_name"}, + "mssql": {cc.buildMSSQLDSN(), "app name"}, + "mongo": {cc.buildMongoDSN(), "appName"}, + } { + u, err := url.Parse(c.dsn) + if err != nil { + t.Fatalf("%s: %v", name, err) + } + if got := u.Query().Get(c.key); got != cc.ApplicationName { + t.Errorf("%s: %s = %q, want %q", name, c.key, got, cc.ApplicationName) + } + } +} + +func TestApplicationNameDefault(t *testing.T) { + cc := ConnectionConfig{Type: DatabaseTypePostgreSQL, Host: "h", Database: "d"} + cc.ApplyDefaults(nil) + if cc.ApplicationName != "ResolveSpec" { + t.Errorf("ApplicationName = %q, want ResolveSpec", cc.ApplicationName) + } + cc = ConnectionConfig{Type: DatabaseTypePostgreSQL, Host: "h", Database: "d", ApplicationName: "x"} + cc.ApplyDefaults(nil) + if cc.ApplicationName != "x" { + t.Errorf("explicit ApplicationName overwritten: %q", cc.ApplicationName) + } +} diff --git a/pkg/dbmanager/providers/pgconnector.go b/pkg/dbmanager/providers/pgconnector.go index 0dcb556..33730d2 100644 --- a/pkg/dbmanager/providers/pgconnector.go +++ b/pkg/dbmanager/providers/pgconnector.go @@ -123,6 +123,12 @@ func buildPGXConfig(cfg ConnectionConfig) (*pgx.ConnConfig, error) { } cc.DialFunc = newDialFunc(cfg.GetConnectTimeout()) + // Also applies to caller-supplied DSNs that do not set application_name. + if name := cfg.GetApplicationName(); name != "" { + if _, set := cc.RuntimeParams["application_name"]; !set { + cc.RuntimeParams["application_name"] = name + } + } if cfg.GetQueryTimeout() > 0 { if _, set := cc.RuntimeParams["statement_timeout"]; !set { cc.RuntimeParams["statement_timeout"] = fmt.Sprintf("%d", cfg.GetQueryTimeout().Milliseconds()) diff --git a/pkg/dbmanager/providers/provider.go b/pkg/dbmanager/providers/provider.go index cdf964b..31b05c6 100644 --- a/pkg/dbmanager/providers/provider.go +++ b/pkg/dbmanager/providers/provider.go @@ -59,6 +59,7 @@ type ConnectionConfig interface { GetConnMaxLifetime() *time.Duration GetConnMaxIdleTime() *time.Duration GetReadPreference() string + GetApplicationName() string GetRetryAttempts() int GetRetryDelay() time.Duration GetRetryMaxDelay() time.Duration