spring-data-commons icon indicating copy to clipboard operation
spring-data-commons copied to clipboard

Property path with whitespace inconsistently throws exception

Open boly38 opened this issue 1 year ago • 2 comments

Hi here 👋 ,

What is the issue ?

On my project I want to update a given MongoDB document having properties map as subdocument.

I'm using org.springframework.data:spring-data-commons:jar:3.1.11 (and spring data mongodb 4.1.11) .

I encounter IllegalArgumentException: Name must not be null or empty from PropertyPath.java:82 while trying to patch a given document properties.

Context - How to reproduce ?

  • Given a following MongoDB content :
{"_id": .., "name":"docA", "properties": {"bien" : "dd", "OK" : "eee"}}

ℹ️ A usecase - I encounter no issue to set / unset a property having " " space in the beginning of a property key value with a word starting with lowercase; example :

  update.set("properties. ooo", "space minus");
  or 
  update.unset("properties. ooo");
  (...)
  mongoTemplate.findAndModify(query, update, options, documentClass);

ℹ️ B usecase - BUT now if I'm doing the same thing with a word starting with an uppercase, I encounter an issue :

  update.set("properties. P", "space Major");
  or 
  update.unset("properties. P");
  (...)
  mongoTemplate.findAndModify(query, update, options, documentClass);

java.lang.IllegalArgumentException: Name must not be null or empty

stack extract

java.lang.IllegalArgumentException: Name must not be null or empty

	at org.springframework.util.Assert.hasText(Assert.java:294)
	at org.springframework.data.mapping.PropertyPath.<init>(PropertyPath.java:82)
	at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:443)
	at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:476)
	at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:419)
	at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:403)
	at org.springframework.data.mapping.PropertyPath.lambda$from$0(PropertyPath.java:375)
	at java.base/java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:330)
	at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:354)
	at org.springframework.data.mongodb.core.convert.QueryMapper$MetadataBackedField.forName(QueryMapper.java:1310)
	at org.springframework.data.mongodb.core.convert.QueryMapper$MetadataBackedField.getPath(QueryMapper.java:1243)
	at org.springframework.data.mongodb.core.convert.QueryMapper$MetadataBackedField.<init>(QueryMapper.java:1136)
	at org.springframework.data.mongodb.core.convert.QueryMapper$MetadataBackedField.<init>(QueryMapper.java:1113)
	at org.springframework.data.mongodb.core.convert.UpdateMapper$MetadataBackedUpdateField.<init>(UpdateMapper.java:294)
	at org.springframework.data.mongodb.core.convert.UpdateMapper.createPropertyField(UpdateMapper.java:254)
	at org.springframework.data.mongodb.core.convert.QueryMapper.getMappedObject(QueryMapper.java:156)
	at org.springframework.data.mongodb.core.convert.UpdateMapper.getMappedObject(UpdateMapper.java:66)
	at org.springframework.data.mongodb.core.convert.QueryMapper.convertSimpleOrDocument(QueryMapper.java:596)
	at org.springframework.data.mongodb.core.convert.QueryMapper.getMappedKeyword(QueryMapper.java:403)
	at org.springframework.data.mongodb.core.convert.QueryMapper.getMappedObject(QueryMapper.java:150)
	at org.springframework.data.mongodb.core.convert.UpdateMapper.getMappedObject(UpdateMapper.java:66)
	at org.springframework.data.mongodb.core.QueryOperations$UpdateContext.getMappedUpdate(QueryOperations.java:861)
	at org.springframework.data.mongodb.core.MongoTemplate.doFindAndModify(MongoTemplate.java:2698)
	at org.springframework.data.mongodb.core.MongoTemplate.findAndModify(MongoTemplate.java:1088)
	at org.springframework.data.mongodb.core.MongoTemplate.findAndModify(MongoTemplate.java:1063)

Further analysis

By debugging some test with different value, I can state that spring-data-commons > PropertyPath component is processing some part of the update query and will see some field following spring data internal logic :

  • for the A use case, ("properties. ooo") only the word ooo is detected under properties
  • for the B use case, ("properties. P") two word P and are detected under properties, and as is empty, the assert exception (l82) throws an exception.

With mongo Shell, I'm trying to reproduce but no issue, all is fine (usecase B too)

case A OK
db.myDocs.find({"name":"docA"},{"properties":1})
db.myDocs.update({"name":"docA"},{ "$set": {"properties. minus":"blob"}})
db.myDocs.update({"name":"docA"},{ "$unset": {"properties. minus":1}})

case B OK
db.myDocs.update({"name":"docA"},{ "$set": {"properties. Major":"blob"}})
db.myDocs.find({"name":"docA"},{"properties":1})
db.myDocs.update({"name":"docA"},{ "$unset": {"properties. Major":1}})

What did I expect

I expect the update to work like on mongo shell.

I though this is a bug at the PropertyPath layer (but not sure) Or maybe in spring data mongodb?

If this is stated as "not a bug", I would like to know the recommendation for this kind of update for the "key" value.

Example (mongo manual):

  • exclude this words : class and _class,
  • . is not autorized
  • $ is not autorized as first character I appreciate some reference to complete this, if any

regards.

boly38 avatar Jul 03 '24 14:07 boly38