Skip to content

Commit 0fd3b04

Browse files
authored
Remove most field name validation (#725)
The driver no longer restricts field names containing ".". The driver no longer restricts field names starting with "$", except for top-level fields in documents being saved via replaceOne, findOneAndReplace, and ReplaceOneModel in bulkWrite. This is to ensure that applications don't accidentally use replace when they meant to use update. JAVA-3996
1 parent 0ce5c4f commit 0fd3b04

File tree

76 files changed

+5211
-219
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+5211
-219
lines changed

bson/src/test/resources/bson/document.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,26 @@
1717
"description": "Single-character key subdoc",
1818
"canonical_bson": "160000000378000E0000000261000200000062000000",
1919
"canonical_extjson": "{\"x\" : {\"a\" : \"b\"}}"
20+
},
21+
{
22+
"description": "Dollar-prefixed key in sub-document",
23+
"canonical_bson": "170000000378000F000000022461000200000062000000",
24+
"canonical_extjson": "{\"x\" : {\"$a\" : \"b\"}}"
25+
},
26+
{
27+
"description": "Dollar as key in sub-document",
28+
"canonical_bson": "160000000378000E0000000224000200000061000000",
29+
"canonical_extjson": "{\"x\" : {\"$\" : \"a\"}}"
30+
},
31+
{
32+
"description": "Dotted key in sub-document",
33+
"canonical_bson": "180000000378001000000002612E62000200000063000000",
34+
"canonical_extjson": "{\"x\" : {\"a.b\" : \"c\"}}"
35+
},
36+
{
37+
"description": "Dot as key in sub-document",
38+
"canonical_bson": "160000000378000E000000022E000200000061000000",
39+
"canonical_extjson": "{\"x\" : {\".\" : \"a\"}}"
2040
}
2141
],
2242
"decodeErrors": [

bson/src/test/resources/bson/top.json

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,24 @@
33
"bson_type": "0x00",
44
"valid": [
55
{
6-
"description": "Document with keys that start with $",
6+
"description": "Dollar-prefixed key in top-level document",
77
"canonical_bson": "0F00000010246B6579002A00000000",
88
"canonical_extjson": "{\"$key\": {\"$numberInt\": \"42\"}}"
9+
},
10+
{
11+
"description": "Dollar as key in top-level document",
12+
"canonical_bson": "0E00000002240002000000610000",
13+
"canonical_extjson": "{\"$\": \"a\"}"
14+
},
15+
{
16+
"description": "Dotted key in top-level document",
17+
"canonical_bson": "1000000002612E620002000000630000",
18+
"canonical_extjson": "{\"a.b\": \"c\"}"
19+
},
20+
{
21+
"description": "Dot as key in top-level document",
22+
"canonical_bson": "0E000000022E0002000000610000",
23+
"canonical_extjson": "{\".\": \"a\"}"
924
}
1025
],
1126
"decodeErrors": [
@@ -69,11 +84,11 @@
6984
"parseErrors": [
7085
{
7186
"description" : "Bad $regularExpression (extra field)",
72-
"string" : "{\"a\" : \"$regularExpression\": {\"pattern\": \"abc\", \"options\": \"\", \"unrelated\": true}}}"
87+
"string" : "{\"a\" : {\"$regularExpression\": {\"pattern\": \"abc\", \"options\": \"\", \"unrelated\": true}}}"
7388
},
7489
{
7590
"description" : "Bad $regularExpression (missing options field)",
76-
"string" : "{\"a\" : \"$regularExpression\": {\"pattern\": \"abc\"}}}"
91+
"string" : "{\"a\" : {\"$regularExpression\": {\"pattern\": \"abc\"}}}"
7792
},
7893
{
7994
"description": "Bad $regularExpression (pattern is number, not string)",
@@ -85,7 +100,7 @@
85100
},
86101
{
87102
"description" : "Bad $regularExpression (missing pattern field)",
88-
"string" : "{\"a\" : \"$regularExpression\": {\"options\":\"ix\"}}}"
103+
"string" : "{\"a\" : {\"$regularExpression\": {\"options\":\"ix\"}}}"
89104
},
90105
{
91106
"description": "Bad $oid (number, not string)",

docs/reference/content/whats-new.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ New features of the 4.3 Java driver release include:
1515

1616
* Added support for the MongoDB Versioned API. See the
1717
[`ServerApi`]({{< apiref "mongodb-driver-core" "com/mongodb/ServerApi.html" >}}) API documentation for details.
18+
* Removed most restrictions on allowed characters in field names of documents being inserted or replaced. This is a behavioral change
19+
for any application that is relying on client-side enforcement of these restrictions. In particular:
20+
* Restrictions on field names containing the "." character have been removed. This affects all insert and replace operations.
21+
* Restrictions on field names starting with the "$" character have been removed for all insert operations.
22+
* Restrictions in nested documents on field names starting with the "$" character have been removed for all replace operations.
23+
* Restrictions in the top-level document on field names starting with the "$" character remain for all replace operations. This is
24+
primarily to prevent accidental use of a replace operation when the intention was to use an update operation.
25+
* Note that unacknowledged writes using dollar-prefixed or dotted keys may be silently rejected by pre-5.0 servers, where some
26+
restrictions on field names are still enforced in the server.
1827

1928
# What's new in 4.2
2029

driver-core/src/main/com/mongodb/internal/connection/InsertMessage.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
package com.mongodb.internal.connection;
1818

1919
import com.mongodb.internal.bulk.InsertRequest;
20-
import com.mongodb.internal.validator.CollectibleDocumentFieldNameValidator;
20+
import com.mongodb.internal.validator.NoOpFieldNameValidator;
2121
import org.bson.io.BsonOutput;
2222

2323
/**
@@ -38,7 +38,7 @@ class InsertMessage extends LegacyMessage {
3838
protected EncodingMetadata encodeMessageBodyWithMetadata(final BsonOutput outputStream) {
3939
writeInsertPrologue(outputStream);
4040
int firstDocumentPosition = outputStream.getPosition();
41-
addCollectibleDocument(insertRequest.getDocument(), outputStream, new CollectibleDocumentFieldNameValidator());
41+
addCollectibleDocument(insertRequest.getDocument(), outputStream, new NoOpFieldNameValidator());
4242
return new EncodingMetadata(firstDocumentPosition);
4343
}
4444

driver-core/src/main/com/mongodb/internal/connection/UpdateMessage.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
package com.mongodb.internal.connection;
1818

1919
import com.mongodb.internal.bulk.UpdateRequest;
20-
import com.mongodb.internal.validator.CollectibleDocumentFieldNameValidator;
20+
import com.mongodb.internal.validator.ReplacingDocumentFieldNameValidator;
2121
import com.mongodb.internal.validator.NoOpFieldNameValidator;
2222
import com.mongodb.internal.validator.UpdateFieldNameValidator;
2323
import org.bson.BsonValue;
@@ -56,7 +56,7 @@ protected EncodingMetadata encodeMessageBodyWithMetadata(final BsonOutput bsonOu
5656

5757
addDocument(updateRequest.getFilter(), bsonOutput, new NoOpFieldNameValidator());
5858
if (updateRequest.getType() == REPLACE && updateRequest.getUpdateValue().isDocument()) {
59-
addDocument(updateRequest.getUpdateValue().asDocument(), bsonOutput, new CollectibleDocumentFieldNameValidator());
59+
addDocument(updateRequest.getUpdateValue().asDocument(), bsonOutput, new ReplacingDocumentFieldNameValidator());
6060
} else {
6161
int bufferPosition = bsonOutput.getPosition();
6262
BsonValue update = updateRequest.getUpdateValue();

driver-core/src/main/com/mongodb/internal/operation/BulkWriteBatch.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@
3636
import com.mongodb.internal.connection.IndexMap;
3737
import com.mongodb.internal.connection.SplittablePayload;
3838
import com.mongodb.internal.session.SessionContext;
39-
import com.mongodb.internal.validator.CollectibleDocumentFieldNameValidator;
4039
import com.mongodb.internal.validator.MappedFieldNameValidator;
4140
import com.mongodb.internal.validator.NoOpFieldNameValidator;
41+
import com.mongodb.internal.validator.ReplacingDocumentFieldNameValidator;
4242
import com.mongodb.internal.validator.UpdateFieldNameValidator;
4343
import org.bson.BsonArray;
4444
import org.bson.BsonBoolean;
@@ -260,12 +260,10 @@ public BulkWriteBatch getNextBatch() {
260260
}
261261

262262
public FieldNameValidator getFieldNameValidator() {
263-
if (batchType == INSERT) {
264-
return new CollectibleDocumentFieldNameValidator();
265-
} else if (batchType == UPDATE || batchType == REPLACE) {
263+
if (batchType == UPDATE || batchType == REPLACE) {
266264
Map<String, FieldNameValidator> rootMap = new HashMap<String, FieldNameValidator>();
267265
if (batchType == WriteRequest.Type.REPLACE) {
268-
rootMap.put("u", new CollectibleDocumentFieldNameValidator());
266+
rootMap.put("u", new ReplacingDocumentFieldNameValidator());
269267
} else {
270268
rootMap.put("u", new UpdateFieldNameValidator());
271269
}

driver-core/src/main/com/mongodb/internal/operation/FindAndReplaceOperation.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121
import com.mongodb.client.model.Collation;
2222
import com.mongodb.connection.ConnectionDescription;
2323
import com.mongodb.connection.ServerDescription;
24-
import com.mongodb.internal.validator.CollectibleDocumentFieldNameValidator;
2524
import com.mongodb.internal.validator.MappedFieldNameValidator;
2625
import com.mongodb.internal.validator.NoOpFieldNameValidator;
2726
import com.mongodb.internal.session.SessionContext;
27+
import com.mongodb.internal.validator.ReplacingDocumentFieldNameValidator;
2828
import com.mongodb.lang.Nullable;
2929
import org.bson.BsonBoolean;
3030
import org.bson.BsonDocument;
@@ -392,7 +392,7 @@ private BsonDocument createCommand(final SessionContext sessionContext, final Se
392392
@Override
393393
protected FieldNameValidator getFieldNameValidator() {
394394
Map<String, FieldNameValidator> map = new HashMap<String, FieldNameValidator>();
395-
map.put("update", new CollectibleDocumentFieldNameValidator());
395+
map.put("update", new ReplacingDocumentFieldNameValidator());
396396
return new MappedFieldNameValidator(new NoOpFieldNameValidator(), map);
397397
}
398398
}

driver-core/src/main/com/mongodb/internal/validator/CollectibleDocumentFieldNameValidator.java renamed to driver-core/src/main/com/mongodb/internal/validator/ReplacingDocumentFieldNameValidator.java

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@
2222
import java.util.List;
2323

2424
/**
25-
* A field name validator for document that are meant for storage in MongoDB collections. It ensures that no fields contain a '.',
26-
* or start with '$' (with the exception of "$db", "$ref", and "$id", so that DBRefs are not rejected).
25+
* A field name validator for documents that are meant for storage in MongoDB collections via replace operations. It ensures that no
26+
* top-level fields start with '$' (with the exception of "$db", "$ref", and "$id", so that DBRefs are not rejected).
2727
*
2828
* <p>This class should not be considered a part of the public API.</p>
2929
*/
30-
public class CollectibleDocumentFieldNameValidator implements FieldNameValidator {
30+
public class ReplacingDocumentFieldNameValidator implements FieldNameValidator {
31+
private static final NoOpFieldNameValidator NO_OP_FIELD_NAME_VALIDATOR = new NoOpFieldNameValidator();
3132
// Have to support DBRef fields
3233
private static final List<String> EXCEPTIONS = Arrays.asList("$db", "$ref", "$id");
3334

@@ -37,18 +38,12 @@ public boolean validate(final String fieldName) {
3738
throw new IllegalArgumentException("Field name can not be null");
3839
}
3940

40-
if (fieldName.contains(".")) {
41-
return false;
42-
}
43-
44-
if (fieldName.startsWith("$") && !EXCEPTIONS.contains(fieldName)) {
45-
return false;
46-
}
47-
return true;
41+
return !fieldName.startsWith("$") || EXCEPTIONS.contains(fieldName);
4842
}
4943

5044
@Override
5145
public FieldNameValidator getValidatorForField(final String fieldName) {
52-
return this;
46+
// Only top-level fields are validated
47+
return NO_OP_FIELD_NAME_VALIDATOR;
5348
}
5449
}

driver-core/src/test/functional/com/mongodb/internal/connection/WriteProtocolCommandEventSpecification.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,8 @@ class WriteProtocolCommandEventSpecification extends OperationFunctionalSpecific
201201

202202
def 'should not deliver any events if encoding fails'() {
203203
given:
204-
def insertRequest = new InsertRequest(new BsonDocument('$set', new BsonInt32(1)))
205-
def protocol = new InsertProtocol(getNamespace(), true, insertRequest)
204+
def updateRequest = new UpdateRequest(new BsonDocument(), new BsonDocument('$set', new BsonInt32(1)), REPLACE)
205+
def protocol = new UpdateProtocol(getNamespace(), true, updateRequest)
206206
def commandListener = new TestCommandListener()
207207
protocol.commandListener = commandListener
208208

driver-core/src/test/resources/transactions/errors-client.json

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,15 @@
2525
"object": "session0"
2626
},
2727
{
28-
"name": "insertOne",
28+
"name": "updateOne",
2929
"object": "collection",
3030
"arguments": {
3131
"session": "session0",
32-
"document": {
33-
"_id": {
34-
".": "."
35-
}
32+
"filter": {
33+
"_id": 1
34+
},
35+
"update": {
36+
"x": 1
3637
}
3738
},
3839
"error": true
@@ -60,22 +61,23 @@
6061
"arguments": {
6162
"session": "session0",
6263
"document": {
63-
"_id": 4
64+
"_id": 1
6465
}
6566
},
6667
"result": {
67-
"insertedId": 4
68+
"insertedId": 1
6869
}
6970
},
7071
{
71-
"name": "insertOne",
72+
"name": "updateOne",
7273
"object": "collection",
7374
"arguments": {
7475
"session": "session0",
75-
"document": {
76-
"_id": {
77-
".": "."
78-
}
76+
"filter": {
77+
"_id": 1
78+
},
79+
"update": {
80+
"x": 1
7981
}
8082
},
8183
"error": true

0 commit comments

Comments
 (0)