Setiap program yang bekerja dengan database dimulai dari langkah yang sama: membuka handle koneksi. Di Go, langkah tersebut memakai sql.Open dengan dua argumen — nama driver dan Data Source Name (DSN) berisi alamat serta kredensial. Di balik kesederhanaannya tersimpan dua hal yang sering mengejutkan pemula: sql.Open sebenarnya tidak langsung tersambung ke server, dan kesalahan penulisan DSN bisa lolos tanpa terdeteksi bila kita tidak memverifikasinya. Chapter ini membahas format DSN PostgreSQL, perilaku malas sql.Open, verifikasi dengan Ping, serta pesan error nyata yang muncul ketika driver lupa diimpor.

Format Data Source Name (DSN) #

DSN adalah string ringkas yang menjelaskan ke mana dan bagaimana program harus tersambung. Driver pgx menerima dua format: bentuk URL dan bentuk pasangan kunci=nilai:

text
postgres://budi:[email protected]:5432/toko?sslmode=disable
text
host=db.example.com port=5432 user=budi password=YOUR_DATABASE_PASSWORD dbname=toko sslmode=disable

Komponen penyusunnya sama di kedua format:

ParameterContoh nilaiArti
hostdb.example.comAlamat server PostgreSQL
port5432Port server; 5432 adalah bawaan PostgreSQL
userbudiNama pengguna database
passwordYOUR_DATABASE_PASSWORDKredensial pengguna
dbnametokoNama database yang dipakai
sslmodedisable, requireMode enkripsi koneksi

sql.Open Bersifat Malas #

Pemanggilan sql.Open("pgx", dsn) hanya melakukan dua hal: memeriksa bahwa nama driver "pgx" sudah terdaftar, lalu menyimpan DSN di dalam objek *sql.DB yang dikembalikan. Tidak ada satu paket jaringan pun yang dikirim ke server pada saat itu. Koneksi fisik baru dibangun ketika operasi pertama berjalan — Ping, Query, atau Exec.

Konsekuensinya serius: nama host yang salah, password yang kedaluwarsa, atau database yang belum dibuat semuanya lolos dari sql.Open tanpa error. Masalah baru muncul saat query pertama tiba — momen terburuk untuk mengetahui aplikasi tidak bisa tersambung. Karena itu, program produksi selalu memverifikasi koneksi segera setelah sql.Open. Kode berikut membutuhkan PostgreSQL aktif untuk dijalankan; versi lengkapnya dipraktikkan di proyek REST API dengan PostgreSQL :

go
package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	"time"

	_ "github.com/jackc/pgx/v5/stdlib"
)

func main() {
	dsn := "postgres://budi:[email protected]:5432/toko?sslmode=disable"

	db, err := sql.Open("pgx", dsn)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := db.PingContext(ctx); err != nil {
		log.Fatal("database tidak terjangkau: ", err)
	}
	fmt.Println("tersambung ke PostgreSQL")
}

Perhatikan dua pemeriksaan error dengan peran berbeda: error dari sql.Open hanya muncul untuk masalah administratif seperti nama driver salah, sedangkan PingContext-lah yang benar-benar menyentuh server dan membuktikan DSN kita valid. context.WithTimeout membatasi waktu ping lima detik supaya startup tidak menggantung selamanya ketika server tidak merespons.

Demo: Mengurai DSN dan Mengamati Error Driver #

Berkas contoh.go pada bagian bawah halaman mengurai DSN bergaya URL menjadi komponen-komponennya menggunakan net/url, lalu sengaja memanggil sql.Open tanpa blank import driver — sehingga pesan error asli Go 1.27 tentang driver tak terdaftar bisa kita amati:

go
package main

import (
	"database/sql"
	"fmt"
	"net/url"
	"strings"
)

// parseDSN mengurai DSN bergaya URL PostgreSQL menjadi komponennya.
func parseDSN(dsn string) (map[string]string, error) {
	u, err := url.Parse(dsn)
	if err != nil {
		return nil, err
	}
	hasil := map[string]string{
		"host":   u.Hostname(),
		"port":   u.Port(),
		"dbname": strings.TrimPrefix(u.Path, "/"),
	}
	if u.User != nil {
		hasil["user"] = u.User.Username()
		if sandi, ok := u.User.Password(); ok {
			hasil["password"] = sandi
		}
	}
	hasil["sslmode"] = u.Query().Get("sslmode")
	return hasil, nil
}

