package pgconn

import (
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	

	
	
	
)

type AfterConnectFunc func(ctx context.Context, pgconn *PgConn) error
type ValidateConnectFunc func(ctx context.Context, pgconn *PgConn) error
type GetSSLPasswordFunc func(ctx context.Context) string

// Config is the settings used to establish a connection to a PostgreSQL server. It must be created by [ParseConfig]. A
// manually initialized Config will cause ConnectConfig to panic.
type Config struct {
	Host           string // host (e.g. localhost) or absolute path to unix domain socket directory (e.g. /private/tmp)
	Port           uint16
	Database       string
	User           string
	Password       string
	TLSConfig      *tls.Config // nil disables TLS
	ConnectTimeout time.Duration
	DialFunc       DialFunc   // e.g. net.Dialer.DialContext
	LookupFunc     LookupFunc // e.g. net.Resolver.LookupHost
	BuildFrontend  BuildFrontendFunc
	RuntimeParams  map[string]string // Run-time parameters to set on connection as session default values (e.g. search_path or application_name)

	KerberosSrvName string
	KerberosSpn     string
	Fallbacks       []*FallbackConfig

	// ValidateConnect is called during a connection attempt after a successful authentication with the PostgreSQL server.
	// It can be used to validate that the server is acceptable. If this returns an error the connection is closed and the next
	// fallback config is tried. This allows implementing high availability behavior such as libpq does with target_session_attrs.
	ValidateConnect ValidateConnectFunc

	// AfterConnect is called after ValidateConnect. It can be used to set up the connection (e.g. Set session variables
	// or prepare statements). If this returns an error the connection attempt fails.
	AfterConnect AfterConnectFunc

	// OnNotice is a callback function called when a notice response is received.
	OnNotice NoticeHandler

	// OnNotification is a callback function called when a notification from the LISTEN/NOTIFY system is received.
	OnNotification NotificationHandler

	createdByParseConfig bool // Used to enforce created by ParseConfig rule.
}

// ParseConfigOptions contains options that control how a config is built such as GetSSLPassword.
type ParseConfigOptions struct {
	// GetSSLPassword gets the password to decrypt a SSL client certificate. This is analogous to the the libpq function
	// PQsetSSLKeyPassHook_OpenSSL.
	GetSSLPassword GetSSLPasswordFunc
}

// Copy returns a deep copy of the config that is safe to use and modify.
// The only exception is the TLSConfig field:
// according to the tls.Config docs it must not be modified after creation.
func ( *Config) () *Config {
	 := new(Config)
	* = *
	if .TLSConfig != nil {
		.TLSConfig = .TLSConfig.Clone()
	}
	if .RuntimeParams != nil {
		.RuntimeParams = make(map[string]string, len(.RuntimeParams))
		for ,  := range .RuntimeParams {
			.RuntimeParams[] = 
		}
	}
	if .Fallbacks != nil {
		.Fallbacks = make([]*FallbackConfig, len(.Fallbacks))
		for ,  := range .Fallbacks {
			 := new(FallbackConfig)
			* = *
			if .TLSConfig != nil {
				.TLSConfig = .TLSConfig.Clone()
			}
			.Fallbacks[] = 
		}
	}
	return 
}

// FallbackConfig is additional settings to attempt a connection with when the primary Config fails to establish a
// network connection. It is used for TLS fallback such as sslmode=prefer and high availability (HA) connections.
type FallbackConfig struct {
	Host      string // host (e.g. localhost) or path to unix domain socket directory (e.g. /private/tmp)
	Port      uint16
	TLSConfig *tls.Config // nil disables TLS
}

// isAbsolutePath checks if the provided value is an absolute path either
// beginning with a forward slash (as on Linux-based systems) or with a capital
// letter A-Z followed by a colon and a backslash, e.g., "C:\", (as on Windows).
func isAbsolutePath( string) bool {
	 := func( string) bool {
		if len() < 3 {
			return false
		}
		 := [0]
		 := [1]
		 := [2]
		if  >= 'A' &&  <= 'Z' &&  == ':' &&  == '\\' {
			return true
		}
		return false
	}
	return strings.HasPrefix(, "/") || ()
}

// NetworkAddress converts a PostgreSQL host and port into network and address suitable for use with
// net.Dial.
func ( string,  uint16) (,  string) {
	if isAbsolutePath() {
		 = "unix"
		 = filepath.Join(, ".s.PGSQL.") + strconv.FormatInt(int64(), 10)
	} else {
		 = "tcp"
		 = net.JoinHostPort(, strconv.Itoa(int()))
	}
	return , 
}

