validationLevel 和 validationAction 到底差在哪?加了校验器之后已有的不合法文档会怎样?这篇用同一个 $jsonSchema 逐个组合试一遍,再看错误详情、日志和 insertMany 的行为。
test> db.createCollection("people", {validator: {$jsonSchema: {bsonType: "object", required: ["name", "age"], properties: {name: {bsonType: "string", minLength: 2}, age: {bsonType: "int", minimum: 0, maximum: 150}}}}})
{ ok: 1 }
test> db.people.insertOne({name: "Alice", age: 30})
{
acknowledged: true,
insertedId: ObjectId('6acc3d5114fbb3d4d43eda28')
}
test> try { db.people.insertOne({name: "B", age: 200, extra: 1}) } catch (e) { ({code: e.code, message: e.message, details: JSON.stringify(e.errInfo.details)}) }
{
code: 121,
message: 'Document failed validation',
details: '{"operatorName":"$jsonSchema","schemaRulesNotSatisfied":[{"operatorName":"properties","propertiesNotSatisfied":[{"propertyName":"name","details":[{"operatorNa
}
test> try { db.people.insertOne({name: "Carol"}) } catch (e) { e.errInfo.details.schemaRulesNotSatisfied[0].missingProperties }
[ 'age' ]
test> try { db.people.insertOne({name: "Dave", age: 30.5}) } catch (e) { JSON.stringify(e.errInfo.details.schemaRulesNotSatisfied[0].propertiesNotSatisfied[0]) }
{"propertyName":"age","details":[{"operatorName":"bsonType","specifiedAs":{"bsonType":"int"},"reason":"type did not match","consideredValue":30.5,"consideredType":"double违反校验器的写入报 121 Document failed validation,errInfo.details 里按规则列出原因:缺少哪个字段、类型不匹配(30.5 是 double,不满足 int)、数值超出范围。
test> db.runCommand({collMod: "people", validationAction: "warn"})
{ ok: 1 }
test> db.people.insertOne({name: "X", age: 999})
{
acknowledged: true,
insertedId: ObjectId('6acc3d5311b2cb41a418ee12')
}
mongod 日志里 "Document would fail validation" 的条数: 1
日志: {"s": "W", "c": "STORAGE", "msg": "Document would fail validation"} ; attr 里的键: ['document', 'errInfo', 'namespace']
test> db.people.countDocuments({})
2warn 下文档照样写入,只在 mongod 日志里记一条 “Document would fail validation”,附带文档与原因。
test> db.runCommand({collMod: "legacy", validator: {$jsonSchema: {bsonType: "object", required: ["name", "age"], properties: {name: {bsonType: "string", minLength: 2}, age: {bsonType: "int", minimum: 0, maximum: 150}}}}, validationLevel: "strict", validationAction: "error"})
{ ok: 1 }
test> db.legacy.find({$nor: [<同一个 $jsonSchema>]}) // 加校验器之前就存在的“不合法”文档不会被拒绝,但可以这样找出来
[ 2 ]
test> db.runCommand({collMod: "legacy", validationLevel: "strict"})
{ ok: 1 }
test> db.legacy.updateOne({_id: 2}, {$set: {note: "x"}}) // 老的不合法文档
Plan executor error during update :: caused by :: Document failed validation
test> db.legacy.updateOne({_id: 1}, {$set: {age: "x"}}) // 合法文档改成不合法
Plan executor error during update :: caused by :: Document failed validation
test> db.runCommand({collMod: "legacy", validationLevel: "moderate"})
{ ok: 1 }
test> db.legacy.updateOne({_id: 2}, {$set: {note: "x"}}) // 老的不合法文档
1
test> db.legacy.updateOne({_id: 1}, {$set: {age: "x"}}) // 合法文档改成不合法
Plan executor error during update :: caused by :: Document failed validation加校验器不会检查已有文档。strict 下连不合法的老文档也不能更新;moderate 下老的不合法文档可以更新,但原本合法的文档被改成不合法仍会被拒。
test> db.runCommand({collMod: "legacy", validationLevel: "strict"})
{ ok: 1 }
test> try { db.legacy.insertOne({_id: 3, name: "Zed", age: -5}) } catch (e) { e.message }
Document failed validation
test> db.legacy.insertOne({...不合法...}, {bypassDocumentValidation: true})
true
test> db.runCommand({collMod: "legacy", validationLevel: "off"})
{ ok: 1 }
test> db.legacy.insertOne({_id: 4, ...不合法...}) // validationLevel: off
true单次操作可以用 bypassDocumentValidation 绕过;validationLevel: off 则整个集合不再校验。
test> db.createCollection("events", {validator: {$expr: {$lte: ["$start", "$end"]}}})
{ ok: 1 }
test> db.events.insertOne({start: 1, end: 5}).acknowledged
true
test> try { db.events.insertOne({start: 9, end: 5}) } catch (e) { e.message + " / " + JSON.stringify(e.errInfo.details) }
Document failed validation / {"operatorName":"$expr","specifiedAs":{"$expr":{"$lte":["$start","$end"]}},"reason":"expression did not match","expressionResult":false}校验器也可以是 $expr,用来校验字段之间的关系,失败详情里是 expressionResult: false。
test> db.getCollectionInfos({name: "people"})[0].options
{
validator: {
'$jsonSchema': {
bsonType: 'object',
required: [ 'name', 'age' ],
properties: {
name: { bsonType: 'string', minLength: 2 },
age: { bsonType: 'int', minimum: 0, maximum: 150 }
}
}
},
validationLevel: 'strict',
validationAction: 'warn'
}
test> db.people.insertMany([合法, 不合法, 合法]) // ordered 默认 true
{ inserted: 1, writeErrors: 1, errIndex: 1 }
test> db.people.countDocuments({name: {$in: ["Ann", "Bob"]}})
1
test> db.people.insertMany([合法, 不合法, 合法], {ordered: false})
{ inserted: 2, writeErrors: 1 }校验器保存在集合 options 里。insertMany 默认 ordered,遇到不合法的文档就停止;ordered: false 则继续插入其余合法的。