// Copyright 2024 Dolthub, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cfgdetails import ( "path/filepath" "strings" "time" "unicode" "unicode/utf8" "github.com/dolthub/dolt/go/libraries/doltcore/env" doltservercfg "github.com/dolthub/dolt/go/libraries/doltcore/servercfg" "github.com/dolthub/go-mysql-server/sql" "gopkg.in/yaml.v2" ) const ( maxConnectionsKey = "max_connections" readTimeoutKey = "net_read_timeout" writeTimeoutKey = "net_write_timeout" eventSchedulerKey = "event_scheduler" OverrideDataDirKey = "data_dir" ) const ( LogLevel_Trace = "trace" LogLevel_Debug = "debug" LogLevel_Info = "info" LogLevel_Warning = "warn" LogLevel_Error = "error" LogLevel_Fatal = "fatal" LogLevel_Panic = "panic" ) const ( DefaultHost = "localhost" DefaultPort = 5432 DefaultUser = "postgres" DefaultPass = "password" DefaultTimeout = 8 * 60 * 60 * 1000 // 8 hours, same as MySQL DefaultReadOnly = false DefaultLogLevel = LogLevel_Info DefaultDoltTransactionCommit = false DefaultMaxConnections = 100 DefaultDataDir = "." DefaultCfgDir = ".doltcfg" DefaultPrivilegeFilePath = "privileges.db" DefaultAuthFilePath = "auth.db" DefaultBranchControlFilePath = "branch_control.db" DefaultMetricsHost = "" DefaultMetricsPort = -1 DefaultAllowCleartextPasswords = false DefaultPostgresUnixSocketFilePath = "/tmp/.s.PGSQL.5432" DefaultMaxLoggedQueryLen = 0 DefaultEncodeLoggedQuery = false ) // DOLTGRES_DATA_DIR is an environment variable that defines the location of DoltgreSQL databases const DOLTGRES_DATA_DIR = "DOLTGRES_DATA_DIR" // DOLTGRES_DATA_DIR_DEFAULT is the portion to append to the user's home directory if DOLTGRES_DATA_DIR has not been specified const DOLTGRES_DATA_DIR_DEFAULT = "doltgres/databases" var ConfigHelp = "Supported fields in the config.yaml file, and their default values, " + "are as follows:\n\n" + InternalDefaultServerConfig().String() type PostgresReplicationConfig struct { PostgresServerAddress *string `yaml:"postgres_server_address,omitempty" minver:"0.7.4"` PostgresUser *string `yaml:"postgres_user,omitempty" minver:"0.7.4"` PostgresPassword *string `yaml:"postgres_password,omitempty" minver:"0.7.4"` PostgresDatabase *string `yaml:"postgres_database,omitempty" minver:"0.7.4"` PostgresPort *int `yaml:"postgres_port,omitempty" minver:"0.7.4"` SlotName *string `yaml:"slot_name,omitempty" minver:"0.7.4"` } // DoltgresBehaviorConfig contains server configuration regarding how the server should behave type DoltgresBehaviorConfig struct { ReadOnly *bool `yaml:"read_only,omitempty" minver:"0.7.4"` // Disable processing CLIENT_MULTI_STATEMENTS support on the // sql server. Dolt's handling of CLIENT_MULTI_STATEMENTS is currently // broken. If a client advertises to support it (mysql cli client // does), and then sends statements that contain embedded unquoted ';'s // (such as a CREATE TRIGGER), then those incoming queries will be // misprocessed. DisableClientMultiStatements *bool `yaml:"disable_client_multi_statements,omitempty" minver:"0.7.4"` // DoltTransactionCommit enables the @@dolt_transaction_commit system variable, which // automatically creates a Dolt commit when any SQL transaction is committed. DoltTransactionCommit *bool `yaml:"dolt_transaction_commit,omitempty" minver:"0.7.4"` } // DoltgresAutoGCBehavior implements Dolt's doltservercfg.AutoGCBehavior. type DoltgresAutoGCBehavior struct { } func (DoltgresAutoGCBehavior) Enable() bool { return false } func (DoltgresAutoGCBehavior) ArchiveLevel() int { return 0 } func (DoltgresAutoGCBehavior) IncrementalFileSize() uint64 { return 0 } type DoltgresUserConfig struct { Name *string `yaml:"name,omitempty" minver:"0.7.4"` Password *string `yaml:"password,omitempty" minver:"0.7.4"` } // DoltgresListenerConfig contains information on the network connection that the server will open type DoltgresListenerConfig struct { HostStr *string `yaml:"host,omitempty" minver:"0.7.4"` PortNumber *int `yaml:"port,omitempty" minver:"0.7.4"` ReadTimeoutMillis *uint64 `yaml:"read_timeout_millis,omitempty" minver:"0.7.4"` WriteTimeoutMillis *uint64 `yaml:"write_timeout_millis,omitempty" minver:"0.7.4"` // TLSKey is a file system path to an unencrypted private TLS key in PEM format. TLSKey *string `yaml:"tls_key,omitempty" minver:"0.7.4"` // TLSCert is a file system path to a TLS certificate chain in PEM format. TLSCert *string `yaml:"tls_cert,omitempty" minver:"0.7.4"` // RequireSecureTransport can enable a mode where non-TLS connections are turned away. RequireSecureTransport *bool `yaml:"require_secure_transport,omitempty" minver:"0.7.4"` // AllowCleartextPasswords enables use of cleartext passwords. AllowCleartextPasswords *bool `yaml:"allow_cleartext_passwords,omitempty" minver:"0.7.4"` // Socket is unix socket file path Socket *string `yaml:"socket,omitempty" minver:"0.7.4"` } // DoltgresPerformanceConfig contains configuration parameters for performance tweaking type DoltgresPerformanceConfig struct { QueryParallelism *int `yaml:"query_parallelism,omitempty" minver:"0.7.4"` } type DoltgesMetricsConfig struct { Labels map[string]string `yaml:"labels,omitempty" minver:"0.7.4"` Host *string `yaml:"host,omitempty" minver:"0.7.4"` Port *int `yaml:"port,omitempty" minver:"0.7.4"` TlsCert *string `yaml:"tls_cert,omitempty" minver:"0.53.5"` TlsKey *string `yaml:"tls_key,omitempty" minver:"0.53.5"` TlsCa *string `yaml:"tls_ca,omitempty" minver:"0.53.5"` } type DoltgresRemotesapiConfig struct { Port *int `yaml:"port,omitempty" minver:"0.7.4"` ReadOnly *bool `yaml:"read_only,omitempty" minver:"0.7.4"` } type DoltgresUserSessionVars struct { Name string `yaml:"name"` Vars map[string]interface{} `yaml:"vars,omitempty"` } // DoltgresConfig is the internal Doltgres configuration file implementation. This should not be used directly, and // instead `servercfg.DoltgresConfig` should be used. type DoltgresConfig struct { LogLevelStr *string `yaml:"log_level,omitempty" minver:"0.7.4"` MaxLenInLogs *int `yaml:"max_query_len_in_logs,omitempty" minver:"0.7.4"` EncodeLoggedQuery *bool `yaml:"encode_logged_query,omitempty" minver:"0.7.4"` BehaviorConfig *DoltgresBehaviorConfig `yaml:"behavior,omitempty" minver:"0.7.4"` UserConfig *DoltgresUserConfig `yaml:"user,omitempty" minver:"0.7.4"` ListenerConfig *DoltgresListenerConfig `yaml:"listener,omitempty" minver:"0.7.4"` PerformanceConfig *DoltgresPerformanceConfig `yaml:"performance,omitempty" minver:"0.7.4"` DataDirStr *string `yaml:"data_dir,omitempty" minver:"0.7.4"` CfgDirStr *string `yaml:"cfg_dir,omitempty" minver:"0.7.4"` MetricsConfig *DoltgesMetricsConfig `yaml:"metrics,omitempty" minver:"0.7.4"` RemotesapiConfig *DoltgresRemotesapiConfig `yaml:"remotesapi,omitempty" minver:"0.7.4"` PrivilegeFile *string `yaml:"privilege_file,omitempty" minver:"0.7.4"` AuthFile *string `yaml:"auth_file,omitempty" minver:"0.54.4"` BranchControlFile *string `yaml:"branch_control_file,omitempty" minver:"0.7.4"` // TODO: Rename to UserVars_ Vars []DoltgresUserSessionVars `yaml:"user_session_vars,omitempty" minver:"0.7.4"` SystemVariables map[string]interface{} `yaml:"system_variables,omitempty" minver:"0.7.4"` Jwks []doltservercfg.JwksConfig `yaml:"jwks,omitempty" minver:"0.7.4"` GoldenMysqlConn *string `yaml:"golden_mysql_conn,omitempty" minver:"0.7.4"` PostgresReplicationConfig *PostgresReplicationConfig `yaml:"postgres_replication,omitempty" minver:"0.7.4"` // ClusterConfig mirrors Dolt's cluster replication configuration. Cluster // replication is not yet implemented in Doltgres; this is accepted so that // cluster config files parse (the integration tests for cluster // replication are ported but skipped until the feature lands). The shape // mirrors Dolt's ClusterYAMLConfig. ClusterCfg *DoltgresClusterConfig `yaml:"cluster,omitempty" minver:"TBD"` } // DoltgresClusterConfig mirrors Dolt's cluster replication configuration so // that ported cluster config files parse under UnmarshalStrict. These fields // are accepted but not yet wired into the server. type DoltgresClusterConfig struct { StandbyRemotes []DoltgresStandbyRemoteConfig `yaml:"standby_remotes"` BootstrapRole string `yaml:"bootstrap_role"` BootstrapEpoch int `yaml:"bootstrap_epoch"` RemotesAPI DoltgresClusterRemotesAPIConfig `yaml:"remotesapi"` } type DoltgresStandbyRemoteConfig struct { Name string `yaml:"name"` RemoteURLTemplate string `yaml:"remote_url_template"` } type DoltgresClusterRemotesAPIConfig struct { Addr string `yaml:"address"` Port int `yaml:"port"` TLSKey string `yaml:"tls_key"` TLSCert string `yaml:"tls_cert"` TLSCA string `yaml:"tls_ca"` URLMatches []string `yaml:"server_name_urls"` DNSMatches []string `yaml:"server_name_dns"` } var _ doltservercfg.ServerConfig = (*DoltgresConfig)(nil) // Ptr is a helper function that returns a pointer to the value passed in. This is necessary to e.g. get a pointer to // a const value without assigning to an intermediate variable. func Ptr[T any](v T) *T { return &v } func (cfg *DoltgresConfig) AutoCommit() bool { return true } func (cfg *DoltgresConfig) DoltTransactionCommit() bool { if cfg.BehaviorConfig == nil || cfg.BehaviorConfig.DoltTransactionCommit == nil { return false } return *cfg.BehaviorConfig.DoltTransactionCommit } func (cfg *DoltgresConfig) DataDir() string { if cfg.DataDirStr == nil { return "" } return *cfg.DataDirStr } func (cfg *DoltgresConfig) CfgDir() string { if cfg.CfgDirStr == nil { return "" } return *cfg.CfgDirStr } func (cfg *DoltgresConfig) Host() string { if cfg.ListenerConfig == nil || cfg.ListenerConfig.HostStr == nil { return "localhost" } return *cfg.ListenerConfig.HostStr } func (cfg *DoltgresConfig) Port() int { if cfg.ListenerConfig == nil || cfg.ListenerConfig.PortNumber == nil { return 5432 } return *cfg.ListenerConfig.PortNumber } func (cfg *DoltgresConfig) User() string { if cfg.UserConfig == nil || cfg.UserConfig.Name == nil { return "postgres" } return *cfg.UserConfig.Name } func (cfg *DoltgresConfig) UserIsSpecified() bool { return cfg.UserConfig != nil && cfg.UserConfig.Name != nil } func (cfg *DoltgresConfig) Password() string { if cfg.UserConfig == nil || cfg.UserConfig.Password == nil { return "password" } return *cfg.UserConfig.Password } func (cfg *DoltgresConfig) ReadTimeout() uint64 { if cfg.ListenerConfig == nil || cfg.ListenerConfig.ReadTimeoutMillis == nil { return 0 } return *cfg.ListenerConfig.ReadTimeoutMillis } func (cfg *DoltgresConfig) WriteTimeout() uint64 { if cfg.ListenerConfig == nil || cfg.ListenerConfig.WriteTimeoutMillis == nil { return 0 } return *cfg.ListenerConfig.WriteTimeoutMillis } func (cfg *DoltgresConfig) ReadOnly() bool { if cfg.BehaviorConfig == nil || cfg.BehaviorConfig.ReadOnly == nil { return false } return *cfg.BehaviorConfig.ReadOnly } func (cfg *DoltgresConfig) LogLevel() doltservercfg.LogLevel { if cfg.LogLevelStr == nil { return doltservercfg.LogLevel_Info } switch *cfg.LogLevelStr { case LogLevel_Trace: return doltservercfg.LogLevel_Trace case LogLevel_Debug: return doltservercfg.LogLevel_Debug case LogLevel_Info: return doltservercfg.LogLevel_Info case LogLevel_Warning: return doltservercfg.LogLevel_Warning case LogLevel_Error: return doltservercfg.LogLevel_Error case LogLevel_Fatal: return doltservercfg.LogLevel_Fatal case LogLevel_Panic: return doltservercfg.LogLevel_Panic default: return doltservercfg.LogLevel_Info } } func (cfg *DoltgresConfig) LogFormat() doltservercfg.LogFormat { return doltservercfg.LogFormat_Text } func (cfg *DoltgresConfig) MaxConnections() uint64 { return 0 } func (cfg *DoltgresConfig) MaxWaitConnections() uint32 { return 0 } func (cfg *DoltgresConfig) MaxWaitConnectionsTimeout() time.Duration { return 0 } func (cfg *DoltgresConfig) CACert() string { // TODO: add support for specifying a CA server and configuration TLS for client cert authentication return "" } func (cfg *DoltgresConfig) TLSKey() string { if cfg.ListenerConfig == nil || cfg.ListenerConfig.TLSKey == nil { return "" } return *cfg.ListenerConfig.TLSKey } func (cfg *DoltgresConfig) TLSCert() string { if cfg.ListenerConfig == nil || cfg.ListenerConfig.TLSCert == nil { return "" } return *cfg.ListenerConfig.TLSCert } func (cfg *DoltgresConfig) RequireSecureTransport() bool { if cfg.ListenerConfig == nil || cfg.ListenerConfig.RequireSecureTransport == nil { return false } return *cfg.ListenerConfig.RequireSecureTransport } func (cfg *DoltgresConfig) RequireClientCert() bool { // TODO: add support for verifying client certs return false } func (cfg *DoltgresConfig) MaxLoggedQueryLen() int { if cfg.MaxLenInLogs == nil { return 1000 } return *cfg.MaxLenInLogs } func (cfg *DoltgresConfig) ShouldEncodeLoggedQuery() bool { if cfg.EncodeLoggedQuery == nil { return false } return *cfg.EncodeLoggedQuery } func (cfg *DoltgresConfig) DisableClientMultiStatements() bool { if cfg.BehaviorConfig == nil || cfg.BehaviorConfig.DisableClientMultiStatements == nil { return false } return *cfg.BehaviorConfig.DisableClientMultiStatements } func (cfg *DoltgresConfig) MetricsLabels() map[string]string { if cfg.MetricsConfig == nil { return nil } return cfg.MetricsConfig.Labels } func (cfg *DoltgresConfig) MetricsHost() string { if cfg.MetricsConfig == nil || cfg.MetricsConfig.Host == nil { return "" } return *cfg.MetricsConfig.Host } func (cfg *DoltgresConfig) MetricsPort() int { if cfg.MetricsConfig == nil || cfg.MetricsConfig.Port == nil { return 0 } return *cfg.MetricsConfig.Port } func (cfg *DoltgresConfig) MetricsTLSCert() string { if cfg.MetricsConfig.TlsCert == nil { return "" } return *cfg.MetricsConfig.TlsCert } func (cfg *DoltgresConfig) MetricsTLSKey() string { if cfg.MetricsConfig.TlsKey == nil { return "" } return *cfg.MetricsConfig.TlsKey } func (cfg *DoltgresConfig) MetricsTLSCA() string { if cfg.MetricsConfig.TlsCa == nil { return "" } return *cfg.MetricsConfig.TlsCa } func (cfg *DoltgresConfig) MetricsJwksConfig() *doltservercfg.JwksConfig { return nil } func (cfg *DoltgresConfig) MetricsJWTRequiredForLocalhost() bool { return false } func (cfg *DoltgresConfig) PrivilegeFilePath() string { if cfg.PrivilegeFile == nil { return "" } return *cfg.PrivilegeFile } func (cfg *DoltgresConfig) AuthFilePath() string { if cfg.AuthFile == nil { return "" } return *cfg.AuthFile } func (cfg *DoltgresConfig) BranchControlFilePath() string { if cfg.BranchControlFile == nil { return "" } return *cfg.BranchControlFile } func (cfg *DoltgresConfig) UserVars() []doltservercfg.UserSessionVars { var userVars []doltservercfg.UserSessionVars for _, uv := range cfg.Vars { userVars = append(userVars, doltservercfg.UserSessionVars{ Name: uv.Name, Vars: uv.Vars, }) } return userVars } func (cfg *DoltgresConfig) SystemVars() map[string]interface{} { if cfg.SystemVariables == nil { return map[string]interface{}{} } return cfg.SystemVariables } func (cfg *DoltgresConfig) JwksConfig() []doltservercfg.JwksConfig { return cfg.Jwks } func (cfg *DoltgresConfig) AllowCleartextPasswords() bool { if cfg.ListenerConfig == nil || cfg.ListenerConfig.AllowCleartextPasswords == nil { return false } return *cfg.ListenerConfig.AllowCleartextPasswords } func (cfg *DoltgresConfig) Socket() string { if cfg.ListenerConfig == nil || cfg.ListenerConfig.Socket == nil { return "" } return *cfg.ListenerConfig.Socket } func (cfg *DoltgresConfig) RemotesapiPort() *int { if cfg.RemotesapiConfig == nil { return nil } return cfg.RemotesapiConfig.Port } func (cfg *DoltgresConfig) RemotesapiReadOnly() *bool { if cfg.RemotesapiConfig == nil { return nil } return cfg.RemotesapiConfig.ReadOnly } // MCPPort returns nil for Doltgres; MCP HTTP server is not configured here. func (cfg *DoltgresConfig) MCPPort() *int { return nil } // MCPUser returns nil for Doltgres; MCP is not configured here. func (cfg *DoltgresConfig) MCPUser() *string { return nil } // MCPPassword returns nil for Doltgres; MCP is not configured here. func (cfg *DoltgresConfig) MCPPassword() *string { return nil } // MCPDatabase returns nil for Doltgres; MCP is not configured here. func (cfg *DoltgresConfig) MCPDatabase() *string { return nil } func (cfg *DoltgresConfig) ClusterConfig() doltservercfg.ClusterConfig { return nil } func (cfg *DoltgresConfig) EventSchedulerStatus() string { return "OFF" } func (cfg *DoltgresConfig) AutoGCBehavior() doltservercfg.AutoGCBehavior { return DoltgresAutoGCBehavior{} } func (cfg *DoltgresConfig) BranchActivityTracking() bool { // TODO In Dolt we require branch activity tracking to be configurable because it does incur a performance cost. // We could make this configurable in Doltgres as well if we need to shave a couple percent off performance. return true } func (cfg *DoltgresConfig) ValueSet(value string) bool { switch value { case readTimeoutKey: return cfg.ListenerConfig != nil && cfg.ListenerConfig.ReadTimeoutMillis != nil case writeTimeoutKey: return cfg.ListenerConfig != nil && cfg.ListenerConfig.WriteTimeoutMillis != nil case maxConnectionsKey: return false case eventSchedulerKey: return false } return false } func (*DoltgresConfig) Overrides() sql.EngineOverrides { panic("Calling Overrides() on the internal DoltgresConfig should not be possible") } func (cfg *DoltgresConfig) String() string { data, err := yaml.Marshal(cfg) if err != nil { return "Failed to marshal as yaml: " + err.Error() } unformatted := string(data) // format the yaml to be easier to read. lines := strings.Split(unformatted, "\n") var formatted []string formatted = append(formatted, lines[0]) for i := 1; i < len(lines); i++ { if len(lines[i]) == 0 { continue } r, _ := utf8.DecodeRuneInString(lines[i]) if !unicode.IsSpace(r) { formatted = append(formatted, "") } formatted = append(formatted, lines[i]) } result := strings.Join(formatted, "\n") return result } // InternalDefaultServerConfig should not be called directly. Use `servercfg.DefaultServerConfig` instead. func InternalDefaultServerConfig() *DoltgresConfig { dataDir := "." homeDir, err := env.GetCurrentUserHomeDir() if err == nil { dataDir = filepath.Join(homeDir, DOLTGRES_DATA_DIR_DEFAULT) } return &DoltgresConfig{ LogLevelStr: Ptr(string(DefaultLogLevel)), EncodeLoggedQuery: Ptr(DefaultEncodeLoggedQuery), BehaviorConfig: &DoltgresBehaviorConfig{ ReadOnly: Ptr(DefaultReadOnly), DoltTransactionCommit: Ptr(DefaultDoltTransactionCommit), }, UserConfig: &DoltgresUserConfig{ Name: Ptr(DefaultUser), Password: Ptr(DefaultPass), }, ListenerConfig: &DoltgresListenerConfig{ HostStr: Ptr(DefaultHost), PortNumber: Ptr(DefaultPort), ReadTimeoutMillis: Ptr(uint64(DefaultTimeout)), WriteTimeoutMillis: Ptr(uint64(DefaultTimeout)), AllowCleartextPasswords: Ptr(DefaultAllowCleartextPasswords), }, DataDirStr: Ptr(dataDir), CfgDirStr: Ptr(filepath.Join(DefaultDataDir, DefaultCfgDir)), PrivilegeFile: Ptr(filepath.Join(DefaultDataDir, DefaultCfgDir, DefaultPrivilegeFilePath)), AuthFile: Ptr(filepath.Join(DefaultDataDir, DefaultCfgDir, DefaultAuthFilePath)), BranchControlFile: Ptr(filepath.Join(DefaultDataDir, DefaultCfgDir, DefaultBranchControlFilePath)), } }