func main() {
	dsn := "postgres://budi:[email protected]:5432/toko?sslmode=disable"

	bagian, err := parseDSN(dsn)
	if err != nil {
		fmt.Println("gagal mengurai DSN:", err)
		return
	}
	for _, k := range []string{"host", "port", "user", "password", "dbname", "sslmode"} {
		fmt.Printf("%s=%s\n", k, bagian[k])
	}

	// Nama driver "pgx" sengaja tidak didaftarkan (program ini tidak
	// memakai blank import driver) agar pesan error aslinya terlihat.
	db, err := sql.Open("pgx", dsn)
	fmt.Println("err dari sql.Open:", err)
	fmt.Println("db bernilai nil?:", db == nil)
}

Output yang Dihasilkan:

text
host=db.example.com
port=5432
user=budi
password=YOUR_DATABASE_PASSWORD
dbname=toko
sslmode=disable
err dari sql.Open: sql: unknown driver "pgx" (forgotten import?)
db bernilai nil?: true

Mari kita telaah baris demi baris program di atas:

  1. Baris 12 (url.Parse): memecah DSN menjadi struktur url.URL — host, path, query, dan kredensial dipisah otomatis oleh standard library.
  2. Baris 16-19 (hasil := map[string]string{...}): u.Hostname() mengambil alamat tanpa port, u.Port() mengambil port, dan strings.TrimPrefix membuang garis miring di depan nama database.
  3. Baris 21-25 (if u.User != nil): bagian user:password pada URL diakses lewat u.User; Password() mengembalikan nilai kedua ok karena password boleh kosong.
  4. Baris 27 (u.Query().Get): parameter query sslmode dibaca seperti query string HTTP biasa.
  5. Baris 39-41 (for _, k := range ...): key map dicetak dalam urutan tetap lewat slice literal, bukan diiterasi langsung — iterasi map di Go memiliki urutan acak.
  6. Baris 45-47 (sql.Open): tanpa blank import pgx, nama "pgx" tidak terdaftar sehingga Go mengembalikan pesan sql: unknown driver "pgx" (forgotten import?) — pengingat langsung dari standard library bahwa import driver terlupakan.

Program demo ini sekaligus menjelaskan mengapa error sql.Open jarang berhubungan dengan server: pemeriksaan terjadi di sisi aplikasi, pada tabel registrasi driver.

Pemanggilan db.Close() menutup seluruh koneksi yang dikelola pool di dalam *sql.DB. Karena satu sql.DB dirancang hidup sepanjang umur aplikasi, Close pada umumnya hanya dipanggil saat aplikasi mematikan diri — biasanya lewat defer db.Close() di main atau di rute shutdown. Membuka dan menutup sql.DB di setiap fungsi justru merusak pool dan menghabiskan waktu handshake TCP.

Koneksi kini bisa dibuka dan diverifikasi. Tetapi di balik satu sql.DB, berapa koneksi yang benar-benar dibuat? Chapter berikutnya mengulas connection pool — MaxOpenConns, MaxIdleConns, dan umur koneksi — beserta cara menghitung angka yang tepat untuk aplikasi kita.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

contoh.go

Unduh berkas
go
package main

import (
	"database/sql"
	"fmt"
	"net/url"
	"strings"
)

// parseDSN mengurai DSN bergaya URL PostgreSQL menjadi komponennya.
func parseDSN(dsn string) (map[string]string, error) {
	u, err := url.Parse(dsn)
	if err != nil {
		return nil, err
	}
	hasil := map[string]string{
		"host":   u.Hostname(),
		"port":   u.Port(),
		"dbname": strings.TrimPrefix(u.Path, "/"),
	}
	if u.User != nil {
		hasil["user"] = u.User.Username()
		if sandi, ok := u.User.Password(); ok {
			hasil["password"] = sandi
		}
	}
	hasil["sslmode"] = u.Query().Get("sslmode")
	return hasil, nil
}

func main() {
	dsn := "postgres://budi:[email protected]:5432/toko?sslmode=disable"

	bagian, err := parseDSN(dsn)
	if err != nil {
		fmt.Println("gagal mengurai DSN:", err)
		return
	}
	for _, k := range []string{"host", "port", "user", "password", "dbname", "sslmode"} {
		fmt.Printf("%s=%s\n", k, bagian[k])
	}

	// Nama driver "pgx" sengaja tidak didaftarkan (program ini tidak
	// memakai blank import driver) agar pesan error aslinya terlihat.
	db, err := sql.Open("pgx", dsn)
	fmt.Println("err dari sql.Open:", err)
	fmt.Println("db bernilai nil?:", db == nil)
}