[JAVA-3392] Custom Key with nested objects fail to find by key. Created: 21/Aug/19  Updated: 27/Oct/23  Resolved: 26/Aug/19

Status: Closed
Project: Java Driver
Component/s: API
Affects Version/s: 3.8.2
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: Attila Grof Assignee: Unassigned
Resolution: Works as Designed Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

Java 8, MongoDB 4.0.6 Enterprise



 Description   

I am having problems using custom keys with nested objects. 

If I insert a record successfully I cannot find it by its key. If I try to use the ReplaceOneModel with upsert=true, then I get BulkWriteException

rite errors: [BulkWriteError{index=0, code=66, message='After applying the update, the (immutable) field '_id' was found to have been altered to _id:.......

If i remove the nested object everything works fine tough.....

This simple test fails with a complex key:

public static class TestKeyComplex {
    private String name;
    private Integer rowNum;
    private int teszt;
    private List<String> strings;
 
 
    public TestKeyComplex(String value) {
        strings = new ArrayList<>();
        strings.add("Hello");
    }
 
    public TestKeyComplex() {
        strings = new ArrayList<>();
        strings.add("Hello");
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public Integer getRowNum() {
        return rowNum;
    }
 
    public void setRowNum(Integer rowNum) {
        this.rowNum = rowNum;
    }
 
    public int getTeszt() {
        return teszt;
    }
 
    public void setTeszt(int teszt) {
        this.teszt = teszt;
    }
 
    public List<String> getStrings() {
        return strings;
    }
 
    public void setStrings(List<String> strings) {
        this.strings = strings;
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        TestKeyComplex that = (TestKeyComplex) o;
        return teszt == that.teszt &&
                Objects.equals(name, that.name) &&
                Objects.equals(rowNum, that.rowNum) &&
                Objects.equals(strings, that.strings);
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(name, rowNum, teszt, strings);
    }
}
 
public static class TestValue {
    private String name;
    private Integer rowNum;
    private Date date;
    private EmbeddedTestClass embeddedTestClass;
 
    public TestValue() {
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public Integer getRowNum() {
        return rowNum;
    }
 
    public void setRowNum(Integer rowNum) {
        this.rowNum = rowNum;
    }
 
    public Date getDate() {
        return date;
    }
 
    public void setDate(Date date) {
        this.date = date;
    }
 
    public EmbeddedTestClass getEmbeddedTestClass() {
        return embeddedTestClass;
    }
 
    public void setEmbeddedTestClass(EmbeddedTestClass embeddedTestClass) {
        this.embeddedTestClass = embeddedTestClass;
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        TestValue testValue = (TestValue) o;
        return Objects.equals(name, testValue.name) &&
                Objects.equals(rowNum, testValue.rowNum) &&
                Objects.equals(date, testValue.date) &&
                Objects.equals(embeddedTestClass, testValue.embeddedTestClass);
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(name, rowNum, date, embeddedTestClass);
    }
}
 
 
@Test
public void dummyDataSimpleComplexKeyInsert() {
    MongoDatabase db = mongoClient.getDatabase("TODO");
    MongoCollection mongoCollection = db.getCollection("TODO");
 
   /*
     set codec and other stuff
   */
 
    //fill object with data..
    // TODO   - MongoTestDataComplexKey -> contains key and value
    MongoTestDataComplexKey data = new MongoTestDataComplexKey(dummyKeyComplex, dummyValue);
 
    // Insert
    typedCollection.insertOne(data);
    assertEquals(1, typedCollection.find().into(new ArrayList<>()).size());
 
    // Find   
    MongoTestDataComplexKey res = typedCollection.find(eq("_id", dummyKey)).first();
    assertNotNull(res); // ------>>>> fails with null! 
    assertEquals(dummyValue, res.getValue());
 
    // Delete
    typedCollection.deleteOne(eq("_id", dummyKey));
    assertEquals(0, typedCollection.find().into(new ArrayList<>()).size());
}
 

 

 



 Comments   
Comment by Attila Grof [ 27/Aug/19 ]

From a usage point of view this is very strange. Lets say two different systems use the same collection, how are they suppose to know the other system's json generation attribute ordering logic. The Java driver automatically ordered the filter into alphabetical order when used with pojos ( I do not have control over that class). 

Shouldn't it be the mongo server's responsibility to order filter and insert attributes in the same order ?

 
Insert query

db.tc.insert(
    {
        _id: {
            valueA:"asd",
            valueZ:"lkj",
            valueB:"aaa"
        },
        field1:"some string"
    }
)

Returns the previously inserted element

db.tc.find(
               {
                   _id: {
                       valueA:"asd",
                       valueZ:"lkj",
                       valueB:"aaa"
                   },
                   field1:"some string"
               }
           )

Returns NULL

db.tc.find(
               {
                   _id: {
                       valueZ:"lkj",
                       valueA:"asd",
                       valueB:"aaa"
                   },
                   field1:"some string"
               }
           )

From a developer point of view these two queries should return the same data.

Comment by Esha Bhargava [ 26/Aug/19 ]

9freedy9@gmail.com You are correct that filtering by the value of a document requires that the field order matches. So this would not be considered a bug. Consider using $elemMatch when you want to match against a document.

Comment by Attila Grof [ 22/Aug/19 ]

So I figured out that the filter is ordering properties in alphabetical order and the data properties need to be saved in the same order.

Comment by Attila Grof [ 22/Aug/19 ]

As it turns out with nested objects in key, the filter json need to have the same order as the data json. Is this a feature or bug?

Generated at Thu Feb 08 08:59:30 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.