MongoDB 3.6 でコレクションに制約を付ける

#MongoDBの制約

MongoDBのコレクションに必須項目などの制約を設けたい時があります。制約の付け方については[公式ドキュメント](https://docs.mongodb.com/manual/core/schema-validation/)に例があるんですけど、色々とカバーしてある分、初見ではなかなか分かりづらいんですよね。

ここでは最小構成として、例えばオラクルで言うところの「NOT NULL」制約を設けたいと思います。MongoDBの言葉では「required」制約ですね。

#コレクション作成時に設定する場合

DBバージョン3.6からは[json Schema](https://ja.wikipedia.org/wiki/JSON_Schema)を使用できます。使うのは$jsonSchemaという演算子ですね。

ここではtestというデータベースを使います。createCollectionメソッドを使って明示的にコレクション作成しますが、その際にvalidatorにて制約を設定してみます。

```javascript:
> use test
switched to db test

> db.createCollection("project_d", {validator: {$jsonSchema: {required: ["name"]}}})
{ "ok" : 1 }
```

上記の例ではnameを必須項目としています。設定の確認はgetCollectionInfosメソッドで行えます。jsonのツリーが深くて見にくいのですが（笑）、nameがrequiredに含まれているっぽい事が分かります。

```javascript:
> db.getCollectionInfos({name: "project_d"})
[
        {
                "name" : "project_d",
                "type" : "collection",
                "options" : {
                        "validator" : {
                                "$jsonSchema" : {
                                        "required" : [
                                                "name"
                                        ]
                                }
                        }
                },
                "info" : {
                        "readOnly" : false,
                        "uuid" : UUID("7fa2aca9-42f7-4660-b276-bce2e75f0f3a")
                },
                "idIndex" : {
                        "v" : 2,
                        "key" : {
                                "_id" : 1
                        },
                        "name" : "_id_",
                        "ns" : "test.project_d"
                }
        }
]
```

次は動作確認です。nameを含まないドキュメントを挿入してみます。

```javascript:
> db.project_d.insertOne({_id: 0})
2018-01-02T18:18:16.287-0800 E QUERY    [thread1] WriteError: Document failed validation :
WriteError({
        "index" : 0,
        "code" : 121,
        "errmsg" : "Document failed validation",
        "op" : {
                "_id" : 0
        }
})
...
```

うまく弾かれてくれました。必須項目を含める事でちゃんと挿入できます。

```javascript:
> db.project_d.insertOne({_id: 0, name: "takumi", role: "downhill"})
{ "acknowledged" : true, "insertedId" : 0 }

> db.project_d.find()
{ "_id" : 0, "name" : "takumi", "role" : "downhill" }
```


#既存コレクションに追加する場合

後から制約を設ける場合には、runCommandメソッドを使います。チームにおける役割をはっきりさせるのは重要ですので、上の例の「role」を必須項目にしてみましょう。

```javascript:
> db.runCommand({collMod: "project_d", validator: {$jsonSchema: {required: ["name", "role"]}}})
{ "ok" : 1 }

> db.getCollectionInfos({name: "project_d"})
[
        {
                "name" : "project_d",
                "type" : "collection",
                "options" : {
                        "validator" : {
                                "$jsonSchema" : {
                                        "required" : [
                                                "name",
                                                "role"
                                        ]
                                }
                        },
                        "validationLevel" : "strict",
                        "validationAction" : "error"
                },
                "info" : {
                        "readOnly" : false,
                        "uuid" : UUID("7fa2aca9-42f7-4660-b276-bce2e75f0f3a")
                },
                "idIndex" : {
                        "v" : 2,
                        "key" : {
                                "_id" : 1
                        },
                        "name" : "_id_",
                        "ns" : "test.project_d"
                }
        }
]
```

設定できたようです。そしたら新しいデータを入れてみます。

```javascript
> db.project_d.insertOne({_id: 1, name: "keisuke"})
2018-01-02T18:29:47.788-0800 E QUERY    [thread1] WriteError: Document failed validation :
WriteError({
        "index" : 0,
        "code" : 121,
        "errmsg" : "Document failed validation",
        "op" : {
                "_id" : 1,
                "name" : "keisuke"
        }
})
...

> db.project_d.insertOne({_id: 1, name: "keisuke", role: "hillclimb"})
{ "acknowledged" : true, "insertedId" : 1 }
```

roleを省いた方はちゃんと弾かれておりますね。

今回は最小構成で動かす事を意識してrequired制約だけを実装してみましたが、json Schemaの中では、データ型の指定など、種々のvalidationもかける事ができます。必要に応じてincrementalに実験して行きたい所存です。