void run() { BSONObj o; { BSONObjBuilder b; b.appendTimestamp( "a" ); b.appendTimestamp( "b" ); b.append( "_id", 1 ); o = b.obj(); } BSONObj fixed = fixDocumentForInsert( o ).getValue(); ASSERT_EQUALS( 3, fixed.nFields() ); ASSERT( fixed.firstElement().fieldNameStringData() == "_id" ); ASSERT( fixed.firstElement().number() == 1 ); BSONElement a = fixed["a"]; ASSERT( o["a"].type() == Timestamp ); ASSERT( o["a"].timestampValue() == 0 ); ASSERT( a.type() == Timestamp ); ASSERT( a.timestampValue() > 0 ); BSONElement b = fixed["b"]; ASSERT( o["b"].type() == Timestamp ); ASSERT( o["b"].timestampValue() == 0 ); ASSERT( b.type() == Timestamp ); ASSERT( b.timestampValue() > 0 ); }
void run() { BSONObjBuilder b; b.appendTimestamp("a"); b.append("_id", 1); BSONObj o = b.done(); BSONObj fixed = fixDocumentForInsert(_opCtx.getServiceContext(), o).getValue(); ASSERT_EQUALS(2, fixed.nFields()); ASSERT(fixed.firstElement().fieldNameStringData() == "_id"); ASSERT(fixed.firstElement().number() == 1); BSONElement a = fixed["a"]; ASSERT(o["a"].type() == bsonTimestamp); ASSERT(o["a"].timestampValue() == 0); ASSERT(a.type() == bsonTimestamp); ASSERT(a.timestampValue() > 0); }
StatusWith<BSONObj> fixDocumentForInsert(ServiceContext* service, const BSONObj& doc) { if (doc.objsize() > BSONObjMaxUserSize) return StatusWith<BSONObj>(ErrorCodes::BadValue, str::stream() << "object to insert too large" << ". size in bytes: " << doc.objsize() << ", max size: " << BSONObjMaxUserSize); auto depthStatus = validateDepth(doc); if (!depthStatus.isOK()) { return depthStatus; } bool firstElementIsId = false; bool hasTimestampToFix = false; bool hadId = false; { BSONObjIterator i(doc); for (bool isFirstElement = true; i.more(); isFirstElement = false) { BSONElement e = i.next(); if (e.type() == bsonTimestamp && e.timestampValue() == 0) { // we replace Timestamp(0,0) at the top level with a correct value // in the fast pass, we just mark that we want to swap hasTimestampToFix = true; } auto fieldName = e.fieldNameStringData(); if (fieldName[0] == '$') { return StatusWith<BSONObj>( ErrorCodes::BadValue, str::stream() << "Document can't have $ prefixed field names: " << fieldName); } // check no regexp for _id (SERVER-9502) // also, disallow undefined and arrays // Make sure _id isn't duplicated (SERVER-19361). if (fieldName == "_id") { if (e.type() == RegEx) { return StatusWith<BSONObj>(ErrorCodes::BadValue, "can't use a regex for _id"); } if (e.type() == Undefined) { return StatusWith<BSONObj>(ErrorCodes::BadValue, "can't use a undefined for _id"); } if (e.type() == Array) { return StatusWith<BSONObj>(ErrorCodes::BadValue, "can't use an array for _id"); } if (e.type() == Object) { BSONObj o = e.Obj(); Status s = o.storageValidEmbedded(); if (!s.isOK()) return StatusWith<BSONObj>(s); } if (hadId) { return StatusWith<BSONObj>(ErrorCodes::BadValue, "can't have multiple _id fields in one document"); } else { hadId = true; firstElementIsId = isFirstElement; } } } } if (firstElementIsId && !hasTimestampToFix) return StatusWith<BSONObj>(BSONObj()); BSONObjIterator i(doc); BSONObjBuilder b(doc.objsize() + 16); if (firstElementIsId) { b.append(doc.firstElement()); i.next(); } else { BSONElement e = doc["_id"]; if (e.type()) { b.append(e); } else { b.appendOID("_id", NULL, true); } } while (i.more()) { BSONElement e = i.next(); if (hadId && e.fieldNameStringData() == "_id") { // no-op } else if (e.type() == bsonTimestamp && e.timestampValue() == 0) { auto nextTime = LogicalClock::get(service)->reserveTicks(1); b.append(e.fieldName(), nextTime.asTimestamp()); } else { b.append(e); } } return StatusWith<BSONObj>(b.obj()); }
StatusWith<BSONObj> fixDocumentForInsert( const BSONObj& doc ) { if ( doc.objsize() > BSONObjMaxUserSize ) return StatusWith<BSONObj>( ErrorCodes::BadValue, str::stream() << "object to insert too large" << ". size in bytes: " << doc.objsize() << ", max size: " << BSONObjMaxUserSize ); bool firstElementIsId = doc.firstElement().fieldNameStringData() == "_id"; bool hasTimestampToFix = false; { BSONObjIterator i( doc ); while ( i.more() ) { BSONElement e = i.next(); if ( e.type() == Timestamp && e.timestampValue() == 0 ) { // we replace Timestamp(0,0) at the top level with a correct value // in the fast pass, we just mark that we want to swap hasTimestampToFix = true; } const char* fieldName = e.fieldName(); if ( fieldName[0] == '$' ) { return StatusWith<BSONObj>( ErrorCodes::BadValue, str::stream() << "Document can't have $ prefixed field names: " << e.fieldName() ); } // check no regexp for _id (SERVER-9502) // also, disallow undefined and arrays if ( str::equals( fieldName, "_id") ) { if ( e.type() == RegEx ) { return StatusWith<BSONObj>( ErrorCodes::BadValue, "can't use a regex for _id" ); } if ( e.type() == Undefined ) { return StatusWith<BSONObj>( ErrorCodes::BadValue, "can't use a undefined for _id" ); } if ( e.type() == Array ) { return StatusWith<BSONObj>( ErrorCodes::BadValue, "can't use an array for _id" ); } if ( e.type() == Object ) { BSONObj o = e.Obj(); Status s = o.storageValidEmbedded(); if ( !s.isOK() ) return StatusWith<BSONObj>( s ); } } } } if ( firstElementIsId && !hasTimestampToFix ) return StatusWith<BSONObj>( BSONObj() ); bool hadId = firstElementIsId; BSONObjIterator i( doc ); BSONObjBuilder b( doc.objsize() + 16 ); if ( firstElementIsId ) { b.append( doc.firstElement() ); i.next(); } else { BSONElement e = doc["_id"]; if ( e.type() ) { b.append( e ); hadId = true; } else { b.appendOID( "_id", NULL, true ); } } while ( i.more() ) { BSONElement e = i.next(); if ( hadId && e.fieldNameStringData() == "_id" ) { // no-op } else if ( e.type() == Timestamp && e.timestampValue() == 0 ) { mutex::scoped_lock lk(OpTime::m); b.append( e.fieldName(), OpTime::now(lk) ); } else { b.append( e ); } } return StatusWith<BSONObj>( b.obj() ); }