Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using $set with nested integer keys breaks the structure #3775

Closed
zeapo opened this issue Aug 20, 2021 · 2 comments
Closed

Using $set with nested integer keys breaks the structure #3775

zeapo opened this issue Aug 20, 2021 · 2 comments
Labels
type: bug A general bug

Comments

@zeapo
Copy link

zeapo commented Aug 20, 2021

Given the following class (kotlin)

@Document(collection = "nested_hell")
@TypeAlias("hell")
data class NestedHell(
    @Id val eyeDee: String,
    val levelOne: HashMap<String, HashMap<String, HashMap<String, Any>>>
)

Update queries where the keys in these nested hashmaps are integers, will not go deeper than the second level:

        ops.save(
            NestedHell(
                eyeDee = "id1",
                levelOne = hashMapOf("0" to hashMapOf("1" to hashMapOf("2" to 3)))
            )
        )
        val updateQuery = Update().set("levelOne.0.1.3", 4)
        println(updateQuery.updateObject.toJson())
        ops.updateFirst(
            Query(NestedHell::eyeDee isEqualTo "id1"),
            updateQuery,
            NestedHell::class.java
        )

The result of the save is:

{
        "_id" : "id1",
        "levelOne" : {
                "0" : {
                        "1" : {
                                "2" : 3
                        }
                }
        },
        "_class" : "hell"
}

The update query object is:

{"$set": {"levelOne.0.1.3": 4}}

The result given by the updateFirst is however

{ "_id" : "id1", "levelOne" : { "0" : 4 }, "_class" : "hell" }

Notice that the level "levelOne.0.1" has gone.

Doing the same thing in Mongo Shell gives the expected result:

> db.nested_hell.findOne()
{
        "_id" : "id1",
        "levelOne" : {
                "0" : {
                        "1" : {
                                "2" : 3
                        }
                }
        },
        "_class" : "hell"
}
> db.nested_hell.update({_id: 'id1'}, {"$set": {"levelOne.0.1.3": 4}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.nested_hell.findOne()
{
        "_id" : "id1",
        "levelOne" : {
                "0" : {
                        "1" : {
                                "2" : 3,
                                "3" : 4
                        }
                }
        },
        "_class" : "hell"
}

When using non-numeric keys, the result is as one would expect:

        ops.save(
            NestedHell(
                eyeDee = "id2",
                levelOne = hashMapOf("a" to hashMapOf("b" to hashMapOf("c" to "d")))
            )
        )
        val updateQueryTwo = Update().set("levelOne.a.b.d", "e")
        println(updateQueryTwo.updateObject.toJson())
        val modified = ops.updateFirst(
            Query(NestedHell::eyeDee isEqualTo "id2"),
            updateQueryTwo,
            NestedHell::class.java
        )

Gives

{ "_id" : "id2", "levelOne" : { "a" : { "b" : { "c" : "d", "d" : "e" } } }, "_class" : "hell" }
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Aug 20, 2021
@christophstrobl
Copy link
Member

Thanks for reporting - we'll look into this.

@christophstrobl christophstrobl added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged labels Aug 20, 2021
@divyajnu08
Copy link
Contributor

Hi Christoph

Please find the below pull request for the solution.
Issue-3775 - Using $set with nested integer keys breaks the structure

I tried to replicate in java and found out the following :
Method: getPath(...) line 1186 , file : QueryMapper
It uses the pattern DOT_POSITIONAL_PATTERN = Pattern.compile("\.\d+"); to remove nested integer keys.

while removing the nested integer keys , it removes the leaf key also in the query expression.
E.g: for path , levelOne.0.1.3 , it removes all the matching dots followed by integer ( becomes "levelOne" ) which removes the leaf key as well due to which we lose the information of leaf index. The result of which the expression rendered is {"$set": {"levelOne.0": 4}}
So I excluded the last key which contains the information of the path to set i.e
DOT_POSITIONAL_PATTERN = Pattern.compile("\.\d+(?!$)")
The result of which the expression rendered correctly as {"$set": {"levelOne.0.1.3": 4}}

I don't know if I am going in the right direction kindly assist. If it is correct I will make the change in Kotlin as well.
I have also added the test cases for this problem.

Thanks
Divya

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

No branches or pull requests

4 participants