// ParseConfig builds a *Config from connString with similar behavior to the PostgreSQL standard C library libpq. It
// uses the same defaults as libpq (e.g. port=5432) and understands most PG* environment variables. ParseConfig closely
// matches the parsing behavior of libpq. connString may either be in URL format or keyword = value format (DSN style).
// See https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING for details. connString also may be
// empty to only read from the environment. If a password is not supplied it will attempt to read the .pgpass file.
//
//	# Example DSN
//	user=jack password=secret host=pg.example.com port=5432 dbname=mydb sslmode=verify-ca
//
//	# Example URL
//	postgres://jack:secret@pg.example.com:5432/mydb?sslmode=verify-ca
//
// The returned *Config may be modified. However, it is strongly recommended that any configuration that can be done
// through the connection string be done there. In particular the fields Host, Port, TLSConfig, and Fallbacks can be
// interdependent (e.g. TLSConfig needs knowledge of the host to validate the server certificate). These fields should
// not be modified individually. They should all be modified or all left unchanged.
//
// ParseConfig supports specifying multiple hosts in similar manner to libpq. Host and port may include comma separated
// values that will be tried in order. This can be used as part of a high availability system. See
// https://www.postgresql.org/docs/11/libpq-connect.html#LIBPQ-MULTIPLE-HOSTS for more information.
//
//	# Example URL
//	postgres://jack:secret@foo.example.com:5432,bar.example.com:5432/mydb
//
// ParseConfig currently recognizes the following environment variable and their parameter key word equivalents passed
// via database URL or DSN:
//
//	PGHOST
//	PGPORT
//	PGDATABASE
//	PGUSER
//	PGPASSWORD
//	PGPASSFILE
//	PGSERVICE
//	PGSERVICEFILE
//	PGSSLMODE
//	PGSSLCERT
//	PGSSLKEY
//	PGSSLROOTCERT
//	PGSSLPASSWORD
//	PGAPPNAME
//	PGCONNECT_TIMEOUT
//	PGTARGETSESSIONATTRS
//
// See http://www.postgresql.org/docs/11/static/libpq-envars.html for details on the meaning of environment variables.
//
// See https://www.postgresql.org/docs/11/libpq-connect.html#LIBPQ-PARAMKEYWORDS for parameter key word names. They are
// usually but not always the environment variable name downcased and without the "PG" prefix.
//
// Important Security Notes:
//
// ParseConfig tries to match libpq behavior with regard to PGSSLMODE. This includes defaulting to "prefer" behavior if
// not set.
//
// See http://www.postgresql.org/docs/11/static/libpq-ssl.html#LIBPQ-SSL-PROTECTION for details on what level of
// security each sslmode provides.
//
// The sslmode "prefer" (the default), sslmode "allow", and multiple hosts are implemented via the Fallbacks field of
// the Config struct. If TLSConfig is manually changed it will not affect the fallbacks. For example, in the case of
// sslmode "prefer" this means it will first try the main Config settings which use TLS, then it will try the fallback
// which does not use TLS. This can lead to an unexpected unencrypted connection if the main TLS config is manually
// changed later but the unencrypted fallback is present. Ensure there are no stale fallbacks when manually setting
// TLSConfig.
//
// Other known differences with libpq:
//
// When multiple hosts are specified, libpq allows them to have different passwords set via the .pgpass file. pgconn
// does not.
//
// In addition, ParseConfig accepts the following options:
//
//   - servicefile.
//     libpq only reads servicefile from the PGSERVICEFILE environment variable. ParseConfig accepts servicefile as a
//     part of the connection string.
func ( string) (*Config, error) {
	var  ParseConfigOptions
	return ParseConfigWithOptions(, )
}

