Installation
go get github.com/yugabyte/pgx/v4
Quick Start
Using pgx (Recommended)
package main
import (
"context"
"fmt"
"log"
"github.com/jackc/pgx/v4"
)
func main() {
// Connection string
connString := "postgresql://yugabyte:yugabyte@localhost:5433/yugabyte"
// Connect to database
conn, err := pgx.Connect(context.Background(), connString)
if err != nil {
log.Fatalf("Unable to connect to database: %v\n", err)
}
defer conn.Close(context.Background())
// Query database
var version string
err = conn.QueryRow(context.Background(), "SELECT version()").Scan(&version)
if err != nil {
log.Fatalf("Query failed: %v\n", err)
}
fmt.Printf("Database version: %s\n", version)
}
Using database/sql with lib/pq
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/lib/pq"
)
func main() {
connStr := "host=localhost port=5433 user=yugabyte password=yugabyte dbname=yugabyte sslmode=disable"
db, err := sql.Open("postgres", connStr)
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Test connection
err = db.Ping()
if err != nil {
log.Fatal(err)
}
var version string
err = db.QueryRow("SELECT version()").Scan(&version)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Database version: %s\n", version)
}
Connection Configuration
pgx Connection String
connString := "postgresql://yugabyte:yugabyte@localhost:5433/yugabyte?sslmode=disable"
conn, err := pgx.Connect(context.Background(), connString)
SSL/TLS Configuration
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"github.com/jackc/pgx/v4"
)
// Load CA certificate
caCert, err := ioutil.ReadFile("/path/to/root.crt")
if err != nil {
log.Fatal(err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
// Configure TLS
tlsConfig := &tls.Config{
RootCAs: caCertPool,
InsecureSkipVerify: false,
}
config, err := pgx.ParseConfig(
"postgresql://yugabyte:yugabyte@localhost:5433/yugabyte?sslmode=verify-full"
)
if err != nil {
log.Fatal(err)
}
config.TLSConfig = tlsConfig
conn, err := pgx.ConnectConfig(context.Background(), config)
Connection Pooling
pgxpool
import (
"context"
"fmt"
"log"
"github.com/jackc/pgx/v4/pgxpool"
)
func main() {
// Create connection pool
poolConfig, err := pgxpool.ParseConfig(
"postgresql://yugabyte:yugabyte@localhost:5433/yugabyte"
)
if err != nil {
log.Fatal(err)
}
// Configure pool settings
poolConfig.MaxConns = 20
poolConfig.MinConns = 5
poolConfig.MaxConnLifetime = 1 * time.Hour
poolConfig.MaxConnIdleTime = 30 * time.Minute
pool, err := pgxpool.ConnectConfig(context.Background(), poolConfig)
if err != nil {
log.Fatal(err)
}
defer pool.Close()
// Use pool for queries
var count int
err = pool.QueryRow(context.Background(), "SELECT COUNT(*) FROM users").Scan(&count)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Total users: %d\n", count)
}
database/sql Connection Pool
import (
"database/sql"
"time"
_ "github.com/lib/pq"
)
func main() {
connStr := "host=localhost port=5433 user=yugabyte password=yugabyte dbname=yugabyte sslmode=disable"
db, err := sql.Open("postgres", connStr)
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Configure connection pool
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(1 * time.Hour)
db.SetConnMaxIdleTime(10 * time.Minute)
// Test connection
err = db.Ping()
if err != nil {
log.Fatal(err)
}
}
CRUD Operations
Create (Insert)
// Single insert
var id int
var name string
err := pool.QueryRow(
context.Background(),
"INSERT INTO users (name, email, age) VALUES ($1, $2, $3) RETURNING id, name",
"John Doe", "[email protected]", 30,
).Scan(&id, &name)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Created user %s with ID %d\n", name, id)
// Bulk insert
batch := &pgx.Batch{}
for i := 0; i < 100; i++ {
batch.Queue(
"INSERT INTO users (name, email, age) VALUES ($1, $2, $3)",
fmt.Sprintf("User%d", i),
fmt.Sprintf("user%d@example.com", i),
20+i,
)
}
br := pool.SendBatch(context.Background(), batch)
defer br.Close()
for i := 0; i < 100; i++ {
_, err := br.Exec()
if err != nil {
log.Fatal(err)
}
}
Read (Select)
// Query single row
type User struct {
ID int
Name string
Email string
Age int
}
var user User
err := pool.QueryRow(
context.Background(),
"SELECT id, name, email, age FROM users WHERE id = $1",
1,
).Scan(&user.ID, &user.Name, &user.Email, &user.Age)
if err != nil {
log.Fatal(err)
}
// Query multiple rows
rows, err := pool.Query(
context.Background(),
"SELECT id, name, email, age FROM users WHERE age > $1",
25,
)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
var users []User
for rows.Next() {
var u User
err := rows.Scan(&u.ID, &u.Name, &u.Email, &u.Age)
if err != nil {
log.Fatal(err)
}
users = append(users, u)
}
if rows.Err() != nil {
log.Fatal(rows.Err())
}
Update
// Update single record
commandTag, err := pool.Exec(
context.Background(),
"UPDATE users SET age = $1 WHERE id = $2",
31, 1,
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Updated %d rows\n", commandTag.RowsAffected())
// Update with RETURNING
var updatedName string
err = pool.QueryRow(
context.Background(),
"UPDATE users SET age = $1 WHERE id = $2 RETURNING name",
32, 1,
).Scan(&updatedName)
if err != nil {
log.Fatal(err)
}
Delete
// Delete record
commandTag, err := pool.Exec(
context.Background(),
"DELETE FROM users WHERE id = $1",
1,
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Deleted %d rows\n", commandTag.RowsAffected())
Transaction Management
pgx Transactions
import (
"context"
"github.com/jackc/pgx/v4"
)
func transferMoney(pool *pgxpool.Pool, fromID, toID int, amount int) error {
ctx := context.Background()
// Begin transaction
tx, err := pool.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx) // Rollback if not committed
// Debit from account
_, err = tx.Exec(
ctx,
"UPDATE accounts SET balance = balance - $1 WHERE id = $2",
amount, fromID,
)
if err != nil {
return err
}
// Credit to account
_, err = tx.Exec(
ctx,
"UPDATE accounts SET balance = balance + $1 WHERE id = $2",
amount, toID,
)
if err != nil {
return err
}
// Commit transaction
return tx.Commit(ctx)
}
database/sql Transactions
func transferMoney(db *sql.DB, fromID, toID int, amount int) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
_, err = tx.Exec(
"UPDATE accounts SET balance = balance - $1 WHERE id = $2",
amount, fromID,
)
if err != nil {
return err
}
_, err = tx.Exec(
"UPDATE accounts SET balance = balance + $1 WHERE id = $2",
amount, toID,
)
if err != nil {
return err
}
return tx.Commit()
}
Best Practices
Context Usage
Always use context for cancellation and timeouts:import (
"context"
"time"
)
// Context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
var result string
err := pool.QueryRow(ctx, "SELECT expensive_query()").Scan(&result)
if err != nil {
if err == context.DeadlineExceeded {
log.Println("Query timed out")
}
log.Fatal(err)
}
Error Handling
import (
"errors"
"github.com/jackc/pgx/v4"
)
var user User
err := pool.QueryRow(
context.Background(),
"SELECT id, name FROM users WHERE id = $1",
999,
).Scan(&user.ID, &user.Name)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
fmt.Println("User not found")
} else {
log.Fatal(err)
}
}
Prepared Statements
// Prepare statement
stmt, err := pool.Prepare(
context.Background(),
"get_user",
"SELECT id, name, email FROM users WHERE id = $1",
)
if err != nil {
log.Fatal(err)
}
// Use prepared statement
var user User
err = pool.QueryRow(
context.Background(),
"get_user",
1,
).Scan(&user.ID, &user.Name, &user.Email)
Graceful Shutdown
import (
"os"
"os/signal"
"syscall"
)
func main() {
pool, err := pgxpool.Connect(
context.Background(),
"postgresql://yugabyte:yugabyte@localhost:5433/yugabyte",
)
if err != nil {
log.Fatal(err)
}
// Setup signal handling
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigChan
fmt.Println("Shutting down...")
pool.Close()
os.Exit(0)
}()
// Application logic
// ...
}

