feature image

2023年9月6日 | ブログ記事

GoのMongoDBドライバー と UUID と omitempty と

この記事は夏のブログリレー17日目の記事です。

22Bのいくら・はむです。GoでMongoDBのドライバー(mongo-driver)でUUIDを使うときのことを書きます。

MongoDBとは

NoSQLのデータベースの一種です。「BSON」と呼ばれるJSONのような形式の「ドキュメント」を保持します。RDBでいうテーブルは「コレクション」、レコードは「ドキュメント」と呼ばれます。カラムにあたるものは「フィールド」ですが、RDBとは異なり制約がゆるく、自由度が高いのが特徴です。

UUIDとは

Universally Unique Identifier の略です。128ビットの数値で、16進数に直して32個の数字とアルファベットで表されることが多いです。数が多く衝突が起きにくいので、かぶると困るIDなどをアプリケーションで生成するのによく使われます。traPの部内SNSであるtraQでも、全てのユーザーやチャンネル、メッセージにこのUUIDが割り振られています。

https://ja.wikipedia.org/wiki/UUID

Goには様々なUUIDを扱うためのライブラリがあります。例えばgithub.com/google/uuidがあります。このようなライブラリでは、UUID型を下のように定義しています。

type UUID [16]byte

GoのMongoDBドライバー

構造体とomitempty

GoのMongoDBドライバー(mongo-driver)では、データベースからの検索や挿入に構造体を使えます。

type User struct {
	Id   int    `bson:"id,omitempty"`
	Name string `bson:"name,omitempty"`
}

// idが100で名前がikura-hamuのユーザーを追加
_, err := br.db.Collection("users").InsertOne(ctx, User{
    Id:   100,
    Name: "ikura-hamu",
})

var user User
// idが200のユーザーを1人取得
err = br.db.Collection("users").FindOne(ctx, User{
    Id:   200,
}).Decode(&user)

構造体の定義に書かれたomitemptyは、MongoDBでBSONとして扱うとき、値がゼロ値であればフィールドが省略されるという意味です。ユーザー取得の方ではIdしか指定してないので、Nameのフィールドは無視され、BSONは下のようになります。

{
    "id":200
}

構造体をmongo-driverでBSONにしたときの結果は、bson.Marshal()を使って調べることができ、bson.Raw()でいい感じに出力できます。

UUIDは?

UUID型をBSONにするとこうなります。

type User struct {
	Id   uuid.UUID `bson:"id,omitempty"`
	Name string    `bson:"name,omitempty"`
}

u := User{
    Id:   uuid.New(),
	Name: "ikura-hamu",
}

b, _ := bson.Marshal(u)
fmt.Println(bson.Raw(b))
{
    "id": {
        "$binary":{
            "base64":"Wn5T1LzrSziAVlaR2Ix1MA==",
            "subType":"00"
            }
        },
    "name": "ikura-hamu"
}

$binaryというものが出てきました。これはUUIDが[16]byteで表されているため、バイナリとして認識されているのです。

https://www.mongodb.com/docs/v7.0/reference/bson-types/#binary-data

ではIdを指定せずにゼロ値にするとどうなるでしょうか。

type User struct {
	Id   uuid.UUID `bson:"id,omitempty"`
	Name string    `bson:"name,omitempty"`
}

u := User{
	Name: "ikura-hamu",
}

b, _ := bson.Marshal(u)
fmt.Println(bson.Raw(b))
{
    "id": {
        "$binary":{
            "base64":"AAAAAAAAAAAAAAAAAAAAAA==",
            "subType":"00"
         }
     },
     "name": "ikura-hamu"
}

"AAAAAAAAAAAAAAAAAAAAAA=="という謎の値になってしまいました。

これはUUIDのゼロ値が00000000-0000-0000-0000-000000000000だからです。これがomitemptyではゼロ値と判断されずBSONのフィールドとして残ってしまいます。

Go Playground で試せます。
https://go.dev/play/p/6vcynRL1npM

これで検索をすると?

UUIDが含まれる構造体で、UUIDに値を入れずにMongoDBの検索をしようとするとうまくいきません。

"$binary":{
            "base64":"AAAAAAAAAAAAAAAAAAAAAA==",
            "subType":"00"
         }

これに該当する値を検索しようとするからです。

これを避けるために、検索用の構造体を新たに定義するか、bson.Mなどを使って構造体以外を使いましょう。

2024/4/17 追記
ポインタにすればいいのか

おわり

最近初めてMongoDBを触ってこのomitemptyの罠にかなり時間を取られました。大変。これの他に、boolfalseがゼロ値なのを忘れてomitemptyをつけて、こちらは消えてほしくないフィールドが意図せず消えてしまいました。気を付けたいです。

MongoDBはちゃんと使い分けをしてうまく使いこなせばRDBより使いやすくなりそうなので、いろいろ試してみたいです。

明日は @ramdosくんの記事です。

ikura-hamu icon
この記事を書いた人
ikura-hamu

SysAd班、ゲーム班 いろいろやりたい

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

2021年8月12日
CPCTFを支えたWebshell
mazrean icon mazrean
2021年5月19日
CPCTF2021を実現させたスコアサーバー
xxpoxx icon xxpoxx
2022年9月26日
競プロしかシラン人間が web アプリ QK Judge を作った話
tqk icon tqk
2022年9月16日
5日でゲームを作った #tararira
Komichi icon Komichi
2024年3月15日
個人開発として2週間でWebサービスを作ってみた話 〜「LABEL」の紹介〜
Natsuki icon Natsuki
2023年10月20日
DIGI-CON HACKATHON 参加記事「Comic DoQ」
mehm8128 icon mehm8128
記事一覧 タグ一覧 Google アナリティクスについて 特定商取引法に基づく表記