// ParseConfigWithOptions builds a *Config from connString and options with similar behavior to the PostgreSQL standard
// C library libpq. options contains settings that cannot be specified in a connString such as providing a function to
// get the SSL password.
func ( string,  ParseConfigOptions) (*Config, error) {
	 := defaultSettings()
	 := parseEnvSettings()

	 := make(map[string]string)
	if  != "" {
		var  error
		// connString may be a database URL or a DSN
		if strings.HasPrefix(, "postgres://") || strings.HasPrefix(, "postgresql://") {
			,  = parseURLSettings()
			if  != nil {
				return nil, &parseConfigError{connString: , msg: "failed to parse as URL", err: }
			}
		} else {
			,  = parseDSNSettings()
			if  != nil {
				return nil, &parseConfigError{connString: , msg: "failed to parse as DSN", err: }
			}
		}
	}

	 := mergeSettings(, , )
	if ,  := ["service"];  {
		,  := parseServiceSettings(["servicefile"], )
		if  != nil {
			return nil, &parseConfigError{connString: , msg: "failed to read service", err: }
		}

		 = mergeSettings(, , , )
	}

	 := &Config{
		createdByParseConfig: true,
		Database:             ["database"],
		User:                 ["user"],
		Password:             ["password"],
		RuntimeParams:        make(map[string]string),
		BuildFrontend: func( io.Reader,  io.Writer) *pgproto3.Frontend {
			return pgproto3.NewFrontend(, )
		},
	}

	if ,  := ["connect_timeout"];  {
		,  := parseConnectTimeoutSetting()
		if  != nil {
			return nil, &parseConfigError{connString: , msg: "invalid connect_timeout", err: }
		}
		.ConnectTimeout = 
		.DialFunc = makeConnectTimeoutDialFunc()
	} else {
		 := makeDefaultDialer()
		.DialFunc = .DialContext
	}

	.LookupFunc = makeDefaultResolver().LookupHost

	 := map[string]struct{}{
		"host":                 {},
		"port":                 {},
		"database":             {},
		"user":                 {},
		"password":             {},
		"passfile":             {},
		"connect_timeout":      {},
		"sslmode":              {},
		"sslkey":               {},
		"sslcert":              {},
		"sslrootcert":          {},
		"sslpassword":          {},
		"sslsni":               {},
		"krbspn":               {},
		"krbsrvname":           {},
		"target_session_attrs": {},
		"service":              {},
		"servicefile":          {},
	}

	// Adding kerberos configuration
	if ,  := ["krbsrvname"];  {
		.KerberosSrvName = ["krbsrvname"]
	}
	if ,  := ["krbspn"];  {
		.KerberosSpn = ["krbspn"]
	}

	for ,  := range  {
		if ,  := [];  {
			continue
		}
		.RuntimeParams[] = 
	}

	 := []*FallbackConfig{}

	 := strings.Split(["host"], ",")
	 := strings.Split(["port"], ",")

	for ,  := range  {
		var  string
		if  < len() {
			 = []
		} else {
			 = [0]
		}

		,  := parsePort()
		if  != nil {
			return nil, &parseConfigError{connString: , msg: "invalid port", err: }
		}

		var  []*tls.Config

		// Ignore TLS settings if Unix domain socket like libpq
		if ,  := NetworkAddress(, );  == "unix" {
			 = append(, nil)
		} else {
			var  error
			,  = configTLS(, , )
			if  != nil {
				return nil, &parseConfigError{connString: , msg: "failed to configure TLS", err: }
			}
		}

		for ,  := range  {
			 = append(, &FallbackConfig{
				Host:      ,
				Port:      ,
				TLSConfig: ,
			})
		}
	}

	.Host = [0].Host
	.Port = [0].Port
	.TLSConfig = [0].TLSConfig
	.Fallbacks = [1:]

	,  := pgpassfile.ReadPassfile(["passfile"])
	if  == nil {
		if .Password == "" {
			 := .Host
			if ,  := NetworkAddress(.Host, .Port);  == "unix" {
				 = "localhost"
			}

			.Password = .FindPassword(, strconv.Itoa(int(.Port)), .Database, .User)
		}
	}

	switch  := ["target_session_attrs"];  {
	case "read-write":
		.ValidateConnect = ValidateConnectTargetSessionAttrsReadWrite
	case "read-only":
		.ValidateConnect = ValidateConnectTargetSessionAttrsReadOnly
	case "primary":
		.ValidateConnect = ValidateConnectTargetSessionAttrsPrimary
	case "standby":
		.ValidateConnect = ValidateConnectTargetSessionAttrsStandby
	case "prefer-standby":
		.ValidateConnect = ValidateConnectTargetSessionAttrsPreferStandby
	case "any":
		// do nothing
	default:
		return nil, &parseConfigError{connString: , msg: fmt.Sprintf("unknown target_session_attrs value: %v", )}
	}

	return , nil
}

