From 2f81f1fc03bef99593e92c64ad9cac954c00e8e6 Mon Sep 17 00:00:00 2001 From: Adam Brightwell Date: Wed, 25 Mar 2026 09:40:57 -0400 Subject: [PATCH] Update `max_protocol_version` and `min_protocol_version` defaults When `max_protocol_version` is not explicitly set, the default value is set to "3.0". This is reasonable and is inline with the same behavior as libpq[1]. Though, what libpq attempts to do that was not being done here is to reasonably upgrade or auto-raise the value of `max_protocol_version` when it has not been explicitly set. Here, we're providing the same functionality. In libpq, this determination is based on the parsed config values. We do the same here, parsing the values in ParseConfig to validate and determine the appropriate default for max_protocol_version. The string fields on Config are then set accordingly, and re-parsed at connection time as before. After parsing, when `max_protocol_version` has not been explicitly set, if `min_protocol_version` is greater than 3.0, `max_protocol_version` defaults to `latest`; otherwise it defaults to 3.0 for compatibility with older servers/poolers that don't support NegotiateProtocolVersion. [1] https://github.com/postgres/postgres/commit/285613c60a7aff5daaf281c67002483b0d26e715 --- pgconn/config.go | 26 ++++++++++++++++++++------ pgconn/config_test.go | 32 +++++++++++++++++++++++++------- pgproto3/startup_message.go | 1 + 3 files changed, 46 insertions(+), 13 deletions(-) diff --git a/pgconn/config.go b/pgconn/config.go index dff550953..0177d22c5 100644 --- a/pgconn/config.go +++ b/pgconn/config.go @@ -454,23 +454,37 @@ func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Con minProto, err := parseProtocolVersion(settings["min_protocol_version"]) if err != nil { - return nil, &ParseConfigError{ConnString: connString, msg: "invalid min_protocol_version", err: err} + return nil, &ParseConfigError{ConnString: connString, msg: fmt.Sprintf("invalid min_protocol_version: %q", settings["min_protocol_version"]), err: err} } maxProto, err := parseProtocolVersion(settings["max_protocol_version"]) if err != nil { - return nil, &ParseConfigError{ConnString: connString, msg: "invalid max_protocol_version", err: err} - } - if minProto > maxProto { - return nil, &ParseConfigError{ConnString: connString, msg: "min_protocol_version cannot be greater than max_protocol_version"} + return nil, &ParseConfigError{ConnString: connString, msg: fmt.Sprintf("invalid max_protocol_version: %q", settings["max_protocol_version"]), err: err} } config.MinProtocolVersion = settings["min_protocol_version"] config.MaxProtocolVersion = settings["max_protocol_version"] + if config.MinProtocolVersion == "" { config.MinProtocolVersion = "3.0" } + + // When max_protocol_version is not explicitly set, default based on + // min_protocol_version. This matches libpq behavior: if min > 3.0, + // default max to latest; otherwise default to 3.0 for compatibility + // with older servers/poolers that don't support NegotiateProtocolVersion. if config.MaxProtocolVersion == "" { - config.MaxProtocolVersion = "3.0" + if minProto > pgproto3.ProtocolVersion30 { + config.MaxProtocolVersion = "latest" + } else { + config.MaxProtocolVersion = "3.0" + } + } + + // Only error when max_protocol_version was explicitly set and conflicts + // with min_protocol_version. When max_protocol_version is not explicitly + // set, the auto-raise logic above already ensures a valid default. + if minProto > maxProto && settings["max_protocol_version"] != "" { + return nil, &ParseConfigError{ConnString: connString, msg: "min_protocol_version cannot be greater than max_protocol_version"} } switch channelBinding := settings["channel_binding"]; channelBinding { diff --git a/pgconn/config_test.go b/pgconn/config_test.go index 57898eb88..e6120aad3 100644 --- a/pgconn/config_test.go +++ b/pgconn/config_test.go @@ -1228,6 +1228,12 @@ func TestParseConfigProtocolVersion(t *testing.T) { expectedMin: "3.2", expectedMax: "3.2", }, + { + name: "min_protocol_version=latest auto-raises max", + connString: "postgres://localhost/test?min_protocol_version=latest", + expectedMin: "latest", + expectedMax: "latest", + }, { name: "max_protocol_version=latest", connString: "postgres://localhost/test?max_protocol_version=latest", @@ -1235,7 +1241,7 @@ func TestParseConfigProtocolVersion(t *testing.T) { expectedMax: "latest", }, { - name: "min and max = latest", + name: "min_protocol_version and max_protocol_version = latest", connString: "postgres://localhost/test?min_protocol_version=latest&max_protocol_version=latest", expectedMin: "latest", expectedMax: "latest", @@ -1244,27 +1250,39 @@ func TestParseConfigProtocolVersion(t *testing.T) { name: "invalid min_protocol_version", connString: "postgres://localhost/test?min_protocol_version=2.0", expectError: true, - expectedErrContain: "invalid min_protocol_version", + expectedErrContain: `invalid min_protocol_version: "2.0"`, }, { name: "invalid max_protocol_version", connString: "postgres://localhost/test?max_protocol_version=4.0", expectError: true, - expectedErrContain: "invalid max_protocol_version", + expectedErrContain: `invalid max_protocol_version: "4.0"`, }, { - name: "min > max", + name: "min_protocol_version > max_protocol_version", connString: "postgres://localhost/test?min_protocol_version=3.2&max_protocol_version=3.0", expectError: true, expectedErrContain: "min_protocol_version cannot be greater than max_protocol_version", }, { - name: "environment variable PGMINPROTOCOLVERSION without matching max fails", - connString: "postgres://localhost/test", - envMin: "3.2", + name: "min_protocol_version (latest) > max_protocol_version", + connString: "postgres://localhost/test?min_protocol_version=latest&max_protocol_version=3.0", expectError: true, expectedErrContain: "min_protocol_version cannot be greater than max_protocol_version", }, + { + name: "min_protocol_version=3.2 auto-raises max", + connString: "postgres://localhost/test?min_protocol_version=3.2", + expectedMin: "3.2", + expectedMax: "latest", + }, + { + name: "environment variable PGMINPROTOCOLVERSION auto-raises max", + connString: "postgres://localhost/test", + envMin: "3.2", + expectedMin: "3.2", + expectedMax: "latest", + }, { name: "environment variables PGMINPROTOCOLVERSION and PGMAXPROTOCOLVERSION together", connString: "postgres://localhost/test", diff --git a/pgproto3/startup_message.go b/pgproto3/startup_message.go index 6caab3ee4..eb48f72bf 100644 --- a/pgproto3/startup_message.go +++ b/pgproto3/startup_message.go @@ -13,6 +13,7 @@ import ( const ( ProtocolVersion30 = 196608 // 3.0 ProtocolVersion32 = 196610 // 3.2 + ProtocolVersionLatest = ProtocolVersion32 // Latest is 3.2 ProtocolVersionNumber = ProtocolVersion30 // Default is still 3.0 )