jackc/pgxで始めるgo+Postgres


Go言語でjackc/pgxを使ったPostgreSQLを使う機会ができたので、最小構成でスタートさせて理解を深めようとしました。

## 動作環境

```console
$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.1 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy

$ go version
go version go1.19.3 linux/amd64
```

## プロジェクトの初期化

`go.mod` にはモジュール名とGoのバージョンのみを指定します。

```console
$ mkdir go-postgres-sample
$ cd go-postgres-sample 
$ go mod init go-postgres-sample 
```

```go.mod
module go-postgres-sample 

go 1.19
```

## PostgreSQLサーバーを立ち上げる(docker-compose)

ここでは `docker-compose` を用いてPostgreSQLを立ち上げます。
下記の構成でファイルを用意して、ローカルで実行された `postgres.go` からDockerのポートフォワーディングを通してPostgreSQLコンテナーに接続します。

```
├── docker-compose.yml
├── go.mod
├── go.sum
├── postgres
│   └── 00-initilize-user-table.sql
└── postgres.go
```

`docker-compose.yml` は次の内容にします。接続確認ができる簡単なものです。

```yaml:./docker-compose.yml
version: '3'

services:
  postgres:
    container_name: go-postgres-sample
    image: postgres:15.1-alpine
    ports:
      - 5432:5432
    volumes:
      - ./postgres:/docker-entrypoint-initdb.d
    environment:
      POSTGRES_USER: go-dev
      POSTGRES_PASSWORD: go-dev
    restart: always
```

PostgreSQLコンテナーの `/docker-entrypoint-initdb.d` に任意のSQLを置くと初回起動時に実行してくれます。

```sql:postgres/00-initilize-user-table.sql
--テーブルを作成
CREATE TABLE users (
    id serial primary key,
    screen_name text not null,
    created timestamp with time zone not null default CURRENT_TIMESTAMP,
    updated timestamp with time zone not null default CURRENT_TIMESTAMP
);

--テーブルにデータを挿入
INSERT INTO users("screen_name") VALUES ('go');
```

準備ができたらPostgreSQLコンテナーを立ち上げます。

```console
$ docker compose up -d
```

データベースが先ほどのSQLで初期化されているかどうかは `docker compose logs` で確認できます。

```console
$ docker compose logs

...

go-postgres-sample  | /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/00-initilize-user-table.sql
```

次の表示が出てデータベースが初期化されない場合は `docker-compose.yml` の `volumes` が正しいか確認します。

> /usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/*

## jackc/pgxでクエリーを実行するサンプル

Goのプログラムは次の通りです。

```go:./postgres.go
package main

import (
	"context"
	"flag"
	"fmt"
	"os"

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

func main() {
	// "-uri" コマンドライン引数でPostgresの接続情報を取得
	uri := flag.String("uri", "", "Postgres URI")

	// flag.Parse()がないと値がデフォルト値になる
	flag.Parse()

	// コマンドライン引数のチェック
	if *uri == "" {
		fmt.Println("-uri option is required. \n example: postgres://username:password@localhost:5432/database_name")
		os.Exit(1)
	}

	// コネクションの作成
	fmt.Printf("Connecting: %v\n", *uri)
	conn, err := pgx.Connect(context.Background(), *uri)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
		os.Exit(1)
	}
	defer conn.Close(context.Background())

	// クエリー
	var screen_name string
	err = conn.QueryRow(context.Background(), "select screen_name from users where id=1").Scan(&screen_name)
	if err != nil {
		fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
		os.Exit(1)
	}

	fmt.Printf("Hello, @%v!\n", screen_name)
}
```

## パッケージの入手と動作確認

`go mod tidy` で `postgres.go` にある `jackc/pgx` を取得し、 `go.mod` を更新します。そのあとに `go run` でビルドと実行ができます。

```console
$ go mod tidy
$ go run postgres.go -uri postgres://go-dev:go-dev@127.0.0.1:5432/go-dev 
Connecting: postgres://go-dev:go-dev@127.0.0.1:5432/go-dev
Hello, @go!
```


これで動作確認できました。そのほかに `pgxpool` を用いてスレッドセーフなコネクションプールでPostgreSQLを操作することができます。


## 参考

pgx package - github.com/jackc/pgx/v5 - Go Packages
https://pkg.go.dev/github.com/jackc/pgx/v5

pgxpool package - github.com/jackc/pgx/v5/pgxpool - Go Packages
https://pkg.go.dev/github.com/jackc/pgx/v5/pgxpool

postgres - Official Image | Docker Hub
https://hub.docker.com/_/postgres