func mergeSettings( ...map[string]string) map[string]string {
	 := make(map[string]string)

	for ,  := range  {
		for ,  := range  {
			[] = 
		}
	}

	return 
}

func parseEnvSettings() map[string]string {
	 := make(map[string]string)

	 := map[string]string{
		"PGHOST":               "host",
		"PGPORT":               "port",
		"PGDATABASE":           "database",
		"PGUSER":               "user",
		"PGPASSWORD":           "password",
		"PGPASSFILE":           "passfile",
		"PGAPPNAME":            "application_name",
		"PGCONNECT_TIMEOUT":    "connect_timeout",
		"PGSSLMODE":            "sslmode",
		"PGSSLKEY":             "sslkey",
		"PGSSLCERT":            "sslcert",
		"PGSSLSNI":             "sslsni",
		"PGSSLROOTCERT":        "sslrootcert",
		"PGSSLPASSWORD":        "sslpassword",
		"PGTARGETSESSIONATTRS": "target_session_attrs",
		"PGSERVICE":            "service",
		"PGSERVICEFILE":        "servicefile",
	}

	for ,  := range  {
		 := os.Getenv()
		if  != "" {
			[] = 
		}
	}

	return 
}

func parseURLSettings( string) (map[string]string, error) {
	 := make(map[string]string)

	,  := url.Parse()
	if  != nil {
		return nil, 
	}

	if .User != nil {
		["user"] = .User.Username()
		if ,  := .User.Password();  {
			["password"] = 
		}
	}

	// Handle multiple host:port's in url.Host by splitting them into host,host,host and port,port,port.
	var  []string
	var  []string
	for ,  := range strings.Split(.Host, ",") {
		if  == "" {
			continue
		}
		if isIPOnly() {
			 = append(, strings.Trim(, "[]"))
			continue
		}
		, ,  := net.SplitHostPort()
		if  != nil {
			return nil, fmt.Errorf("failed to split host:port in '%s', err: %w", , )
		}
		if  != "" {
			 = append(, )
		}
		if  != "" {
			 = append(, )
		}
	}
	if len() > 0 {
		["host"] = strings.Join(, ",")
	}
	if len() > 0 {
		["port"] = strings.Join(, ",")
	}

	 := strings.TrimLeft(.Path, "/")
	if  != "" {
		["database"] = 
	}

	 := map[string]string{
		"dbname": "database",
	}

	for ,  := range .Query() {
		if ,  := [];  {
			 = 
		}

		[] = [0]
	}

	return , nil
}

func isIPOnly( string) bool {
	return net.ParseIP(strings.Trim(, "[]")) != nil || !strings.Contains(, ":")
}

var asciiSpace = [256]uint8{'\t': 1, '\n': 1, '\v': 1, '\f': 1, '\r': 1, ' ': 1}

func parseDSNSettings( string) (map[string]string, error) {
	 := make(map[string]string)

	 := map[string]string{
		"dbname": "database",
	}

	for len() > 0 {
		var ,  string
		 := strings.IndexRune(, '=')
		if  < 0 {
			return nil, errors.New("invalid dsn")
		}

		 = strings.Trim([:], " \t\n\r\v\f")
		 = strings.TrimLeft([+1:], " \t\n\r\v\f")
		if len() == 0 {
		} else if [0] != '\'' {
			 := 0
			for ;  < len(); ++ {
				if asciiSpace[[]] == 1 {
					break
				}
				if [] == '\\' {
					++
					if  == len() {
						return nil, errors.New("invalid backslash")
					}
				}
			}
			 = strings.Replace(strings.Replace([:], "\\\\", "\\", -1), "\\'", "'", -1)
			if  == len() {
				 = ""
			} else {
				 = [+1:]
			}
		} else { // quoted string
			 = [1:]
			 := 0
			for ;  < len(); ++ {
				if [] == '\'' {
					break
				}
				if [] == '\\' {
					++
				}
			}
			if  == len() {
				return nil, errors.New("unterminated quoted string in connection info string")
			}
			 = strings.Replace(strings.Replace([:], "\\\\", "\\", -1), "\\'", "'", -1)
			if  == len() {
				 = ""
			} else {
				 = [+1:]
			}
		}

		if ,  := [];  {
			 = 
		}

		if  == "" {
			return nil, errors.New("invalid dsn")
		}

		[] = 
	}

	return , nil
}

