MongoDB.com公式Goドライバを使った基本操作

[Mgo](https://github.com/globalsign/mgo)を使った例は結構あるのだけど、MongoDB.comの公式ドライバを使った例があまりないなと思い、未来の自分のために書き残しておく。タグは[StackOverflowに合わせ](https://stackoverflow.com/questions/tagged/mongo-go)て `mongo-go` としてみた。

今回のソースコード一式は[https://github.com/mkiuchi/go-mongo-driver-example](https://github.com/mkiuchi/go-mongo-driver-example)に置いておきました。

# Mgoと公式ドライバの違いとか情報源とか

調べてないのでよくわからない。[公式でも若干触れられてる](https://www.mongodb.com/blog/post/go-migration-guide)けどあまりこのへんをどうこう言う気はないので気になる人は各自なんとかしてください。

公式ドライバについては[MongoDB.comのドライバページ](https://docs.mongodb.com/ecosystem/drivers/)を見る。記事執筆時点の最新バージョンは 1.0.3。たぶん最もよく見るのは[GoDoc](https://godoc.org/go.mongodb.org/mongo-driver/mongo), [チュートリアル](https://www.mongodb.com/blog/post/mongodb-go-driver-tutorial)だとは思うんだけど、いまいち詳細さに欠けていて、これを見てもわかるようなわからないようなイマイチわかりきらないところがある。この記事を書くことにした理由の一つ。

上でも書いたけど、公式以外で私が見た限りでは[StackOverflow](https://stackoverflow.com/questions/tagged/mongo-go)が情報源としては一番ある。あるけどほとんど会話がなく、正直あてにならない。各自頑張ってください。

# インストール

公式のとおりにすれば入る。私の場合は `dep` が動かなかったので `go get` で入れた。

```shell
$ dep ensure -add "go.mongodb.org/mongo-driver/mongo@~1.0.0"
```

```shell
$ go get -u "go.mongodb.org/mongo-driver/mongo"
$ go get "go.mongodb.org/mongo-driver/bsontype"
```

# 接続

以下のようにする。以下の例ではデータベースへの接続をチェックして、結果を出力する。

```go:connect.go
package main

import (
	"context"
	"fmt"
	"time"

	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"go.mongodb.org/mongo-driver/mongo/readpref"
)

func main() {
	// コンテキストの作成
	//   - バックグラウンドで接続する。タイムアウトは10秒
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	// 関数を抜けたらクローズするようにする
	defer cancel()
	// 指定したURIに接続する
	c, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017"))
	defer c.Disconnect(ctx)
	// DBにPingする
	err = c.Ping(ctx, readpref.Primary())
	if err != nil {
		fmt.Println("connection error:", err)
	} else {
		fmt.Println("connection success:")
	}
}
```

接続オプションはだいたいどの記事でも [context.Background()](https://godoc.org/context#Background) が使用されているけど、例によっては [context.TODO()](https://godoc.org/context#TODO) も使用されていることがある。とりあえず context.Background() を使用しておけばいいと思う。

# 読み出し
## 単純に検索して1件読み出す

```golang:readone.go
package main

import (
	"context"
	"fmt"
	"time"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

func main() {
	// MongoDBの接続設定
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	c, _ := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017"))
	defer c.Disconnect(ctx)

	// 結果を格納する変数の構造定義
	type resultType struct {
		Rid     string
		Keyword string
	}

	// 結果を格納する変数を宣言
	var result resultType

	// MongoDBのCollectionを取得
	col := c.Database("rec").Collection("autorec")

	// 検索条件となるprimitive.ObjectID型の変数を指定
	objectID, _ := primitive.ObjectIDFromHex("5cffa613c74a91322fc7cbb2")

	// 検索を実行し、結果を変数 result に格納
	err := col.FindOne(context.Background(), bson.M{"_id": objectID}).Decode(&result)
	_ = err
	fmt.Println(result)
}
```

結果は以下のような感じになる

```shell
$ go run readone.go 
{abcd1234 オリンピック}
```

## 複数の検索結果を読み出す

```golang:readmany.go
package main

import (
	"context"
	"fmt"
	"time"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

func main() {
	// MongoDBの接続設定
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	c, _ := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017"))
	defer c.Disconnect(ctx)

	// 結果を格納する変数の構造定義
	type resultType struct {
		Start int
		Title string
	}

	// MongoDBのCollectionを取得
	col := c.Database("rec").Collection("rec")

	// 検索条件となるint型の変数を指定
	location, _ := time.LoadLocation("Asia/Tokyo")
	start := time.Date(2019, 6, 30, 0, 0, 0, 0, location).Unix() * 1000
	end := time.Date(2019, 6, 30, 23, 59, 59, 99, location).Unix() * 1000
	fmt.Println(start, end)

	// 検索を実行
	cur, err := col.Find(context.Background(), bson.M{
		"start": bson.M{
			"$gte": start,
			"$lte": end,
		}})
	_ = err

	// 結果のカーソルをforで回して順番に結果を取得
	for cur.Next(context.Background()) {
		var ret resultType
		cur.Decode(&ret)
		fmt.Println(ret)
	}
}
```

結果は以下のようになる

```shell
$ go run readmany.go 
1561820400000 1561906799000
{1561879320000 プレマップ}
{1561880460000 フラッシュ天気}
```

# ドキュメント(=レコード)の挿入

```golang:insert.go
package main

import (
	"context"
	"time"

	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

func main() {
	// MongoDBの接続設定
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	c, _ := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017"))
	defer c.Disconnect(ctx)

	// 挿入するデータの構造を定義
	// 21, 22行目の末尾の文字はType aliasと呼ぶらしい。MongoDB内でフィールド名として解釈される
	type dataType struct {
		Rid     string `bson:"rid"`
		Keyword string `bson:"keyword"`
	}

	// 挿入するデータを作成
	data := dataType{
		Rid:     "俺のID",
		Keyword: "俺のキーワード",
	}

	// MongoDBのCollectionを取得
	col := c.Database("rec").Collection("autorec")

	// データを挿入
	col.InsertOne(context.Background(), data)

}
```

上記の例では構造体(struct)を挿入しているが、他の型(例えばmapとか)を直接突っ込んでも割とよろしく取り扱ってくれる

複数データのバルク挿入はまだやったことがないのでスキップ。

# ドキュメント(=レコード)の削除

```golang:delete.go
package main

import (
	"context"
	"fmt"
	"time"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

func main() {
	// MongoDBの接続設定
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	c, _ := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017"))
	defer c.Disconnect(ctx)

	// MongoDBのCollectionを取得
	col := c.Database("rec").Collection("autorec")

	// 検索条件となるprimitive.ObjectID型の変数を指定
	objectID, _ := primitive.ObjectIDFromHex("5d1924916a81c3556cf3479b")

	// 検索を実行し、結果のドキュメントを削除
	_, err := col.DeleteOne(context.Background(), bson.M{"_id": objectID})
	if err != nil {
		fmt.Println("delete failed:", err)
	} else {
		fmt.Println("delete success")
	}
}
```

特にコメントなし。