前回投稿した「MongoDBの Offical Go言語 Driverを使ってみる(1)準備・Insert」の続きでFindOne編です。
FindOne
FindOneは検索条件にマッチした最初の1件目のドキュメントを取得します
FindOne書式
FindOne(context.Context, 検索条件, FindOne オプション)
検索条件なし、オプションなしでの実行可能なソース
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
- 検索条件にマッチしたドキュメントが1件もないときにこのエラーを返します。
(注意) **doc.String()**でBSONをJSON化して表示していますが、リリースされたv1.0.0ではバグがありObjectIdの表示でコードが""で囲まれていません。そのため、このJSONをBSON化しようとするとエラーになります。最新では修正されていますが、v1.0.0を使っている方は注意してください。修正箇所は以下のURLの通りです。
https://github.com/mongodb/mongo-go-driver/commit/c72645a64800adf5455dd9ad9cb811c0bf7d34c1
検索条件の設定の仕方
紹介するのは次の3つタイプです。
- bson.D
- 構造体(struct)
- JSON文字列
前回(1)で投稿したときに使ったデータと同じものを想定しています。
{"str1": "abc", "num1":1, "str2": "xyz", "num2": [2,3,4], "subdoc": {"str": "subdoc", "num": 987},"date": 現在の日付}
bson.Dを使った検索条件例
//{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)
構造体を使った検索条件例
//{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文字列を使った検索条件例
//{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の例だけ示します。
findOptions := options.FindOne()
findOptions.SetProjection(bson.D{{"_id", 0},{"str1",1},{"num1",1}})
検索したドキュメントを受け取るデータタイプ
- bson.D
- bson.M
- 構造体(struct)
- bson.Raw
bson.D
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
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)
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)
前回(1)の投稿でも述べましたが、num1の項目が無いときは0になってしまいますので、区別するためにはnum1をポインタまたは**interface{}**として定義します。
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に変換した例です。
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の値を取り出してみます。
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の予定です