func parseServiceSettings(,  string) (map[string]string, error) {
	,  := pgservicefile.ReadServicefile()
	if  != nil {
		return nil, fmt.Errorf("failed to read service file: %v", )
	}

	,  := .GetService()
	if  != nil {
		return nil, fmt.Errorf("unable to find service: %v", )
	}

	 := map[string]string{
		"dbname": "database",
	}

	 := make(map[string]string, len(.Settings))
	for ,  := range .Settings {
		if ,  := [];  {
			 = 
		}
		[] = 
	}

	return , nil
}

// configTLS uses libpq's TLS parameters to construct  []*tls.Config. It is
// necessary to allow returning multiple TLS configs as sslmode "allow" and
// "prefer" allow fallback.
func configTLS( map[string]string,  string,  ParseConfigOptions) ([]*tls.Config, error) {
	 := 
	 := ["sslmode"]
	 := ["sslrootcert"]
	 := ["sslcert"]
	 := ["sslkey"]
	 := ["sslpassword"]
	 := ["sslsni"]

	// Match libpq default behavior
	if  == "" {
		 = "prefer"
	}
	if  == "" {
		 = "1"
	}

	 := &tls.Config{}

	switch  {
	case "disable":
		return []*tls.Config{nil}, nil
	case "allow", "prefer":
		.InsecureSkipVerify = true
	case "require":
		// According to PostgreSQL documentation, if a root CA file exists,
		// the behavior of sslmode=require should be the same as that of verify-ca
		//
		// See https://www.postgresql.org/docs/12/libpq-ssl.html
		if  != "" {
			goto 
		}
		.InsecureSkipVerify = true
		break
	:
		fallthrough
	case "verify-ca":
		// Don't perform the default certificate verification because it
		// will verify the hostname. Instead, verify the server's
		// certificate chain ourselves in VerifyPeerCertificate and
		// ignore the server name. This emulates libpq's verify-ca
		// behavior.
		//
		// See https://github.com/golang/go/issues/21971#issuecomment-332693931
		// and https://pkg.go.dev/crypto/tls?tab=doc#example-Config-VerifyPeerCertificate
		// for more info.
		.InsecureSkipVerify = true
		.VerifyPeerCertificate = func( [][]byte,  [][]*x509.Certificate) error {
			 := make([]*x509.Certificate, len())
			for ,  := range  {
				,  := x509.ParseCertificate()
				if  != nil {
					return errors.New("failed to parse certificate from server: " + .Error())
				}
				[] = 
			}

			// Leave DNSName empty to skip hostname verification.
			 := x509.VerifyOptions{
				Roots:         .RootCAs,
				Intermediates: x509.NewCertPool(),
			}
			// Skip the first cert because it's the leaf. All others
			// are intermediates.
			for ,  := range [1:] {
				.Intermediates.AddCert()
			}
			,  := [0].Verify()
			return 
		}
	case "verify-full":
		.ServerName = 
	default:
		return nil, errors.New("sslmode is invalid")
	}

	if  != "" {
		 := x509.NewCertPool()

		 := 
		,  := os.ReadFile()
		if  != nil {
			return nil, fmt.Errorf("unable to read CA file: %w", )
		}

		if !.AppendCertsFromPEM() {
			return nil, errors.New("unable to add CA to cert pool")
		}

		.RootCAs = 
		.ClientCAs = 
	}

	if ( != "" &&  == "") || ( == "" &&  != "") {
		return nil, errors.New(`both "sslcert" and "sslkey" are required`)
	}

	if  != "" &&  != "" {
		,  := os.ReadFile()
		if  != nil {
			return nil, fmt.Errorf("unable to read sslkey: %w", )
		}
		,  := pem.Decode()
		var  []byte
		var  []byte
		var  error
		// If PEM is encrypted, attempt to decrypt using pass phrase
		if x509.IsEncryptedPEMBlock() {
			// Attempt decryption with pass phrase
			// NOTE: only supports RSA (PKCS#1)
			if  != "" {
				,  = x509.DecryptPEMBlock(, []byte())
			}
			//if sslpassword not provided or has decryption error when use it
			//try to find sslpassword with callback function
			if  == "" ||  != nil {
				if .GetSSLPassword != nil {
					 = .GetSSLPassword(context.Background())
				}
				if  == "" {
					return nil, fmt.Errorf("unable to find sslpassword")
				}
			}
			,  = x509.DecryptPEMBlock(, []byte())
			// Should we also provide warning for PKCS#1 needed?
			if  != nil {
				return nil, fmt.Errorf("unable to decrypt key: %w", )
			}

			 := pem.Block{
				Type:  "RSA PRIVATE KEY",
				Bytes: ,
			}
			 = pem.EncodeToMemory(&)
		} else {
			 = pem.EncodeToMemory()
		}
		,  := os.ReadFile()
		if  != nil {
			return nil, fmt.Errorf("unable to read cert: %w", )
		}
		,  := tls.X509KeyPair(, )
		if  != nil {
			return nil, fmt.Errorf("unable to load cert: %w", )
		}
		.Certificates = []tls.Certificate{}
	}

	// Set Server Name Indication (SNI), if enabled by connection parameters.
	// Per RFC 6066, do not set it if the host is a literal IP address (IPv4
	// or IPv6).
	if  == "1" && net.ParseIP() == nil {
		.ServerName = 
	}

	switch  {
	case "allow":
		return []*tls.Config{nil, }, nil
	case "prefer":
		return []*tls.Config{, nil}, nil
	case "require", "verify-ca", "verify-full":
		return []*tls.Config{}, nil
	default:
		panic("BUG: bad sslmode should already have been caught")
	}
}

