MongoDBでスキーマ検証を使用する方法
著者は、 Open Internet / Free Speech Fund を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
リレーショナルデータベースの重要な側面の1つは、データベースを行と列で構成されるテーブルに格納することです。これらは、既知のデータ型のフィールドを持つ固定された厳密なスキーマで動作します。 MongoDBのようなドキュメント指向データベースは、必要に応じてドキュメントの構造を再形成できるため、この点でより柔軟性があります。
ただし、特定の構造に従うか、特定の要件を満たすためにデータドキュメントが必要になる場合があります。 多くのドキュメントデータベースでは、ドキュメントのデータの一部をどのように構造化するかを指示するルールを定義できますが、必要に応じてこの構造を自由に変更できます。
MongoDBには、ドキュメントの構造に制約を適用できるスキーマ検証と呼ばれる機能があります。 スキーマ検証は、JSONドキュメント構造の記述と検証のオープンスタンダードである JSONSchemaを中心に構築されています。 このチュートリアルでは、検証ルールを記述して適用し、MongoDBコレクションの例でドキュメントの構造を制御します。
前提条件
このチュートリアルに従うには、次のものが必要です。
sudo権限を持つ通常の非rootユーザーと、UFWで構成されたファイアウォールを備えたサーバー。 このチュートリアルは、Ubuntu 20.04を実行しているサーバーを使用して検証されており、Ubuntu20.04のこの初期サーバーセットアップチュートリアルに従ってサーバーを準備できます。- サーバーにインストールされているMongoDB。 これを設定するには、 Ubuntu20.04にMongoDBをインストールする方法に関するチュートリアルに従ってください。
- 認証を有効にして管理ユーザーを作成することにより、サーバーのMongoDBインスタンスを保護します。 このようにMongoDBを保護するには、 Ubuntu20.04でMongoDBを保護する方法に関するチュートリアルに従ってください。
- MongoDBコレクションのクエリと結果のフィルタリングに精通していること。 MongoDBクエリの使用方法については、MongoDBでクエリを作成する方法に関するガイドに従ってください。
注:サーバーの構成方法、MongoDBのインストール方法、およびMongoDBのインストールを保護する方法に関するリンクされたチュートリアルは、Ubuntu20.04を参照しています。 このチュートリアルは、基盤となるオペレーティングシステムではなく、MongoDB自体に焦点を当てています。 通常、認証が有効になっている限り、オペレーティングシステムに関係なく、すべてのMongoDBインストールで機能します。
ステップ1—スキーマ検証を適用せずにドキュメントを挿入する
MongoDBのスキーマ検証機能とそれらが役立つ理由を強調するために、このステップでは、MongoDBシェルを開いて、ローカルにインストールされたMongoDBインスタンスに接続し、その中にサンプルコレクションを作成する方法の概要を説明します。 次に、このコレクションにいくつかのサンプルドキュメントを挿入することで、このステップでは、MongoDBがデフォルトでスキーマ検証を強制しない方法を示します。 後の手順で、このようなルールの作成と適用を自分で開始します。
このガイドで使用するサンプルコレクションを作成するには、管理ユーザーとしてMongoDBシェルに接続します。 このチュートリアルは、前提条件 MongoDBセキュリティチュートリアルの規則に従い、この管理ユーザーの名前が AdminSammy であり、その認証データベースがadminであることを前提としています。 異なる場合は、次のコマンドでこれらの詳細を変更して、独自の設定を反映させてください。
mongo -u AdminSammy -p --authenticationDatabase admin
インストール中に設定したパスワードを入力して、シェルにアクセスします。 パスワードを入力すると、>プロンプトサインが表示されます。
スキーマ検証機能を説明するために、このガイドの例では、世界で最も高い山を表すドキュメントを含むサンプルデータベースを使用しています。 エベレストのサンプルドキュメントは次の形式になります。
エベレスト文書
{
"name": "Everest",
"height": 8848,
"location": ["Nepal", "China"],
"ascents": {
"first": {
"year": 1953,
},
"first_winter": {
"year": 1980,
},
"total": 5656,
}
}
このドキュメントには、次の情報が含まれています。
name:ピークの名前。height:ピークの標高(メートル単位)。location:山が位置する国。 このフィールドには、複数の国にある山を許可するための配列として値が格納されます。ascents:このフィールドの値は別のドキュメントです。 あるドキュメントがこのように別のドキュメント内に保存されている場合、それは埋め込みまたはネストされたドキュメントと呼ばれます。 各ascentsドキュメントは、指定された山の成功した登山について説明しています。 具体的には、各ascentsドキュメントには、totalフィールドが含まれており、各ピークの成功した上昇の総数が一覧表示されます。 さらに、これらのネストされたドキュメントのそれぞれには、値がネストされたドキュメントでもある2つのフィールドが含まれています。 最初:このフィールドの値は、最初に全体的に成功した上昇の年を表す1つのフィールドyearを含むネストされたドキュメントです。 first_winter:このフィールドの値は、年フィールドも含むネストされたドキュメントであり、その値は、指定された山の最初の成功した冬の登山の年を表します。
次のinsertOne()メソッドを実行して、MongoDBインストールにpeaksという名前のコレクションを同時に作成し、エベレストを表す前のサンプルドキュメントを挿入します。
db.peaks.insertOne(
{
"name": "Everest",
"height": 8848,
"location": ["Nepal", "China"],
"ascents": {
"first": {
"year": 1953
},
"first_winter": {
"year": 1980
},
"total": 5656
}
}
)
出力には、成功メッセージと、新しく挿入されたオブジェクトに割り当てられたオブジェクト識別子が含まれます。
Output{
"acknowledged" : true,
"insertedId" : ObjectId("618ffa70bfa69c93a8980443")
}
提供されているinsertOne()メソッドを実行してこのドキュメントを挿入しましたが、このドキュメントの構造を完全に自由に設計できました。 場合によっては、データベース内のドキュメントの構造にある程度の柔軟性が必要になることがあります。 ただし、データの分析や処理を容易にするために、ドキュメントの構造の一部の側面に一貫性を持たせることもできます。
これが重要である理由を説明するために、このデータベースに入力される可能性のある他のいくつかのサンプルドキュメントを検討してください。
次のドキュメントは、エベレストを表す前のドキュメントとほぼ同じですが、nameフィールドが含まれていません。
名前のない山
{
"height": 8611,
"location": ["Pakistan", "China"],
"ascents": {
"first": {
"year": 1954
},
"first_winter": {
"year": 1921
},
"total": 306
}
}
世界で最も高い山のリストを含むデータベースの場合、山を表すがその名前を含まないドキュメントを追加すると、重大なエラーになる可能性があります。
この次のサンプルドキュメントでは、山の名前が表示されていますが、山の高さは数字ではなく文字列として表されています。 さらに、場所は配列ではなく単一の値であり、上昇の試行の総数に関する情報はありません。
高さの文字列値を持つ山
{
"name": "Manaslu",
"height": "8163m",
"location": "Nepal"
}
この例と同じくらい多くの省略があるドキュメントを解釈することは難しいかもしれません。 たとえば、height属性値がドキュメント間で異なるデータ型として保存されている場合、コレクションをピークの高さで正常に並べ替えることはできません。
次に、次のinsertMany()メソッドを実行して、これらのドキュメントをエラーを発生させずにデータベースに挿入できるかどうかをテストします。
db.peaks.insertMany([
{
"height": 8611,
"location": ["Pakistan", "China"],
"ascents": {
"first": {
"year": 1954
},
"first_winter": {
"year": 1921
},
"total": 306
}
},
{
"name": "Manaslu",
"height": "8163m",
"location": "Nepal"
}
])
結局のところ、MongoDBはエラーを返さず、両方のドキュメントが正常に挿入されます。
Output{
"acknowledged" : true,
"insertedIds" : [
ObjectId("618ffd0bbfa69c93a8980444"),
ObjectId("618ffd0bbfa69c93a8980445")
]
}
この出力が示すように、これらのドキュメントは両方とも有効なJSONであり、コレクションに挿入するのに十分です。 ただし、これだけではデータベースの論理的な一貫性と意味を維持するのに十分ではありません。 次の手順では、スキーマ検証ルールを作成して、peaksコレクションのデータドキュメントがいくつかの重要な要件に従っていることを確認します。
ステップ2—文字列フィールドの検証
MongoDBでは、スキーマ検証は、JSONスキーマドキュメントをコレクションに割り当てることで個々のコレクションに対して機能します。 JSON Schema は、JSONドキュメントの構造を定義および検証できるオープンスタンダードです。 これを行うには、特定のコレクション内のドキュメントが有効であると見なされるために従わなければならない一連の要件をリストするスキーマ定義を作成します。
特定のコレクションは単一のJSONスキーマのみを使用できますが、コレクションの作成時またはその後いつでもスキーマを割り当てることができます。 後で元の検証ルールを変更する場合は、元のJSONスキーマドキュメントを新しい要件に一致するものに置き換える必要があります。
前の手順で作成したpeaksコレクションにJSONスキーマバリデータードキュメントを割り当てるには、次のコマンドを実行します。
db.runCommand({
"collMod": "collection_name",
"validator": {
$jsonSchema: {JSON_Schema_document}
}
})
runCommandメソッドは、collModコマンドを実行します。このコマンドは、validator属性を適用して、指定されたコレクションを変更します。 validator属性はスキーマの検証を担当し、この構文例では、$jsonSchema演算子を受け入れます。 この演算子は、特定のコレクションのスキーマバリデーターとして使用されるJSONスキーマドキュメントを定義します。
警告:collModコマンドを実行するには、MongoDBユーザーに適切な権限が付与されている必要があります。 Ubuntu 20.04 でMongoDBを保護する方法の前提条件のチュートリアルに従い、そのガイドで作成した管理ユーザーとしてMongoDBインスタンスに接続している場合は、それに続く追加の役割を付与する必要があります。このガイドの例。
まず、ユーザーの認証データベースに切り替えます。 これは次の例ではadminですが、異なる場合は自分のユーザーの認証データベースに接続します。
use admin
Outputswitched to db admin
次に、grantRolesToUser()メソッドを実行し、peaksコレクションを作成したデータベースに対してdbAdminロールをユーザーに付与します。 次の例では、peaksコレクションがtestデータベースにあると想定しています。
db.grantRolesToUser(
"AdminSammy",
[ { role : "dbAdmin", db : "test" } ]
)
または、ユーザーにdbAdminAnyDatabaseロールを付与することもできます。 このロールの名前が示すように、MongoDBインスタンス上のすべてのデータベースに対するdbAdmin特権をユーザーに付与します。
db.grantRolesToUser( "AdminSammy", [ "dbAdminAnyDatabase" ] )
ユーザーに適切な役割を付与した後、peaksコレクションが保存されているデータベースに戻ります。
use test
Outputswitched to db test
コレクションを作成するときに、JSONスキーマバリデーターを割り当てることもできることに注意してください。 これを行うには、次の構文を使用できます。
db.createCollection(
"collection_name", {
"validator": {
$jsonSchema: {JSON_Schema_document}
}
})
前の例とは異なり、この構文にはcollModコマンドが含まれていません。これは、コレクションがまだ存在しておらず、変更できないためです。 ただし、前の例と同様に、collection_nameはバリデータードキュメントを割り当てるコレクションの名前であり、validatorオプションは指定されたJSONスキーマドキュメントをコレクションのバリデーターとして割り当てます。
このように最初からJSONスキーマバリデーターを適用するということは、コレクションに追加するすべてのドキュメントがバリデーターによって設定された要件を満たさなければならないことを意味します。 ただし、既存のコレクションに検証ルールを追加すると、それらを変更しようとするまで、新しいルールは既存のドキュメントに影響を与えません。
validator属性に渡すJSONスキーマドキュメントは、コレクションに適用するすべての検証ルールの概要を示している必要があります。 次のJSONスキーマの例では、nameフィールドがコレクション内のすべてのドキュメントに存在し、nameフィールドの値が常に文字列であることを確認します。
名前フィールドを検証する最初のJSONスキーマドキュメント
{
"bsonType": "object",
"description": "Document describing a mountain peak",
"required": ["name"],
"properties": {
"name": {
"bsonType": "string",
"description": "Name must be a string and is required"
}
},
}
このスキーマドキュメントは、コレクションに入力されたドキュメントの特定の部分が従わなければならない特定の要件の概要を示しています。 JSONスキーマドキュメントのルート部分(propertiesの前のフィールド(この場合はbsonType、description、およびrequired)は、データベースドキュメントを記述します)自体。
bsonTypeプロパティは、検証エンジンが検出することを期待するデータ型を記述します。 データベースドキュメント自体の場合、予想されるタイプはobjectです。 つまり、オブジェクト(つまり、中括弧({および})で囲まれた完全で有効なJSONドキュメント)のみをこのコレクションに追加できます。 他の種類のデータ型(スタンドアロンの文字列、整数、配列など)を挿入しようとすると、エラーが発生します。
MongoDBでは、すべてのドキュメントがオブジェクトです。 ただし、JSONスキーマは、あらゆる種類の有効なJSONドキュメントを記述および検証するために使用される標準であり、プレーン配列または文字列も有効なJSONです。 MongoDBスキーマ検証を使用する場合、JSONスキーマバリデーターでルートドキュメントのbsonType値を常にobjectとして設定する必要があることがわかります。
次に、descriptionプロパティは、このコレクションで見つかったドキュメントの簡単な説明を提供します。 このフィールドは必須ではありませんが、ドキュメントの検証に使用されるだけでなく、JSONスキーマを使用してドキュメントの構造に注釈を付けることもできます。 これは、他のユーザーがドキュメントの目的を理解するのに役立つため、descriptionフィールドを含めることをお勧めします。
検証ドキュメントの次のプロパティは、requiredフィールドです。 requiredフィールドは、コレクション内のすべてのドキュメントに存在する必要があるドキュメントフィールドのリストを含む配列のみを受け入れることができます。 この例では、["name"]は、ドキュメントにnameフィールドが含まれているだけで有効と見なされることを意味します。
続いて、ドキュメントフィールドの検証に使用されるルールを説明するpropertiesオブジェクトがあります。 ルールを定義するフィールドごとに、フィールドにちなんで名付けられた埋め込みJSONスキーマドキュメントを含めます。 required配列にリストされていないフィールドのスキーマルールを定義できることに注意してください。 これは、データに必須ではないフィールドが含まれているが、それらが存在する場合でも特定のルールに従うようにしたい場合に役立ちます。
これらの埋め込みスキーマドキュメントは、メインドキュメントと同様の構文に従います。 この例では、bsonTypeプロパティでは、すべてのドキュメントのnameフィールドがstringである必要があります。 この埋め込みドキュメントには、簡単なdescriptionフィールドも含まれています。
このJSONスキーマを前の手順で作成したpeaksコレクションに適用するには、次のrunCommand()メソッドを実行します。
db.runCommand({
"collMod": "peaks",
"validator": {
$jsonSchema: {
"bsonType": "object",
"description": "Document describing a mountain peak",
"required": ["name"],
"properties": {
"name": {
"bsonType": "string",
"description": "Name must be a string and is required"
}
},
}
}
})
MongoDBは、コレクションが正常に変更されたことを示す成功メッセージで応答します。
Output{ "ok" : 1 }
その後、MongoDBでは、nameフィールドがない場合、peaksコレクションにドキュメントを挿入できなくなります。 これをテストするには、nameフィールドがないことを除いて、山を完全に説明する前の手順で挿入したドキュメントを挿入してみてください。
db.peaks.insertOne(
{
"height": 8611,
"location": ["Pakistan", "China"],
"ascents": {
"first": {
"year": 1954
},
"first_winter": {
"year": 1921
},
"total": 306
}
}
)
今回の操作では、ドキュメントの検証に失敗したことを示すエラーメッセージが表示されます。
OutputWriteError({
"index" : 0,
"code" : 121,
"errmsg" : "Document failed validation",
. . .
})
MongoDBは、JSONスキーマで指定された検証ルールに合格しなかったドキュメントを挿入しません。
注: MongoDB 5.0以降、検証が失敗すると、エラーメッセージは失敗した制約を示します。 MongoDB 4.4以前では、データベースは失敗の理由に関する詳細を提供しません。
次のinsertOne()メソッドを実行して、MongoDBがJSONスキーマに含めたデータ型の要件を適用するかどうかをテストすることもできます。 これは前回の操作と似ていますが、今回はnameフィールドが含まれています。 ただし、このフィールドの値は文字列ではなく数値です。
db.peaks.insertOne(
{
"name": 123,
"height": 8611,
"location": ["Pakistan", "China"],
"ascents": {
"first": {
"year": 1954
},
"first_winter": {
"year": 1921
},
"total": 306
}
}
)
もう一度、検証は失敗します。 nameフィールドは存在しますが、文字列である必要があるという制約を満たしていません。
OutputWriteError({
"index" : 0,
"code" : 121,
"errmsg" : "Document failed validation",
. . .
})
もう一度試してください。ただし、ドキュメントにnameフィールドがあり、その後に文字列値が続きます。 今回は、nameがドキュメント内の唯一のフィールドです。
db.peaks.insertOne(
{
"name": "K2"
}
)
操作は成功し、ドキュメントは通常どおりオブジェクト識別子を受け取ります。
Output{
"acknowledged" : true,
"insertedId" : ObjectId("61900965bfa69c93a8980447")
}
スキーマ検証ルールは、nameフィールドにのみ関係します。 この時点で、nameフィールドが検証要件を満たしている限り、ドキュメントはエラーなしで挿入されます。 ドキュメントの残りの部分は、任意の形をとることができます。
これで、最初のJSONスキーマドキュメントを作成し、最初のスキーマ検証ルールをnameフィールドに適用し、それが存在し、文字列である必要があります。 ただし、データ型ごとに異なる検証オプションがあります。 次に、各ドキュメントのheightフィールドに格納されている数値を検証します。
ステップ3—数値フィールドの検証
次のドキュメントをpeaksコレクションに挿入したときの手順1を思い出してください。
高さの文字列値を持つ山
{
"name": "Manaslu",
"height": "8163m",
"location": "Nepal"
}
このドキュメントのheight値は数値ではなく文字列ですが、このドキュメントの挿入に使用したinsertMany()メソッドは成功しました。 これが可能だったのは、heightフィールドの検証ルールをまだ追加していないためです。
挿入されたドキュメントが有効なJSON構文で記述されている限り、MongoDBは、このフィールドの任意の値(負の値など、このフィールドに意味をなさない値も含む)を受け入れます。 これを回避するには、前の手順のスキーマ検証ドキュメントを拡張して、heightフィールドに関する追加のルールを含めることができます。
heightフィールドが常に新しく挿入されたドキュメントに存在し、常に数値として表現されていることを確認することから始めます。 次のコマンドを使用して、スキーマ検証を変更します。
db.runCommand({
"collMod": "peaks",
"validator": {
$jsonSchema: {
"bsonType": "object",
"description": "Document describing a mountain peak",
"required": ["name", "height"],
"properties": {
"name": {
"bsonType": "string",
"description": "Name must be a string and is required"
},
"height": {
"bsonType": "number",
"description": "Height must be a number and is required"
}
},
}
}
})
このコマンドのスキーマドキュメントでは、heightフィールドがrequired配列に含まれています。 同様に、propertiesオブジェクト内にheightドキュメントがあり、新しいheight値をnumberにする必要があります。 繰り返しになりますが、descriptionフィールドは補助的なものであり、含める説明は、他のユーザーがJSONスキーマの背後にある意図を理解できるようにするためだけのものにする必要があります。
MongoDBは、コレクションが正常に変更されたことを通知する短い成功メッセージで応答します。
Output{ "ok" : 1 }
これで、新しいルールをテストできます。 検証ドキュメントに合格するために必要な最小限のドキュメント構造でドキュメントを挿入してみてください。 次のメソッドは、nameとheightの2つの必須フィールドのみを含むドキュメントを挿入します。
db.peaks.insertOne(
{
"name": "Test peak",
"height": 8300
}
)
挿入は成功します:
Output{
acknowledged: true,
insertedId: ObjectId("61e0c8c376b24e08f998e371")
}
次に、heightフィールドが欠落しているドキュメントを挿入してみてください。
db.peaks.insertOne(
{
"name": "Test peak"
}
)
次に、heightフィールドを含む別のフィールドを試してください。ただし、このフィールドには文字列値が含まれています。
db.peaks.insertOne(
{
"name": "Test peak",
"height": "8300m"
}
)
どちらの場合も、操作によってエラーメッセージが表示され、失敗します。
OutputWriteError({
"index" : 0,
"code" : 121,
"errmsg" : "Document failed validation",
. . .
})
ただし、負の高さの山頂を挿入しようとすると、山は適切に保存されます。
db.peaks.insertOne(
{
"name": "Test peak",
"height": -100
}
)
これを防ぐために、スキーマ検証ドキュメントにさらにいくつかのプロパティを追加できます。 次の操作を実行して、現在のスキーマ検証設定を置き換えます。
db.runCommand({
"collMod": "peaks",
"validator": {
$jsonSchema: {
"bsonType": "object",
"description": "Document describing a mountain peak",
"required": ["name", "height"],
"properties": {
"name": {
"bsonType": "string",
"description": "Name must be a string and is required"
},
"height": {
"bsonType": "number",
"description": "Height must be a number between 100 and 10000 and is required",
"minimum": 100,
"maximum": 10000
}
},
}
}
})
新しいminimumおよびmaximum属性は、heightフィールドに含まれる値に制約を設定し、100未満または10000を超えないようにします。 このコレクションは山頂の高さに関する情報を格納するために使用されるため、この範囲は理にかなっていますが、これらの属性には任意の値を選択できます。
ここで、heightの値が負のピークを再度挿入しようとすると、操作は失敗します。
db.peaks.insertOne(
{
"name": "Test peak",
"height": -100
}
)
OutputWriteError({
"index" : 0,
"code" : 121,
"errmsg" : "Document failed validation",
. . .
この出力が示すように、ドキュメントスキーマは、各ドキュメントのnameフィールドに保持されている文字列値と、heightフィールドに保持されている数値を検証するようになりました。 各ドキュメントのlocationフィールドに格納されている配列値を検証する方法については、読み続けてください。
ステップ4—配列フィールドの検証
各ピークのnameおよびheight値がスキーマ検証制約によって検証されているので、データの一貫性を保証するためにlocationフィールドに注意を向けることができます。
山の場所を指定することは、ピークが複数の国にまたがるため、予想よりも難しいです。これは、有名な8000メートル峰の多くに当てはまります。 このため、各ピークのlocationデータを、単なる文字列値ではなく、1つ以上の国名を含む配列として格納することは理にかなっています。 height値と同様に、各locationフィールドのデータ型がすべてのドキュメントで一貫していることを確認すると、集計パイプラインを使用するときにデータを要約するのに役立ちます。
まず、ユーザーが入力する可能性のあるlocation値の例をいくつか検討し、どれが有効か無効かを検討します。
["Nepal", "China"]:これは2要素の配列であり、2か国にまたがる山の有効な値になります。["Nepal"]:この例は単一要素の配列であり、単一の国にある山の有効な値でもあります。"Nepal":この例はプレーンな文字列です。 単一の国がリストされていますが、locationフィールドには常に配列が含まれている必要があるため、無効になります。[]:空の配列。この例は有効な値ではありません。 結局のところ、すべての山は少なくとも1つの国に存在する必要があります。["Nepal", "Nepal"]:この2要素配列も、同じ値が2回出現するため、無効になります。["Nepal", 15]:最後に、この2要素配列は無効になります。これは、その値の1つが文字列ではなく数値であり、これが正しい場所の名前ではないためです。
MongoDBがこれらの各例を有効または無効として正しく解釈するようにするには、次の操作を実行して、peaksコレクションの新しい検証ルールを作成します。
db.runCommand({
"collMod": "peaks",
"validator": {
$jsonSchema: {
"bsonType": "object",
"description": "Document describing a mountain peak",
"required": ["name", "height", "location"],
"properties": {
"name": {
"bsonType": "string",
"description": "Name must be a string and is required"
},
"height": {
"bsonType": "number",
"description": "Height must be a number between 100 and 10000 and is required",
"minimum": 100,
"maximum": 10000
},
"location": {
"bsonType": "array",
"description": "Location must be an array of strings",
"minItems": 1,
"uniqueItems": true,
"items": {
"bsonType": "string"
}
}
},
}
}
})
この$jsonSchemaオブジェクトでは、locationフィールドがrequired配列とpropertiesオブジェクトに含まれています。 そこでは、bsonTypeのarrayで定義され、locationの値が単一の文字列や数値ではなく常に配列になるようになっています。
minItemsプロパティは、配列に少なくとも1つの要素が含まれている必要があることを検証し、uniqueItemsプロパティがtrueに設定されて、各location配列内の要素が確実に含まれるようにします。ユニークになります。 これにより、["Nepal", "Nepal"]などの値が受け入れられなくなります。 最後に、itemsサブドキュメントは、個々の配列アイテムの検証スキーマを定義します。 ここでの唯一の期待は、location配列内のすべての項目が文字列でなければならないということです。
注:使用可能なスキーマドキュメントのプロパティはbsonTypeごとに異なり、フィールドタイプに応じて、フィールド値のさまざまな側面を検証できます。 たとえば、number値を使用すると、許容値の範囲を作成するための最小および最大許容値を定義できます。 前の例では、locationフィールドのbsonTypeをarrayに設定することで、アレイに固有の機能を検証できます。
可能なすべての検証の選択肢の詳細については、JSONスキーマのドキュメントを参照してください。
コマンドを実行した後、MongoDBは、コレクションが新しいスキーマドキュメントで正常に変更されたことを示す短い成功メッセージで応答します。
Output{ "ok" : 1 }
ここで、前に準備した例に一致するドキュメントを挿入して、新しいルールがどのように動作するかをテストしてみてください。 ここでも、name、height、およびlocationフィールドのみが存在する、最小限のドキュメント構造を使用してみましょう。
db.peaks.insertOne(
{
"name": "Test peak",
"height": 8300,
"location": ["Nepal", "China"]
}
)
定義された検証の期待をすべて満たすため、ドキュメントは正常に挿入されます。 同様に、次のドキュメントはエラーなしで挿入されます。
db.peaks.insertOne(
{
"name": "Test peak",
"height": 8300,
"location": ["Nepal"]
}
)
ただし、次のinsertOne()メソッドのいずれかを実行すると、検証エラーがトリガーされて失敗します。
db.peaks.insertOne(
{
"name": "Test peak",
"height": 8300,
"location": "Nepal"
}
)
db.peaks.insertOne(
{
"name": "Test peak",
"height": 8300,
"location": []
}
)
db.peaks.insertOne(
{
"name": "Test peak",
"height": 8300,
"location": ["Nepal", "Nepal"]
}
)
db.peaks.insertOne(
{
"name": "Test peak",
"height": 8300,
"location": ["Nepal", 15]
}
)
以前に定義した検証ルールに従って、これらの操作で提供されるlocation値は無効と見なされます。
この手順を実行した後、山頂を表す3つの主要なフィールドは、MongoDBのスキーマ検証機能によってすでに検証されています。 次のステップでは、例としてascentsフィールドを使用して、ネストされたドキュメントを検証する方法を学習します。
ステップ5—埋め込みドキュメントの検証
この時点で、peaksコレクションにはname、height、locationの3つのフィールドがあり、スキーマ検証によってチェックされています。 このステップでは、ascentsフィールドの検証ルールの定義に焦点を当てます。このフィールドでは、各ピークをサミットするための成功した試行について説明します。
エベレストを表すステップ1のサンプルドキュメントでは、ascentsフィールドは次のように構成されています。
エベレスト文書
{
"name": "Everest",
"height": 8848,
"location": ["Nepal", "China"],
"ascents": {
"first": {
"year": 1953,
},
"first_winter": {
"year": 1980,
},
"total": 5656,
}
}
ascentsサブドキュメントには、totalフィールドが含まれています。このフィールドの値は、特定の山での登山の合計試行回数を表します。 また、山の最初の冬の登りと全体的な最初の登りに関する情報も含まれています。 ただし、これらは山の説明に不可欠ではない場合があります。 結局のところ、一部の山はまだ冬に登っていないか、登山日が争われているか不明である可能性があります。 今のところ、各ドキュメントに常に必要な情報は、上昇の試行の総数であると想定してください。
ascentsフィールドが常に存在し、その値が常にサブドキュメントである必要があるように、スキーマ検証ドキュメントを変更できます。 このサブドキュメントには、常にゼロ以上の数値を保持するtotal属性が含まれている必要があります。 firstおよびfirst_winterフィールドはこのガイドの目的には必要ないため、検証フォームはそれらを考慮せず、柔軟なフォームをとることができます。
もう一度、次のrunCommand()メソッドを実行して、peaksコレクションのスキーマ検証ドキュメントを置き換えます。
db.runCommand({
"collMod": "peaks",
"validator": {
$jsonSchema: {
"bsonType": "object",
"description": "Document describing a mountain peak",
"required": ["name", "height", "location", "ascents"],
"properties": {
"name": {
"bsonType": "string",
"description": "Name must be a string and is required"
},
"height": {
"bsonType": "number",
"description": "Height must be a number between 100 and 10000 and is required",
"minimum": 100,
"maximum": 10000
},
"location": {
"bsonType": "array",
"description": "Location must be an array of strings",
"minItems": 1,
"uniqueItems": true,
"items": {
"bsonType": "string"
}
},
"ascents": {
"bsonType": "object",
"description": "Ascent attempts information",
"required": ["total"],
"properties": {
"total": {
"bsonType": "number",
"description": "Total number of ascents must be 0 or higher",
"minimum": 0
}
}
}
},
}
}
})
ドキュメントのいずれかのフィールドにサブドキュメントが含まれている場合、そのフィールドのJSONスキーマは、メインドキュメントスキーマとまったく同じ構文に従います。 同じドキュメントを相互にネストする方法と同様に、検証スキーマもドキュメントを相互にネストします。 これにより、階層構造に複数のサブドキュメントを含むドキュメント構造の複雑な検証スキーマを簡単に定義できます。
このJSONスキーマドキュメントでは、ascentsフィールドがrequired配列内に含まれているため、必須です。 また、ルートドキュメント自体と同様に、objectのbsonTypeで定義されているpropertiesオブジェクトにも表示されます。
ascents検証の定義は、ルートドキュメントと同様の原則に従っていることに注意してください。 requiredフィールドがあり、サブドキュメントに含める必要のあるプロパティを示します。 また、同じ構造に従って、propertiesリストを定義します。 ascentsフィールドはサブドキュメントであるため、その値は、より大きなドキュメントの値と同じように検証されます。
ascents内にはrequired配列があり、その値はtotalのみです。つまり、すべてのascentsサブドキュメントにはtotalが含まれている必要があります。分野。 続いて、total値は、propertiesオブジェクト内で完全に記述されます。これは、これが常にnumberであり、minimum値がゼロでなければならないことを指定します。
繰り返しになりますが、firstフィールドもfirst_winterフィールドもこのガイドの目的では必須ではないため、これらの検証ルールには含まれていません。
このスキーマ検証ドキュメントを適用した状態で、最初のステップからサンプルのエベレストマウントドキュメントを挿入して、有効として確立済みのドキュメントを挿入できることを確認してください。
db.peaks.insertOne(
{
"name": "Everest",
"height": 8848,
"location": ["Nepal", "China"],
"ascents": {
"first": {
"year": 1953,
},
"first_winter": {
"year": 1980,
},
"total": 5656,
}
}
)
ドキュメントは正常に保存され、MongoDBは新しいオブジェクト識別子を返します。
Output{
"acknowledged" : true,
"insertedId" : ObjectId("619100f51292cb2faee531f8")
}
最後の検証が正しく機能することを確認するには、ascentsフィールドを含まないドキュメントを挿入してみてください。
db.peaks.insertOne(
{
"name": "Everest",
"height": 8848,
"location": ["Nepal", "China"]
}
)
今回の操作では、ドキュメントの検証に失敗したことを示すエラーメッセージが表示されます。
OutputWriteError({
"index" : 0,
"code" : 121,
"errmsg" : "Document failed validation",
. . .
})
次に、ascentsサブドキュメントにtotalフィールドがないドキュメントを挿入してみてください。
db.peaks.insertOne(
{
"name": "Everest",
"height": 8848,
"location": ["Nepal", "China"],
"ascents": {
"first": {
"year": 1953,
},
"first_winter": {
"year": 1980,
}
}
}
)
これにより、再びエラーが発生します。
最後のテストとして、ascentsフィールドにtotal値が含まれているドキュメントを入力してみてください。ただし、この値は負です。
db.peaks.insertOne(
{
"name": "Everest",
"height": 8848,
"location": ["Nepal", "China"],
"ascents": {
"first": {
"year": 1953,
},
"first_winter": {
"year": 1980,
},
"total": -100
}
}
)
totalの値が負であるため、このドキュメントも検証テストに失敗します。
結論
このチュートリアルに従うことで、JSONスキーマドキュメントと、それらをコレクションに保存する前にドキュメント構造を検証するためにそれらを使用する方法に精通しました。 次に、JSONスキーマドキュメントを使用してフィールドタイプを確認し、数値と配列に値の制約を適用しました。 また、ネストされたドキュメント構造でサブドキュメントを検証する方法も学習しました。
MongoDBのスキーマ検証機能は、アプリケーションレベルでのデータ検証の代わりと見なすべきではありませんが、データを意味のあるものに保つために不可欠なデータ制約に違反することをさらに防ぐことができます。 スキーマ検証の使用は、データストレージへのスキーマレスアプローチの柔軟性を維持しながら、データを構造化するための便利なツールになります。 スキーマ検証を使用すると、検証するドキュメント構造の部分と、制限のないままにしておきたい部分を完全に制御できます。
チュートリアルでは、MongoDBのスキーマ検証機能のサブセットのみを説明しました。 さまざまなMongoDBデータ型にさらに制約を適用できます。また、検証動作の厳密さを変更し、JSONスキーマを使用して既存のドキュメントをフィルタリングおよび検証することもできます。 公式の公式のMongoDBドキュメントを調べて、スキーマの検証と、データベースに保存されているデータの操作にどのように役立つかについて学ぶことをお勧めします。