Skip to content

Commit cb33732

Browse files
DOCSP-49411-schema-validation-example (#12166) (#12371)
* DOCSP-49411-schema-validation-example * fixes * reviewer feedback * edits * add to toc * update snooty * feedback * add testing and validation * rebasing * small fixes * add highlight * remove edits to snooty file * feedback
1 parent 6a746d6 commit cb33732

File tree

6 files changed

+924
-0
lines changed

6 files changed

+924
-0
lines changed

content/manual/manual/source/core/schema-validation.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ Get Started
9595
For common tasks involving schema validation, see the following pages:
9696

9797
- :ref:`schema-validation-json`
98+
- :ref:`schema-validation-polymorphic-collections`
9899
- :ref:`schema-validation-query-expression`
99100
- :ref:`schema-allowed-field-values`
100101
- :ref:`schema-view-validation-rules`
@@ -112,6 +113,7 @@ To learn about MongoDB's flexible schema model, see
112113
:titlesonly:
113114

114115
Specify JSON Validation </core/schema-validation/specify-json-schema>
116+
Specify Validation for Polymorphic Collections </core/schema-validation/specify-validation-polymorphic-collections>
115117
Specify Query Operators </core/schema-validation/specify-query-expression-rules>
116118
Specify Validation Level </core/schema-validation/specify-validation-level>
117119
Handle Invalid Documents </core/schema-validation/handle-invalid-documents>
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
.. _schema-validation-polymorphic-collections:
2+
3+
==============================================
4+
Specify Validation for Polymorphic Collections
5+
==============================================
6+
7+
.. facet::
8+
:name: genre
9+
:values: tutorial
10+
11+
.. meta::
12+
:description: Specify schema validation on a polymorphic collection, or a collection with multiple schemas.
13+
14+
.. contents:: On this page
15+
:local:
16+
:backlinks: none
17+
:depth: 2
18+
:class: singlecol
19+
20+
You can specify schema validation for a collection that stores :ref:`polymorphic
21+
data<polymorphic-data>`, or documents with varying structures or schemas.
22+
23+
To create schema validation for multiple schemas within a single collection,
24+
you can set the schemas in your validation rules and ensure that
25+
documents conform to one of your collection's schemas.
26+
27+
About this Task
28+
---------------
29+
30+
Consider a collection, ``accounts``, that stores data on customers of a
31+
bank and their account details. The collection contains both ``customer``
32+
documents and ``account`` documents.
33+
34+
The following code inserts two ``customer`` documents into the ``accounts``
35+
collection to store the details of customers Andrew and Anne, respectively.
36+
It also inserts two ``account`` documents to represent each of their individual savings
37+
accounts and a third ``account`` document to represent their shared checking account.
38+
You can run the code for this tutorial in the :mongosh:`MongoDB Shell (mongosh) </>`.
39+
40+
.. code-block:: javascript
41+
42+
db.accounts.insertMany( [
43+
{
44+
"customerId": "CUST-123456789",
45+
"docType": "customer",
46+
"name": {
47+
"title": "Mr",
48+
"first": "Andrew",
49+
"middle": "James",
50+
"last": "Morgan"
51+
},
52+
"address": {
53+
"street1": "240 Blackfriars Rd",
54+
"city": "London",
55+
"postCode": "SE1 8NW",
56+
"country": "UK"
57+
},
58+
"customerSince": ISODate("2005-05-20")
59+
},
60+
{
61+
"customerId": "CUST-987654321",
62+
"docType": "customer",
63+
"name": {
64+
"title": "Mrs",
65+
"first": "Anne",
66+
"last": "Morgan"
67+
},
68+
"address": {
69+
"street1": "240 Blackfriars Rd",
70+
"city": "London",
71+
"postCode": "SE1 8NW",
72+
"country": "UK"
73+
},
74+
"customerSince": ISODate("2003-12-01")
75+
},
76+
{
77+
"accountNumber": "ACC1000000654",
78+
"docType": "account",
79+
"accountType": "checking",
80+
"customerId": [
81+
"CUST-123456789",
82+
"CUST-987654321"
83+
],
84+
"dateOpened": ISODate("2003-12-01"),
85+
"balance": NumberDecimal("5067.65")
86+
},
87+
{
88+
"accountNumber": "ACC1000000432",
89+
"docType": "account",
90+
"accountType": "savings",
91+
"customerId": [
92+
"CUST-123456789"
93+
],
94+
"dateOpened": ISODate("2005-10-28"),
95+
"balance": NumberDecimal("10341.21")
96+
},
97+
{
98+
"accountNumber": "ACC1000000890",
99+
"docType": "account",
100+
"accountType": "savings",
101+
"customerId": [
102+
"CUST-987654321"
103+
],
104+
"dateOpened": ISODate("2003-12-15"),
105+
"balance": NumberDecimal("10341.89")
106+
}
107+
] );
108+
109+
To only allow documents that adhere to the ``customer`` or ``account`` schemas into the ``accounts``
110+
collection, set up schema validation using the following procedure.
111+
112+
Steps
113+
-----
114+
115+
.. procedure::
116+
:style: normal
117+
118+
.. step:: Create a JSON schema definition for each type of document
119+
120+
To distinguish between different types of documents, you can use multiple JSON schemas.
121+
To define what attributes need to be in a document and what data types they accept,
122+
create two schemas: one for a ``customer`` document, and one
123+
for an ``account`` document. Each schema includes a ``docType``
124+
attribute to identify which type of entity it represents.
125+
126+
.. code-block:: javascript
127+
128+
const customerSchema = {
129+
required: ["docType", "customerId", "name", "customerSince"],
130+
properties: {
131+
docType: { enum: ["customer"] },
132+
customerId: { bsonType: "string"},
133+
name: {
134+
bsonType: "object",
135+
required: ["first", "last"],
136+
properties: {
137+
title: { enum: ["Mr", "Mrs", "Ms", "Dr"]},
138+
first: { bsonType: "string" },
139+
middle: { bsonType: "string" },
140+
last: { bsonType: "string" }
141+
}
142+
},
143+
address: {
144+
bsonType: "object",
145+
required: ["street1", "city", "postCode", "country"],
146+
properties: {
147+
street1: { bsonType: "string" },
148+
street2: { bsonType: "string" },
149+
postCode: { bsonType: "string" },
150+
country: { bsonType: "string" }
151+
}
152+
},
153+
customerSince: {
154+
bsonType: "date"
155+
}
156+
}
157+
};
158+
159+
const accountSchema = {
160+
required: ["docType", "accountNumber", "accountType", "customerId", "dateOpened", "balance"],
161+
properties: {
162+
docType: { enum: ["account"] },
163+
accountNumber: { bsonType: "string" },
164+
accountType: { enum: ["checking", "savings", "mortgage", "loan"] },
165+
customerId: { bsonType: "array" },
166+
dateOpened: { bsonType: "date" },
167+
balance: { bsonType: "decimal" }
168+
}
169+
};
170+
171+
.. step:: Configure the collection to only accept the appropriate documents
172+
173+
To allow documents that match either the ``customerSchema`` or the
174+
``accountSchema``, use the ``oneOf`` JSON schema operator. Then,
175+
use the :dbcommand:`collMod` command to update the ``accounts``
176+
collection to use to your schema validation.
177+
178+
.. code-block:: javascript
179+
180+
db.runCommand({
181+
collMod: "accounts",
182+
validator: { $jsonSchema: { oneOf: [ customerSchema, accountSchema ] } }
183+
})
184+
185+
.. step:: Add extra semantic validations
186+
187+
You can optionally add extra semantic validations. For example, you can add
188+
the following constraints to your collection’s documents:
189+
190+
- For ``customer`` documents, the ``customerSince`` value can't be any earlier than the current time.
191+
- For ``account`` documents, the ``dateOpened`` value can't be any earlier than the current time.
192+
- For savings accounts, the ``balance`` can't fall below zero.
193+
194+
You can implement the extra validations by identifying invalid ``customer`` and
195+
``account`` documents and implementing those constraints into your schema validation.
196+
197+
.. code-block:: javascript
198+
199+
const invalidCustomer = {
200+
"$expr": { "$gt": ["$customerSince", "$$NOW"] }
201+
};
202+
203+
const invalidAccount = {
204+
$or: [
205+
{
206+
accountType: "savings",
207+
balance: { $lt: 0}
208+
},
209+
{
210+
"$expr": { "$gt": ["$dateOpened", "$$NOW"]}
211+
}
212+
]
213+
};
214+
215+
.. code-block:: javascript
216+
217+
const schemaValidation = {
218+
"$and": [
219+
{ $jsonSchema: { oneOf: [ customerSchema, accountSchema ] }},
220+
{ $nor: [
221+
invalidCustomer,
222+
invalidAccount
223+
]
224+
}
225+
]
226+
};
227+
228+
db.runCommand({
229+
collMod: "accounts",
230+
validator: schemaValidation
231+
})
232+
233+
.. step:: Verify the documents in your collection
234+
235+
To verify that all the documents already in your collection adhere to your new
236+
schema validation, use the :method:`db.collection.validate()` command.
237+
238+
.. io-code-block::
239+
:copyable: true
240+
241+
.. input::
242+
:language: javascript
243+
244+
db.accounts.validate()
245+
246+
.. output::
247+
:language: json
248+
:emphasize-lines: 5
249+
250+
{
251+
ns: '66cf8508e64dbb03ce45b30e_test.accounts',
252+
uuid: UUID('1aedf62a-f202-4e7c-b434-879057bb6d6b'),
253+
nInvalidDocuments: 0,
254+
nNonCompliantDocuments: 0,
255+
nrecords: 10,
256+
nIndexes: 1,
257+
keysPerIndex: { _id_: 10 },
258+
indexDetails: { _id_: { valid: true } },
259+
valid: true,
260+
repaired: false,
261+
readTimestamp: Timestamp({ t: 1749235730, i: 26 }),
262+
warnings: [],
263+
errors: [],
264+
extraIndexEntries: [],
265+
missingIndexEntries: [],
266+
corruptRecords: [],
267+
ok: 1,
268+
'$clusterTime': {
269+
clusterTime: Timestamp({ t: 1749235753, i: 31 }),
270+
signature: {
271+
hash: Binary.createFromBase64('3h7qyhLsgU21Pnzf/KVLl8suu2I=', 0),
272+
keyId: Long('7449048397505364002')
273+
}
274+
},
275+
operationTime: Timestamp({ t: 1749235753, i: 31 })
276+
}
277+
278+
``nNonCompliantDocuments: 0`` in the output indicates that all the documents in the
279+
``accounts`` collection comply with the collection schemas.
280+
281+
.. step:: Test your schema validation
282+
283+
To verify your schema validation, you can try to insert an invalid document into the ``accounts`` collection.
284+
For example, try inserting a ``customer`` document missing the required ``last`` field, for last name:
285+
286+
.. io-code-block::
287+
:copyable: true
288+
289+
.. input::
290+
:language: javascript
291+
292+
db.accounts.insertOne(
293+
{
294+
"docType": "customer",
295+
"customerId": "12345",
296+
"name": {
297+
"first": "John",
298+
},
299+
"customerSince": "2025-01-01T00:00:00Z"
300+
}
301+
)
302+
303+
.. output::
304+
:language: json
305+
306+
MongoServerError: Document failed validation

content/manual/v6.0/source/core/schema-validation.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ Tasks
8888
For common tasks involving schema validation, see the following pages:
8989

9090
- :ref:`schema-validation-json`
91+
- :ref:`schema-validation-polymorphic-collections`
9192
- :ref:`schema-validation-query-expression`
9293
- :ref:`schema-allowed-field-values`
9394
- :ref:`schema-view-validation-rules`
@@ -105,6 +106,7 @@ To learn about MongoDB's flexible schema model, see
105106
:titlesonly:
106107

107108
Specify JSON Validation </core/schema-validation/specify-json-schema>
109+
Specify Validation for Polymorphic Collections </core/schema-validation/specify-validation-polymorphic-collections>
108110
Specify Query Operators </core/schema-validation/specify-query-expression-rules>
109111
Specify Validation Level </core/schema-validation/specify-validation-level>
110112
Handle Invalid Documents </core/schema-validation/handle-invalid-documents>

0 commit comments

Comments
 (0)