MongoDBの Offical Go言語 Driverを使ってみる（２）FindOne

前回投稿した「[MongoDBの Offical Go言語 Driverを使ってみる（１）準備・Insert](https://qiita.com/h6591/items/f3a7c1bca31cfa634cca)」の続きでFindOne編です。

# FindOne
**FindOne**は検索条件にマッチした最初の1件目のドキュメントを取得します

## FindOne書式

```go
FindOne(context.Context, 検索条件, FindOne オプション)
```

## 検索条件なし、オプションなしでの実行可能なソース

```go
package main

import (
	"context"
	"fmt"
	"log"

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

func mainMain() error {
	client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017"))
	if err != nil {
		return err
	}
	if err = client.Connect(context.Background()); err != nil {
		return err
	}
	defer client.Disconnect(context.Background())
	col := client.Database("test").Collection("col")
	var doc bson.Raw
	findOptions := options.FindOne()
	err = col.FindOne(context.Background(), bson.D{}, findOptions).Decode(&doc)
	if err == mongo.ErrNoDocuments {
		log.Println("Documents not found")
		return nil
	} else if err != nil {
		return err
	}
	fmt.Println(doc.String())
	return nil
}

func main() {
	if err := mainMain(); err != nil {
		log.Fatal(err)
	}
	log.Println("normal end.")
}

```

- **bson.D{}**
    - これは空ドキュメントで検索条件なしです
- **findOptions**
    - オプションですが、ここでは何も設定していないので省略可能です
    - **projection,sort,skip**などが設定できます
- **Decode(&doc)**
    - 取得したドキュメントをデコードします。この例では**doc**変数は**bson.Raw**なので**BSON**のまま受け取ります
- エラー**mongo.ErrNoDocuments**
    - 検索条件にマッチしたドキュメントが１件もないときにこのエラーを返します。

**（注意）** **doc.String()**でBSONをJSON化して表示していますが、リリースされたv1.0.0ではバグがありObjectIdの表示でコードが""で囲まれていません。そのため、このJSONをBSON化しようとするとエラーになります。最新では修正されていますが、v1.0.0を使っている方は注意してください。修正箇所は以下のURLの通りです。
https://github.com/mongodb/mongo-go-driver/commit/c72645a64800adf5455dd9ad9cb811c0bf7d34c1

## 検索条件の設定の仕方
紹介するのは次の３つタイプです。

- bson.D
- 構造体(struct)
- JSON文字列

前回（１）で投稿したときに使ったデータと同じものを想定しています。

```json
{"str1": "abc", "num1":1, "str2": "xyz", "num2": [2,3,4], "subdoc": {"str": "subdoc", "num": 987},"date": 現在の日付}
```
#### bson.Dを使った検索条件例

```go
//{str1: "abc"}
filter := bson.D{{"str1","abc"}}
//{str1: "abc", num1: 1}
filter := bson.D{{"str1","abc"},{"num1",1}} 
//{num2: {$gt: 3}}
filter := bson.D{{"num2", bson.D{{"$gt", 3}}}}

//------------------------------------------------------------------------
err = col.FindOne(context.Background(), filter, findOptions).Decode(&doc)
```


### 構造体を使った検索条件例
```go
//{str1: "abc"}
filter := struct {
    Str1 string
}{"abc"}

//{str1: "abc", num1: 1}
filter := struct {
    Str1 string
    Num1 int 
}{"abc", 1}

//{num2: {$gt: 3}}
filter := struct {
	Num2 struct {
		Gt int "$gt"
	}
}{}
filter.Num2.Gt = 3
//------------------------------------------------------------------------
err = col.FindOne(context.Background(), filter, findOptions).Decode(&doc)
```
**$gt**は変数名にできないのでタグで指定します。

### JSON文字列を使った検索条件例

```go
//{str1: "abc"}
jsonFilter := `{"str1": "abc"}`
//{str1: "abc", num1: 1}
jsonFilter := `{"str1": "abc", "num1": 1}` 
//{num2: {$gt: 3}}
jsonFilter := `{"num2": {"$gt": 3}}`

//------------------------------------------------------------------------

var filter bson.Raw
err = bson.UnmarshalExtJSON([]byte(jsonFilter), false, &filter)
if err != nil {
	return err
}
err = col.FindOne(context.Background(), filter, findOptions).Decode(&doc)
```

## FindOneオプションのProjectionの例

検索条件同様に設定できるタイプは**bson.D,構造体,JSON文字列**などがあります。ここでは**bson.D**の例だけ示します。

```go
findOptions := options.FindOne()
findOptions.SetProjection(bson.D{{"_id", 0},{"str1",1},{"num1",1}})
```



## 検索したドキュメントを受け取るデータタイプ

- bson.D
- bson.M
- 構造体(struct)
- bson.Raw


### bson.D
```go
	var doc bson.D
	err = col.FindOne(context.Background(), filter, findOptions).Decode(&doc)	
	//エラー処理 
	for _, item := range doc {
		val := item.Value
		typ := reflect.TypeOf(val)
		fmt.Printf("type:%s, key:%s, val:%v\n", typ, item.Key, val)
	}
	//num1
	num1 := doc[2]
	num1Val := num1.Value.(int32)
	fmt.Printf("num1: %d\n", num1Val)
```
**bson.D**は**map**ではないので**num1**をキーにして値を取得できない。上記の例では**num1**を**doc[2]**しているが実際においては必ずしも**doc[2]**とは限らない。

### bson.M

```go
	var doc bson.M
	err = col.FindOne(context.Background(), filter, findOptions).Decode(&doc)
	//エラー処理 
	for key, val := range doc {
		typ := reflect.TypeOf(val)
		fmt.Printf("type:%s, key:%s, val:%v\n", typ, key, val)
	}
	//num1
	num1Val := doc["num1"].(int32)
	fmt.Printf("num1: %d\n", num1Val)
```

**bson.M**は**map**なので便利ですが、この**doc**の一部の値を変更して再ストアすると項目の順序が変わってしまうので注意が必要です。

### 構造体(struct)

```go
	var doc struct {
		Str1   string
		Num1   int
		Str2   string
		Num2   []int
		Subdoc struct {
			Str string
			Num int
		}
		Date time.Time
	}
	err = col.FindOne(context.Background(), filter, findOptions).Decode(&doc)
	//エラー処理 
	fmt.Printf("%+v\n", doc)
	fmt.Printf("num1: %d\n", doc.Num1)
```

前回（１）の投稿でも述べましたが、**num1**の項目が無いときは0になってしまいますので、区別するためには**num1**をポインタまたは**interface{}**として定義します。

```go
var doc struct {

    Num1 *int
    //あるいは
    Num1 interface{}
}
```
`doc.Num1 == nil`のときは**num1**の項目が無いか値が**null**です。 項目の型が限定できない場合も**interface{}**とすれば大丈夫です。

### bson.Raw

**bson.Raw**のままでは使い勝手が悪いので結局**bson.D,bson.M,構造体**に変換して使います。
次の例は**bson.Raw**を**bson.D**に変換した例です。

```go
	var doc bson.Raw
	err = col.FindOne(context.Background(), filter, findOptions).Decode(&doc)	
	//エラー処理 
	fmt.Println(doc.String())
	var docBsonD bson.D
	err = bson.Unmarshal(doc, &docBsonD)
	if err != nil {
		return err
	}
	fmt.Println(docBsonD)
```

**bson.Raw**の良いところは、後で**bson.Unmarshal**を使っていろいろなデータタイプに変換して使えることです。**String()**でJSONにもできます。
また、**bson.Raw**にはいろいろなメソッドが用意されていて、**bson.RawElement,bson.RawValue**のメソッドも使えます。
https://godoc.org/go.mongodb.org/mongo-driver/bson#Raw

例として**subdoc.num**の値を取り出してみます。

```go
	var doc bson.Raw
	err = col.FindOne(context.Background(), filter, findOptions).Decode(&doc)
	//エラー処理 
	fmt.Println(doc.String())
	val := doc.Lookup("subdoc", "num")
	numVal, ok := val.Int32OK()
	if ok {
		fmt.Printf("subdoc.num: %d\n", numVal)
	}
```

-------------------------------------
次回はFindの予定です