func parsePort( string) (uint16, error) {
	,  := strconv.ParseUint(, 10, 16)
	if  != nil {
		return 0, 
	}
	if  < 1 ||  > math.MaxUint16 {
		return 0, errors.New("outside range")
	}
	return uint16(), nil
}

func makeDefaultDialer() *net.Dialer {
	return &net.Dialer{KeepAlive: 5 * time.Minute}
}

func makeDefaultResolver() *net.Resolver {
	return net.DefaultResolver
}

func parseConnectTimeoutSetting( string) (time.Duration, error) {
	,  := strconv.ParseInt(, 10, 64)
	if  != nil {
		return 0, 
	}
	if  < 0 {
		return 0, errors.New("negative timeout")
	}
	return time.Duration() * time.Second, nil
}

func makeConnectTimeoutDialFunc( time.Duration) DialFunc {
	 := makeDefaultDialer()
	.Timeout = 
	return .DialContext
}

// ValidateConnectTargetSessionAttrsReadWrite is an ValidateConnectFunc that implements libpq compatible
// target_session_attrs=read-write.
func ( context.Context,  *PgConn) error {
	 := .ExecParams(, "show transaction_read_only", nil, nil, nil, nil).Read()
	if .Err != nil {
		return .Err
	}

	if string(.Rows[0][0]) == "on" {
		return errors.New("read only connection")
	}

	return nil
}

// ValidateConnectTargetSessionAttrsReadOnly is an ValidateConnectFunc that implements libpq compatible
// target_session_attrs=read-only.
func ( context.Context,  *PgConn) error {
	 := .ExecParams(, "show transaction_read_only", nil, nil, nil, nil).Read()
	if .Err != nil {
		return .Err
	}

	if string(.Rows[0][0]) != "on" {
		return errors.New("connection is not read only")
	}

	return nil
}

// ValidateConnectTargetSessionAttrsStandby is an ValidateConnectFunc that implements libpq compatible
// target_session_attrs=standby.
func ( context.Context,  *PgConn) error {
	 := .ExecParams(, "select pg_is_in_recovery()", nil, nil, nil, nil).Read()
	if .Err != nil {
		return .Err
	}

	if string(.Rows[0][0]) != "t" {
		return errors.New("server is not in hot standby mode")
	}

	return nil
}

// ValidateConnectTargetSessionAttrsPrimary is an ValidateConnectFunc that implements libpq compatible
// target_session_attrs=primary.
func ( context.Context,  *PgConn) error {
	 := .ExecParams(, "select pg_is_in_recovery()", nil, nil, nil, nil).Read()
	if .Err != nil {
		return .Err
	}

	if string(.Rows[0][0]) == "t" {
		return errors.New("server is in standby mode")
	}

	return nil
}

// ValidateConnectTargetSessionAttrsPreferStandby is an ValidateConnectFunc that implements libpq compatible
// target_session_attrs=prefer-standby.
func ( context.Context,  *PgConn) error {
	 := .ExecParams(, "select pg_is_in_recovery()", nil, nil, nil, nil).Read()
	if .Err != nil {
		return .Err
	}

	if string(.Rows[0][0]) != "t" {
		return &NotPreferredError{err: errors.New("server is not in hot standby mode")}
	}

	return nil
}