Skip to content

Commit c074af6

Browse files
GiteaBotmpldr
andauthored
refactor postgres connection string building (#27723) (#27869)
Backport #27723 by @mpldr This patchset changes the connection string builder to use net.URL and the host/port parser to use the stdlib function for splitting host from port. It also adds a footnote about a potentially required portnumber for postgres UNIX sockets. Fixes: #24552 Co-authored-by: Moritz Poldrack <33086936+mpldr@users.noreply.github.com>
1 parent 3959611 commit c074af6

File tree

3 files changed

+47
-30
lines changed

3 files changed

+47
-30
lines changed

docs/content/administration/config-cheat-sheet.en-us.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
423423
## Database (`database`)
424424

425425
- `DB_TYPE`: **mysql**: The database type in use \[mysql, postgres, mssql, sqlite3\].
426-
- `HOST`: **127.0.0.1:3306**: Database host address and port or absolute path for unix socket \[mysql, postgres\] (ex: /var/run/mysqld/mysqld.sock).
426+
- `HOST`: **127.0.0.1:3306**: Database host address and port or absolute path for unix socket \[mysql, postgres[^1]\] (ex: /var/run/mysqld/mysqld.sock).
427427
- `NAME`: **gitea**: Database name.
428428
- `USER`: **root**: Database username.
429429
- `PASSWD`: **_empty_**: Database user password. Use \`your password\` or """your password""" for quoting if you use special characters in the password.
@@ -454,6 +454,8 @@ The following configuration set `Content-Type: application/vnd.android.package-a
454454
- `CONN_MAX_LIFETIME` **0 or 3s**: Sets the maximum amount of time a DB connection may be reused - default is 0, meaning there is no limit (except on MySQL where it is 3s - see #6804 & #7071).
455455
- `AUTO_MIGRATION` **true**: Whether execute database models migrations automatically.
456456

457+
[^1]: It may be necessary to specify a hostport even when listening on a unix socket, as the port is part of the socket name. see [#24552](https://github.com/go-gitea/gitea/issues/24552#issuecomment-1681649367) for additional details.
458+
457459
Please see #8540 & #8273 for further discussion of the appropriate values for `MAX_OPEN_CONNS`, `MAX_IDLE_CONNS` & `CONN_MAX_LIFETIME` and their
458460
relation to port exhaustion.
459461

modules/setting/database.go

+26-14
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package setting
66
import (
77
"errors"
88
"fmt"
9+
"net"
910
"net/url"
1011
"os"
1112
"path"
@@ -135,15 +136,18 @@ func DBConnStr() (string, error) {
135136
// parsePostgreSQLHostPort parses given input in various forms defined in
136137
// https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
137138
// and returns proper host and port number.
138-
func parsePostgreSQLHostPort(info string) (string, string) {
139-
host, port := "127.0.0.1", "5432"
140-
if strings.Contains(info, ":") && !strings.HasSuffix(info, "]") {
141-
idx := strings.LastIndex(info, ":")
142-
host = info[:idx]
143-
port = info[idx+1:]
144-
} else if len(info) > 0 {
139+
func parsePostgreSQLHostPort(info string) (host, port string) {
140+
if h, p, err := net.SplitHostPort(info); err == nil {
141+
host, port = h, p
142+
} else {
143+
// treat the "info" as "host", if it's an IPv6 address, remove the wrapper
145144
host = info
145+
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
146+
host = host[1 : len(host)-1]
147+
}
146148
}
149+
150+
// set fallback values
147151
if host == "" {
148152
host = "127.0.0.1"
149153
}
@@ -155,14 +159,22 @@ func parsePostgreSQLHostPort(info string) (string, string) {
155159

156160
func getPostgreSQLConnectionString(dbHost, dbUser, dbPasswd, dbName, dbParam, dbsslMode string) (connStr string) {
157161
host, port := parsePostgreSQLHostPort(dbHost)
158-
if host[0] == '/' { // looks like a unix socket
159-
connStr = fmt.Sprintf("postgres://%s:%s@:%s/%s%ssslmode=%s&host=%s",
160-
url.PathEscape(dbUser), url.PathEscape(dbPasswd), port, dbName, dbParam, dbsslMode, host)
161-
} else {
162-
connStr = fmt.Sprintf("postgres://%s:%s@%s:%s/%s%ssslmode=%s",
163-
url.PathEscape(dbUser), url.PathEscape(dbPasswd), host, port, dbName, dbParam, dbsslMode)
162+
connURL := url.URL{
163+
Scheme: "postgres",
164+
User: url.UserPassword(dbUser, dbPasswd),
165+
Host: net.JoinHostPort(host, port),
166+
Path: dbName,
167+
OmitHost: false,
168+
RawQuery: dbParam,
169+
}
170+
query := connURL.Query()
171+
if dbHost[0] == '/' { // looks like a unix socket
172+
query.Add("host", dbHost)
173+
connURL.Host = ":" + port
164174
}
165-
return connStr
175+
query.Set("sslmode", dbsslMode)
176+
connURL.RawQuery = query.Encode()
177+
return connURL.String()
166178
}
167179

168180
// ParseMSSQLHostPort splits the host into host and port

modules/setting/database_test.go

+18-15
Original file line numberDiff line numberDiff line change
@@ -10,46 +10,49 @@ import (
1010
)
1111

1212
func Test_parsePostgreSQLHostPort(t *testing.T) {
13-
tests := []struct {
13+
tests := map[string]struct {
1414
HostPort string
1515
Host string
1616
Port string
1717
}{
18-
{
18+
"host-port": {
1919
HostPort: "127.0.0.1:1234",
2020
Host: "127.0.0.1",
2121
Port: "1234",
2222
},
23-
{
23+
"no-port": {
2424
HostPort: "127.0.0.1",
2525
Host: "127.0.0.1",
2626
Port: "5432",
2727
},
28-
{
28+
"ipv6-port": {
2929
HostPort: "[::1]:1234",
30-
Host: "[::1]",
30+
Host: "::1",
3131
Port: "1234",
3232
},
33-
{
33+
"ipv6-no-port": {
3434
HostPort: "[::1]",
35-
Host: "[::1]",
35+
Host: "::1",
3636
Port: "5432",
3737
},
38-
{
38+
"unix-socket": {
3939
HostPort: "/tmp/pg.sock:1234",
4040
Host: "/tmp/pg.sock",
4141
Port: "1234",
4242
},
43-
{
43+
"unix-socket-no-port": {
4444
HostPort: "/tmp/pg.sock",
4545
Host: "/tmp/pg.sock",
4646
Port: "5432",
4747
},
4848
}
49-
for _, test := range tests {
50-
host, port := parsePostgreSQLHostPort(test.HostPort)
51-
assert.Equal(t, test.Host, host)
52-
assert.Equal(t, test.Port, port)
49+
for k, test := range tests {
50+
t.Run(k, func(t *testing.T) {
51+
t.Log(test.HostPort)
52+
host, port := parsePostgreSQLHostPort(test.HostPort)
53+
assert.Equal(t, test.Host, host)
54+
assert.Equal(t, test.Port, port)
55+
})
5356
}
5457
}
5558

@@ -72,7 +75,7 @@ func Test_getPostgreSQLConnectionString(t *testing.T) {
7275
Name: "gitea",
7376
Param: "",
7477
SSLMode: "false",
75-
Output: "postgres://testuser:space%20space%20%21%23$%25%5E%5E%25%5E%60%60%60-=%3F=@:5432/giteasslmode=false&host=/tmp/pg.sock",
78+
Output: "postgres://testuser:space%20space%20%21%23$%25%5E%5E%25%5E%60%60%60-=%3F=@:5432/gitea?host=%2Ftmp%2Fpg.sock&sslmode=false",
7679
},
7780
{
7881
Host: "localhost",
@@ -82,7 +85,7 @@ func Test_getPostgreSQLConnectionString(t *testing.T) {
8285
Name: "gitea",
8386
Param: "",
8487
SSLMode: "true",
85-
Output: "postgres://pgsqlusername:I%20love%20Gitea%21@localhost:5432/giteasslmode=true",
88+
Output: "postgres://pgsqlusername:I%20love%20Gitea%21@localhost:5432/gitea?sslmode=true",
8689
},
8790
}
8891

0 commit comments

Comments
 (0)