1package pgconn_test
2
3import (
4	"context"
5	"crypto/tls"
6	"fmt"
7	"io/ioutil"
8	"os"
9	"os/user"
10	"runtime"
11	"strings"
12	"testing"
13	"time"
14
15	"github.com/jackc/pgconn"
16	"github.com/stretchr/testify/assert"
17	"github.com/stretchr/testify/require"
18)
19
20func TestParseConfig(t *testing.T) {
21	t.Parallel()
22
23	var osUserName string
24	osUser, err := user.Current()
25	if err == nil {
26		// Windows gives us the username here as `DOMAIN\user` or `LOCALPCNAME\user`,
27		// but the libpq default is just the `user` portion, so we strip off the first part.
28		if runtime.GOOS == "windows" && strings.Contains(osUser.Username, "\\") {
29			osUserName = osUser.Username[strings.LastIndex(osUser.Username, "\\")+1:]
30		} else {
31			osUserName = osUser.Username
32		}
33	}
34
35	config, err := pgconn.ParseConfig("")
36	require.NoError(t, err)
37	defaultHost := config.Host
38
39	tests := []struct {
40		name       string
41		connString string
42		config     *pgconn.Config
43	}{
44		// Test all sslmodes
45		{
46			name:       "sslmode not set (prefer)",
47			connString: "postgres://jack:secret@localhost:5432/mydb",
48			config: &pgconn.Config{
49				User:     "jack",
50				Password: "secret",
51				Host:     "localhost",
52				Port:     5432,
53				Database: "mydb",
54				TLSConfig: &tls.Config{
55					InsecureSkipVerify: true,
56				},
57				RuntimeParams: map[string]string{},
58				Fallbacks: []*pgconn.FallbackConfig{
59					&pgconn.FallbackConfig{
60						Host:      "localhost",
61						Port:      5432,
62						TLSConfig: nil,
63					},
64				},
65			},
66		},
67		{
68			name:       "sslmode disable",
69			connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable",
70			config: &pgconn.Config{
71				User:          "jack",
72				Password:      "secret",
73				Host:          "localhost",
74				Port:          5432,
75				Database:      "mydb",
76				TLSConfig:     nil,
77				RuntimeParams: map[string]string{},
78			},
79		},
80		{
81			name:       "sslmode allow",
82			connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=allow",
83			config: &pgconn.Config{
84				User:          "jack",
85				Password:      "secret",
86				Host:          "localhost",
87				Port:          5432,
88				Database:      "mydb",
89				TLSConfig:     nil,
90				RuntimeParams: map[string]string{},
91				Fallbacks: []*pgconn.FallbackConfig{
92					&pgconn.FallbackConfig{
93						Host: "localhost",
94						Port: 5432,
95						TLSConfig: &tls.Config{
96							InsecureSkipVerify: true,
97						},
98					},
99				},
100			},
101		},
102		{
103			name:       "sslmode prefer",
104			connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=prefer",
105			config: &pgconn.Config{
106
107				User:     "jack",
108				Password: "secret",
109				Host:     "localhost",
110				Port:     5432,
111				Database: "mydb",
112				TLSConfig: &tls.Config{
113					InsecureSkipVerify: true,
114				},
115				RuntimeParams: map[string]string{},
116				Fallbacks: []*pgconn.FallbackConfig{
117					&pgconn.FallbackConfig{
118						Host:      "localhost",
119						Port:      5432,
120						TLSConfig: nil,
121					},
122				},
123			},
124		},
125		{
126			name:       "sslmode require",
127			connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=require",
128			config: &pgconn.Config{
129				User:     "jack",
130				Password: "secret",
131				Host:     "localhost",
132				Port:     5432,
133				Database: "mydb",
134				TLSConfig: &tls.Config{
135					InsecureSkipVerify: true,
136				},
137				RuntimeParams: map[string]string{},
138			},
139		},
140		{
141			name:       "sslmode verify-ca",
142			connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=verify-ca",
143			config: &pgconn.Config{
144				User:     "jack",
145				Password: "secret",
146				Host:     "localhost",
147				Port:     5432,
148				Database: "mydb",
149				TLSConfig: &tls.Config{
150					InsecureSkipVerify: true,
151				},
152				RuntimeParams: map[string]string{},
153			},
154		},
155		{
156			name:       "sslmode verify-full",
157			connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=verify-full",
158			config: &pgconn.Config{
159				User:          "jack",
160				Password:      "secret",
161				Host:          "localhost",
162				Port:          5432,
163				Database:      "mydb",
164				TLSConfig:     &tls.Config{ServerName: "localhost"},
165				RuntimeParams: map[string]string{},
166			},
167		},
168		{
169			name:       "database url everything",
170			connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable&application_name=pgxtest&search_path=myschema&connect_timeout=5",
171			config: &pgconn.Config{
172				User:           "jack",
173				Password:       "secret",
174				Host:           "localhost",
175				Port:           5432,
176				Database:       "mydb",
177				TLSConfig:      nil,
178				ConnectTimeout: 5 * time.Second,
179				RuntimeParams: map[string]string{
180					"application_name": "pgxtest",
181					"search_path":      "myschema",
182				},
183			},
184		},
185		{
186			name:       "database url missing password",
187			connString: "postgres://jack@localhost:5432/mydb?sslmode=disable",
188			config: &pgconn.Config{
189				User:          "jack",
190				Host:          "localhost",
191				Port:          5432,
192				Database:      "mydb",
193				TLSConfig:     nil,
194				RuntimeParams: map[string]string{},
195			},
196		},
197		{
198			name:       "database url missing user and password",
199			connString: "postgres://localhost:5432/mydb?sslmode=disable",
200			config: &pgconn.Config{
201				User:          osUserName,
202				Host:          "localhost",
203				Port:          5432,
204				Database:      "mydb",
205				TLSConfig:     nil,
206				RuntimeParams: map[string]string{},
207			},
208		},
209		{
210			name:       "database url missing port",
211			connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable",
212			config: &pgconn.Config{
213				User:          "jack",
214				Password:      "secret",
215				Host:          "localhost",
216				Port:          5432,
217				Database:      "mydb",
218				TLSConfig:     nil,
219				RuntimeParams: map[string]string{},
220			},
221		},
222		{
223			name:       "database url unix domain socket host",
224			connString: "postgres:///foo?host=/tmp",
225			config: &pgconn.Config{
226				User:          osUserName,
227				Host:          "/tmp",
228				Port:          5432,
229				Database:      "foo",
230				TLSConfig:     nil,
231				RuntimeParams: map[string]string{},
232			},
233		},
234		{
235			name:       "database url dbname",
236			connString: "postgres://localhost/?dbname=foo&sslmode=disable",
237			config: &pgconn.Config{
238				User:          osUserName,
239				Host:          "localhost",
240				Port:          5432,
241				Database:      "foo",
242				TLSConfig:     nil,
243				RuntimeParams: map[string]string{},
244			},
245		},
246		{
247			name:       "database url postgresql protocol",
248			connString: "postgresql://jack@localhost:5432/mydb?sslmode=disable",
249			config: &pgconn.Config{
250				User:          "jack",
251				Host:          "localhost",
252				Port:          5432,
253				Database:      "mydb",
254				TLSConfig:     nil,
255				RuntimeParams: map[string]string{},
256			},
257		},
258		{
259			name:       "database url IPv4 with port",
260			connString: "postgresql://jack@127.0.0.1:5433/mydb?sslmode=disable",
261			config: &pgconn.Config{
262				User:          "jack",
263				Host:          "127.0.0.1",
264				Port:          5433,
265				Database:      "mydb",
266				TLSConfig:     nil,
267				RuntimeParams: map[string]string{},
268			},
269		},
270		{
271			name:       "database url IPv6 with port",
272			connString: "postgresql://jack@[2001:db8::1]:5433/mydb?sslmode=disable",
273			config: &pgconn.Config{
274				User:          "jack",
275				Host:          "2001:db8::1",
276				Port:          5433,
277				Database:      "mydb",
278				TLSConfig:     nil,
279				RuntimeParams: map[string]string{},
280			},
281		},
282		{
283			name:       "database url IPv6 no port",
284			connString: "postgresql://jack@[2001:db8::1]/mydb?sslmode=disable",
285			config: &pgconn.Config{
286				User:          "jack",
287				Host:          "2001:db8::1",
288				Port:          5432,
289				Database:      "mydb",
290				TLSConfig:     nil,
291				RuntimeParams: map[string]string{},
292			},
293		},
294		{
295			name:       "DSN everything",
296			connString: "user=jack password=secret host=localhost port=5432 dbname=mydb sslmode=disable application_name=pgxtest search_path=myschema connect_timeout=5",
297			config: &pgconn.Config{
298				User:           "jack",
299				Password:       "secret",
300				Host:           "localhost",
301				Port:           5432,
302				Database:       "mydb",
303				TLSConfig:      nil,
304				ConnectTimeout: 5 * time.Second,
305				RuntimeParams: map[string]string{
306					"application_name": "pgxtest",
307					"search_path":      "myschema",
308				},
309			},
310		},
311		{
312			name:       "DSN with escaped single quote",
313			connString: "user=jack\\'s password=secret host=localhost port=5432 dbname=mydb sslmode=disable",
314			config: &pgconn.Config{
315				User:          "jack's",
316				Password:      "secret",
317				Host:          "localhost",
318				Port:          5432,
319				Database:      "mydb",
320				TLSConfig:     nil,
321				RuntimeParams: map[string]string{},
322			},
323		},
324		{
325			name:       "DSN with escaped backslash",
326			connString: "user=jack password=sooper\\\\secret host=localhost port=5432 dbname=mydb sslmode=disable",
327			config: &pgconn.Config{
328				User:          "jack",
329				Password:      "sooper\\secret",
330				Host:          "localhost",
331				Port:          5432,
332				Database:      "mydb",
333				TLSConfig:     nil,
334				RuntimeParams: map[string]string{},
335			},
336		},
337		{
338			name:       "DSN with single quoted values",
339			connString: "user='jack' host='localhost' dbname='mydb' sslmode='disable'",
340			config: &pgconn.Config{
341				User:          "jack",
342				Host:          "localhost",
343				Port:          5432,
344				Database:      "mydb",
345				TLSConfig:     nil,
346				RuntimeParams: map[string]string{},
347			},
348		},
349		{
350			name:       "DSN with single quoted value with escaped single quote",
351			connString: "user='jack\\'s' host='localhost' dbname='mydb' sslmode='disable'",
352			config: &pgconn.Config{
353				User:          "jack's",
354				Host:          "localhost",
355				Port:          5432,
356				Database:      "mydb",
357				TLSConfig:     nil,
358				RuntimeParams: map[string]string{},
359			},
360		},
361		{
362			name:       "DSN with empty single quoted value",
363			connString: "user='jack' password='' host='localhost' dbname='mydb' sslmode='disable'",
364			config: &pgconn.Config{
365				User:          "jack",
366				Host:          "localhost",
367				Port:          5432,
368				Database:      "mydb",
369				TLSConfig:     nil,
370				RuntimeParams: map[string]string{},
371			},
372		},
373		{
374			name:       "DSN with space between key and value",
375			connString: "user = 'jack' password = '' host = 'localhost' dbname = 'mydb' sslmode='disable'",
376			config: &pgconn.Config{
377				User:          "jack",
378				Host:          "localhost",
379				Port:          5432,
380				Database:      "mydb",
381				TLSConfig:     nil,
382				RuntimeParams: map[string]string{},
383			},
384		},
385		{
386			name:       "URL multiple hosts",
387			connString: "postgres://jack:secret@foo,bar,baz/mydb?sslmode=disable",
388			config: &pgconn.Config{
389				User:          "jack",
390				Password:      "secret",
391				Host:          "foo",
392				Port:          5432,
393				Database:      "mydb",
394				TLSConfig:     nil,
395				RuntimeParams: map[string]string{},
396				Fallbacks: []*pgconn.FallbackConfig{
397					&pgconn.FallbackConfig{
398						Host:      "bar",
399						Port:      5432,
400						TLSConfig: nil,
401					},
402					&pgconn.FallbackConfig{
403						Host:      "baz",
404						Port:      5432,
405						TLSConfig: nil,
406					},
407				},
408			},
409		},
410		{
411			name:       "URL multiple hosts and ports",
412			connString: "postgres://jack:secret@foo:1,bar:2,baz:3/mydb?sslmode=disable",
413			config: &pgconn.Config{
414				User:          "jack",
415				Password:      "secret",
416				Host:          "foo",
417				Port:          1,
418				Database:      "mydb",
419				TLSConfig:     nil,
420				RuntimeParams: map[string]string{},
421				Fallbacks: []*pgconn.FallbackConfig{
422					&pgconn.FallbackConfig{
423						Host:      "bar",
424						Port:      2,
425						TLSConfig: nil,
426					},
427					&pgconn.FallbackConfig{
428						Host:      "baz",
429						Port:      3,
430						TLSConfig: nil,
431					},
432				},
433			},
434		},
435		// https://github.com/jackc/pgconn/issues/72
436		{
437			name:       "URL without host but with port still uses default host",
438			connString: "postgres://jack:secret@:1/mydb?sslmode=disable",
439			config: &pgconn.Config{
440				User:          "jack",
441				Password:      "secret",
442				Host:          defaultHost,
443				Port:          1,
444				Database:      "mydb",
445				TLSConfig:     nil,
446				RuntimeParams: map[string]string{},
447			},
448		},
449		{
450			name:       "DSN multiple hosts one port",
451			connString: "user=jack password=secret host=foo,bar,baz port=5432 dbname=mydb sslmode=disable",
452			config: &pgconn.Config{
453				User:          "jack",
454				Password:      "secret",
455				Host:          "foo",
456				Port:          5432,
457				Database:      "mydb",
458				TLSConfig:     nil,
459				RuntimeParams: map[string]string{},
460				Fallbacks: []*pgconn.FallbackConfig{
461					&pgconn.FallbackConfig{
462						Host:      "bar",
463						Port:      5432,
464						TLSConfig: nil,
465					},
466					&pgconn.FallbackConfig{
467						Host:      "baz",
468						Port:      5432,
469						TLSConfig: nil,
470					},
471				},
472			},
473		},
474		{
475			name:       "DSN multiple hosts multiple ports",
476			connString: "user=jack password=secret host=foo,bar,baz port=1,2,3 dbname=mydb sslmode=disable",
477			config: &pgconn.Config{
478				User:          "jack",
479				Password:      "secret",
480				Host:          "foo",
481				Port:          1,
482				Database:      "mydb",
483				TLSConfig:     nil,
484				RuntimeParams: map[string]string{},
485				Fallbacks: []*pgconn.FallbackConfig{
486					&pgconn.FallbackConfig{
487						Host:      "bar",
488						Port:      2,
489						TLSConfig: nil,
490					},
491					&pgconn.FallbackConfig{
492						Host:      "baz",
493						Port:      3,
494						TLSConfig: nil,
495					},
496				},
497			},
498		},
499		{
500			name:       "multiple hosts and fallback tsl",
501			connString: "user=jack password=secret host=foo,bar,baz dbname=mydb sslmode=prefer",
502			config: &pgconn.Config{
503				User:     "jack",
504				Password: "secret",
505				Host:     "foo",
506				Port:     5432,
507				Database: "mydb",
508				TLSConfig: &tls.Config{
509					InsecureSkipVerify: true,
510				},
511				RuntimeParams: map[string]string{},
512				Fallbacks: []*pgconn.FallbackConfig{
513					&pgconn.FallbackConfig{
514						Host:      "foo",
515						Port:      5432,
516						TLSConfig: nil,
517					},
518					&pgconn.FallbackConfig{
519						Host: "bar",
520						Port: 5432,
521						TLSConfig: &tls.Config{
522							InsecureSkipVerify: true,
523						}},
524					&pgconn.FallbackConfig{
525						Host:      "bar",
526						Port:      5432,
527						TLSConfig: nil,
528					},
529					&pgconn.FallbackConfig{
530						Host: "baz",
531						Port: 5432,
532						TLSConfig: &tls.Config{
533							InsecureSkipVerify: true,
534						}},
535					&pgconn.FallbackConfig{
536						Host:      "baz",
537						Port:      5432,
538						TLSConfig: nil,
539					},
540				},
541			},
542		},
543		{
544			name:       "target_session_attrs",
545			connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable&target_session_attrs=read-write",
546			config: &pgconn.Config{
547				User:            "jack",
548				Password:        "secret",
549				Host:            "localhost",
550				Port:            5432,
551				Database:        "mydb",
552				TLSConfig:       nil,
553				RuntimeParams:   map[string]string{},
554				ValidateConnect: pgconn.ValidateConnectTargetSessionAttrsReadWrite,
555			},
556		},
557	}
558
559	for i, tt := range tests {
560		config, err := pgconn.ParseConfig(tt.connString)
561		if !assert.Nilf(t, err, "Test %d (%s)", i, tt.name) {
562			continue
563		}
564
565		assertConfigsEqual(t, tt.config, config, fmt.Sprintf("Test %d (%s)", i, tt.name))
566	}
567}
568
569// https://github.com/jackc/pgconn/issues/47
570func TestParseConfigDSNWithTrailingEmptyEqualDoesNotPanic(t *testing.T) {
571	_, err := pgconn.ParseConfig("host= user= password= port= database=")
572	require.NoError(t, err)
573}
574
575func TestParseConfigDSNLeadingEqual(t *testing.T) {
576	_, err := pgconn.ParseConfig("= user=jack")
577	require.Error(t, err)
578}
579
580// https://github.com/jackc/pgconn/issues/49
581func TestParseConfigDSNTrailingBackslash(t *testing.T) {
582	_, err := pgconn.ParseConfig(`x=x\`)
583	require.Error(t, err)
584	assert.Contains(t, err.Error(), "invalid backslash")
585}
586
587func TestConfigCopyReturnsEqualConfig(t *testing.T) {
588	connString := "postgres://jack:secret@localhost:5432/mydb?application_name=pgxtest&search_path=myschema&connect_timeout=5"
589	original, err := pgconn.ParseConfig(connString)
590	require.NoError(t, err)
591
592	copied := original.Copy()
593	assertConfigsEqual(t, original, copied, "Test Config.Copy() returns equal config")
594}
595
596func TestConfigCopyOriginalConfigDidNotChange(t *testing.T) {
597	connString := "postgres://jack:secret@localhost:5432/mydb?application_name=pgxtest&search_path=myschema&connect_timeout=5&sslmode=prefer"
598	original, err := pgconn.ParseConfig(connString)
599	require.NoError(t, err)
600
601	copied := original.Copy()
602	assertConfigsEqual(t, original, copied, "Test Config.Copy() returns equal config")
603
604	copied.Port = uint16(5433)
605	copied.RuntimeParams["foo"] = "bar"
606	copied.Fallbacks[0].Port = uint16(5433)
607
608	assert.Equal(t, uint16(5432), original.Port)
609	assert.Equal(t, "", original.RuntimeParams["foo"])
610	assert.Equal(t, uint16(5432), original.Fallbacks[0].Port)
611}
612
613func TestConfigCopyCanBeUsedToConnect(t *testing.T) {
614	connString := os.Getenv("PGX_TEST_CONN_STRING")
615	original, err := pgconn.ParseConfig(connString)
616	require.NoError(t, err)
617
618	copied := original.Copy()
619	assert.NotPanics(t, func() {
620		_, err = pgconn.ConnectConfig(context.Background(), copied)
621	})
622	assert.NoError(t, err)
623}
624
625func assertConfigsEqual(t *testing.T, expected, actual *pgconn.Config, testName string) {
626	if !assert.NotNil(t, expected) {
627		return
628	}
629	if !assert.NotNil(t, actual) {
630		return
631	}
632
633	assert.Equalf(t, expected.Host, actual.Host, "%s - Host", testName)
634	assert.Equalf(t, expected.Database, actual.Database, "%s - Database", testName)
635	assert.Equalf(t, expected.Port, actual.Port, "%s - Port", testName)
636	assert.Equalf(t, expected.User, actual.User, "%s - User", testName)
637	assert.Equalf(t, expected.Password, actual.Password, "%s - Password", testName)
638	assert.Equalf(t, expected.ConnectTimeout, actual.ConnectTimeout, "%s - ConnectTimeout", testName)
639	assert.Equalf(t, expected.RuntimeParams, actual.RuntimeParams, "%s - RuntimeParams", testName)
640
641	// Can't test function equality, so just test that they are set or not.
642	assert.Equalf(t, expected.ValidateConnect == nil, actual.ValidateConnect == nil, "%s - ValidateConnect", testName)
643	assert.Equalf(t, expected.AfterConnect == nil, actual.AfterConnect == nil, "%s - AfterConnect", testName)
644
645	if assert.Equalf(t, expected.TLSConfig == nil, actual.TLSConfig == nil, "%s - TLSConfig", testName) {
646		if expected.TLSConfig != nil {
647			assert.Equalf(t, expected.TLSConfig.InsecureSkipVerify, actual.TLSConfig.InsecureSkipVerify, "%s - TLSConfig InsecureSkipVerify", testName)
648			assert.Equalf(t, expected.TLSConfig.ServerName, actual.TLSConfig.ServerName, "%s - TLSConfig ServerName", testName)
649		}
650	}
651
652	if assert.Equalf(t, len(expected.Fallbacks), len(actual.Fallbacks), "%s - Fallbacks", testName) {
653		for i := range expected.Fallbacks {
654			assert.Equalf(t, expected.Fallbacks[i].Host, actual.Fallbacks[i].Host, "%s - Fallback %d - Host", testName, i)
655			assert.Equalf(t, expected.Fallbacks[i].Port, actual.Fallbacks[i].Port, "%s - Fallback %d - Port", testName, i)
656
657			if assert.Equalf(t, expected.Fallbacks[i].TLSConfig == nil, actual.Fallbacks[i].TLSConfig == nil, "%s - Fallback %d - TLSConfig", testName, i) {
658				if expected.Fallbacks[i].TLSConfig != nil {
659					assert.Equalf(t, expected.Fallbacks[i].TLSConfig.InsecureSkipVerify, actual.Fallbacks[i].TLSConfig.InsecureSkipVerify, "%s - Fallback %d - TLSConfig InsecureSkipVerify", testName)
660					assert.Equalf(t, expected.Fallbacks[i].TLSConfig.ServerName, actual.Fallbacks[i].TLSConfig.ServerName, "%s - Fallback %d - TLSConfig ServerName", testName)
661				}
662			}
663		}
664	}
665}
666
667func TestParseConfigEnvLibpq(t *testing.T) {
668	var osUserName string
669	osUser, err := user.Current()
670	if err == nil {
671		// Windows gives us the username here as `DOMAIN\user` or `LOCALPCNAME\user`,
672		// but the libpq default is just the `user` portion, so we strip off the first part.
673		if runtime.GOOS == "windows" && strings.Contains(osUser.Username, "\\") {
674			osUserName = osUser.Username[strings.LastIndex(osUser.Username, "\\")+1:]
675		} else {
676			osUserName = osUser.Username
677		}
678	}
679
680	pgEnvvars := []string{"PGHOST", "PGPORT", "PGDATABASE", "PGUSER", "PGPASSWORD", "PGAPPNAME", "PGSSLMODE", "PGCONNECT_TIMEOUT"}
681
682	savedEnv := make(map[string]string)
683	for _, n := range pgEnvvars {
684		savedEnv[n] = os.Getenv(n)
685	}
686	defer func() {
687		for k, v := range savedEnv {
688			err := os.Setenv(k, v)
689			if err != nil {
690				t.Fatalf("Unable to restore environment: %v", err)
691			}
692		}
693	}()
694
695	tests := []struct {
696		name    string
697		envvars map[string]string
698		config  *pgconn.Config
699	}{
700		{
701			// not testing no environment at all as that would use default host and that can vary.
702			name:    "PGHOST only",
703			envvars: map[string]string{"PGHOST": "123.123.123.123"},
704			config: &pgconn.Config{
705				User: osUserName,
706				Host: "123.123.123.123",
707				Port: 5432,
708				TLSConfig: &tls.Config{
709					InsecureSkipVerify: true,
710				},
711				RuntimeParams: map[string]string{},
712				Fallbacks: []*pgconn.FallbackConfig{
713					&pgconn.FallbackConfig{
714						Host:      "123.123.123.123",
715						Port:      5432,
716						TLSConfig: nil,
717					},
718				},
719			},
720		},
721		{
722			name: "All non-TLS environment",
723			envvars: map[string]string{
724				"PGHOST":            "123.123.123.123",
725				"PGPORT":            "7777",
726				"PGDATABASE":        "foo",
727				"PGUSER":            "bar",
728				"PGPASSWORD":        "baz",
729				"PGCONNECT_TIMEOUT": "10",
730				"PGSSLMODE":         "disable",
731				"PGAPPNAME":         "pgxtest",
732			},
733			config: &pgconn.Config{
734				Host:           "123.123.123.123",
735				Port:           7777,
736				Database:       "foo",
737				User:           "bar",
738				Password:       "baz",
739				ConnectTimeout: 10 * time.Second,
740				TLSConfig:      nil,
741				RuntimeParams:  map[string]string{"application_name": "pgxtest"},
742			},
743		},
744	}
745
746	for i, tt := range tests {
747		for _, n := range pgEnvvars {
748			err := os.Unsetenv(n)
749			require.NoError(t, err)
750		}
751
752		for k, v := range tt.envvars {
753			err := os.Setenv(k, v)
754			require.NoError(t, err)
755		}
756
757		config, err := pgconn.ParseConfig("")
758		if !assert.Nilf(t, err, "Test %d (%s)", i, tt.name) {
759			continue
760		}
761
762		assertConfigsEqual(t, tt.config, config, fmt.Sprintf("Test %d (%s)", i, tt.name))
763	}
764}
765
766func TestParseConfigReadsPgPassfile(t *testing.T) {
767	t.Parallel()
768
769	tf, err := ioutil.TempFile("", "")
770	require.NoError(t, err)
771
772	defer tf.Close()
773	defer os.Remove(tf.Name())
774
775	_, err = tf.Write([]byte("test1:5432:curlydb:curly:nyuknyuknyuk"))
776	require.NoError(t, err)
777
778	connString := fmt.Sprintf("postgres://curly@test1:5432/curlydb?sslmode=disable&passfile=%s", tf.Name())
779	expected := &pgconn.Config{
780		User:          "curly",
781		Password:      "nyuknyuknyuk",
782		Host:          "test1",
783		Port:          5432,
784		Database:      "curlydb",
785		TLSConfig:     nil,
786		RuntimeParams: map[string]string{},
787	}
788
789	actual, err := pgconn.ParseConfig(connString)
790	assert.NoError(t, err)
791
792	assertConfigsEqual(t, expected, actual, "passfile")
793}
794
795func TestParseConfigReadsPgServiceFile(t *testing.T) {
796	t.Parallel()
797
798	tf, err := ioutil.TempFile("", "")
799	require.NoError(t, err)
800
801	defer tf.Close()
802	defer os.Remove(tf.Name())
803
804	_, err = tf.Write([]byte(`
805[abc]
806host=abc.example.com
807port=9999
808dbname=abcdb
809user=abcuser
810
811[def]
812host = def.example.com
813dbname = defdb
814user = defuser
815application_name = spaced string
816`))
817	require.NoError(t, err)
818
819	tests := []struct {
820		name       string
821		connString string
822		config     *pgconn.Config
823	}{
824		{
825			name:       "abc",
826			connString: fmt.Sprintf("postgres:///?servicefile=%s&service=%s", tf.Name(), "abc"),
827			config: &pgconn.Config{
828				Host:     "abc.example.com",
829				Database: "abcdb",
830				User:     "abcuser",
831				Port:     9999,
832				TLSConfig: &tls.Config{
833					InsecureSkipVerify: true,
834				},
835				RuntimeParams: map[string]string{},
836				Fallbacks: []*pgconn.FallbackConfig{
837					&pgconn.FallbackConfig{
838						Host:      "abc.example.com",
839						Port:      9999,
840						TLSConfig: nil,
841					},
842				},
843			},
844		},
845		{
846			name:       "def",
847			connString: fmt.Sprintf("postgres:///?servicefile=%s&service=%s", tf.Name(), "def"),
848			config: &pgconn.Config{
849				Host:     "def.example.com",
850				Port:     5432,
851				Database: "defdb",
852				User:     "defuser",
853				TLSConfig: &tls.Config{
854					InsecureSkipVerify: true,
855				},
856				RuntimeParams: map[string]string{"application_name": "spaced string"},
857				Fallbacks: []*pgconn.FallbackConfig{
858					&pgconn.FallbackConfig{
859						Host:      "def.example.com",
860						Port:      5432,
861						TLSConfig: nil,
862					},
863				},
864			},
865		},
866		{
867			name:       "conn string has precedence",
868			connString: fmt.Sprintf("postgres://other.example.com:7777/?servicefile=%s&service=%s&sslmode=disable", tf.Name(), "abc"),
869			config: &pgconn.Config{
870				Host:          "other.example.com",
871				Database:      "abcdb",
872				User:          "abcuser",
873				Port:          7777,
874				TLSConfig:     nil,
875				RuntimeParams: map[string]string{},
876			},
877		},
878	}
879
880	for i, tt := range tests {
881		config, err := pgconn.ParseConfig(tt.connString)
882		if !assert.NoErrorf(t, err, "Test %d (%s)", i, tt.name) {
883			continue
884		}
885
886		assertConfigsEqual(t, tt.config, config, fmt.Sprintf("Test %d (%s)", i, tt.name))
887	}
888}
889
890func TestParseConfigExtractsMinReadBufferSize(t *testing.T) {
891	t.Parallel()
892
893	config, err := pgconn.ParseConfig("min_read_buffer_size=0")
894	require.NoError(t, err)
895	_, present := config.RuntimeParams["min_read_buffer_size"]
896	require.False(t, present)
897
898	// The buffer size is internal so there isn't much that can be done to test it other than see that the runtime param
899	// was removed.
900}
901