MongoDBにて.（ピリオド）をクエリに使う時の注意点

# はじめに
MongoDBにおける.（ピリオド）は特殊な記号として扱われており、使用する際は注意が必要になっています。
自分も使っていく中で微妙に引っかかった点があったので、簡易的ですがまとめておきます。

# オペレーションによる解釈の違い
まず、FINDやUPDATE系のクエリでは、基本的にピリオドは**オブジェクト内部のフィールドを参照するもの**として扱われます。
```
# usersコレクション内のドキュメント(_id省略)
[
  { name: 'John', favorite_colors: { red: true, blue: false, green: true } },
  { name: 'Anna', favorite_colors: { red: true, blue: true, green: true } },
  { name: 'Nancy', favorite_colors: { red: true, blue: false, green: false } },
]


# オブジェクトのフィールドをfind
> db.users.find({ 'favorite_colors.blue': true }).toArray()
< [
    { name: 'Anna', favorite_colors: { red: true, blue: true, green: true } },
  ]


# オブジェクトのフィールドをupdate
> db.users.updateMany({ 'favorite_colors.blue': false }, { $set: { 'favorite_colors.skyblue': false } })

# UPDATE後のusersコレクション内のドキュメント(_id省略)
[
  { name: 'John', favorite_colors: { red: true, blue: false, green: true, skyblue: false } },
  { name: 'Anna', favorite_colors: { red: true, blue: true, green: true } },
  { name: 'Nancy', favorite_colors: { red: true, blue: false, green: false, skyblue: false } },
]
```

上の例だと、ピリオド（.）を使用することで`favorite_colors`オブジェクトの中のフィールドがいずれも参照されています。
オブジェクト内のフィールドに簡単にアクセスすることが出来るので、便利ですね。

では、INSERT系のクエリだとどうなるでしょうか。

```
> db.users.insertOne({ name: 'Mark', 'favorite_colors.red': true })

# INSERTされたドキュメント(_id省略)
[
  { name: 'Mark', 'favorite_colors.red': true }
]
```

`favorite_colors.red`という、ピリオド（.）が入ったフィールドが出来ています。
記述そのままと言えばそうなのですが、UPDATEやFIND系のクエリとは異なる挙動になっているので注意しなければいけません。
特に<INS>UPDATEとINSERTでクエリを流用する</INS>とかやっちゃうと事故の元になります。

:::note
もしオブジェクトのフィールドを作りたい場合は、ピリオドを使わずそのまま書きましょう。
例）`db.users.insertOne({ name: 'Mark', favorite_colors: { red: true } })`
:::

:::note warn
これはVer5.0以降の挙動になっています。
それより前ではそもそもピリオド入りのフィールドが許可されておらず、エラーとなります。
:::

## UPSERTでの挙動
「えっ、じゃあ`upsert`を設定してたらどうなるの？」と思った方もいるかもしれません。
この場合は、結果的にinsertされる事になっても**UPDATE系のクエリと同様にピリオドはオブジェクト内のフィールドを参照する**ようになります。

```
> db.users.updateOne({ name: 'Mark' }, { $set: { 'favorite_colors.red': true } }, { upsert: true })

# INSERTされたドキュメント(_id省略)
[
  { name: 'Mark', favorite_colors: { red: true } }
]
```

上記のような感じですね。
まあupdateとinsertでもし挙動が変わったら困るので、当たり前といえばその通りかと思います。

# ピリオド（.）入りのフィールドを扱うには
...じゃあピリオド入りのフィールドってどうやってUPDATEやFINDをするんだ？オブジェクト内のフィールドを参照してしまうのでは？
と思った方もいると思います。自分もそうです。

ただ、これもちゃんと方法が用意してあります。
それが`$getField`と`$setField`です。
使い方はこんな感じ。

```
# usersコレクション内のドキュメント(_id省略)
[
  { name: 'John', favorite_colors: { red: true, blue: false, green: true }, 'favorite_colors.yellow': true },
  { name: 'Anna', favorite_colors: { red: true, blue: true, green: true }, 'favorite_colors.yellow': false },
  { name: 'Nancy', favorite_colors: { red: true, blue: false, green: false }, 'favorite_colors.yellow': true },
]


# ピリオド入りのフィールドをfind
> db.users.find({
    $expr: {
      $eq: [{ $getField: { field: 'favorite_colors.yellow', input: '$$CURRENT' } }, true]
    }
  })
  .toArray()
< [
    { name: 'John', favorite_colors: { red: true, blue: false, green: true }, 'favorite_colors.yellow': true },
    { name: 'Nancy', favorite_colors: { red: true, blue: false, green: false }, 'favorite_colors.yellow': true },
  ]


# ピリオド入りのフィールドをupdate
> db.users.updateOne(
    { name: 'John' },
    [
      {
        $replaceWith: {
          $setField: { field: 'favorite_colors.yellow', input: '$$ROOT', value: false }
        }
      }
    ]
  )

# UPDATE後のusersコレクション内のドキュメント(_id省略)
[
  { name: 'John', favorite_colors: { red: true, blue: false, green: true }, 'favorite_colors.yellow': false },
  { name: 'Anna', favorite_colors: { red: true, blue: true, green: true }, 'favorite_colors.yellow': false },
  { name: 'Nancy', favorite_colors: { red: true, blue: false, green: false }, 'favorite_colors.yellow': true },
]
```

~~めっちゃ面倒くさいけど~~`$getField`と`$setField`を通すことで、ちゃんとピリオド入りのフィールドを参照することが出来ます。
細かい使い方はあるのですが、ここで解説するよりも公式ドキュメントを参照したほうが早いので見てみてください。（参考リンクは下記）

# 結論
明らかに面倒なので<INS>**ピリオド入りのフィールドは使わない**</INS>ようにしましょう。
そして<INS>**うっかりINSERTしない**</INS>ように注意しましょう。
ピリオド入りフィールドにしないとシステムがぶっ壊れるとか無ければね！

# 参考
- [Field Names with Periods (.) and Dollar Signs ($) - MongoDB Manual v5.0](https://www.mongodb.com/docs/v5.0/core/dot-dollar-considerations/#std-label-crud-concepts-dot-dollar-considerations)
- [$getField (aggregation) - MongoDB Manual v7.0](https://www.mongodb.com/docs/manual/reference/operator/aggregation/getField/)
- [$setField (aggregation) - MongoDB Manual v7.0](https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/)