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

Projecting a field within an association fails with `MappingException`

Open mp911de opened this issue 2 years ago • 1 comments

Originally reported by kliarist at spring-projects/spring-graphql#746:

I am using spring-graphql along with spring-data-mongo and querydsl and faced with the below issue

org.springframework.data.mapping.MappingException: Invalid path reference articles.title; Associations can only be pointed to directly or via their id property
	at org.springframework.data.mongodb.core.convert.QueryMapper$MetadataBackedField.getPath(QueryMapper.java:1275) ~[spring-data-mongodb-4.1.1.jar:4.1.1]
	at org.springframework.data.mongodb.core.convert.QueryMapper$MetadataBackedField.<init>(QueryMapper.java:1134) ~[spring-data-mongodb-4.1.1.jar:4.1.1]
	at org.springframework.data.mongodb.core.convert.QueryMapper$MetadataBackedField.<init>(QueryMapper.java:1111) ~[spring-data-mongodb-4.1.1.jar:4.1.1]
	at org.springframework.data.mongodb.core.convert.QueryMapper.createPropertyField(QueryMapper.java:365) ~[spring-data-mongodb-4.1.1.jar:4.1.1]
	at org.springframework.data.mongodb.core.convert.QueryMapper.lambda$mapFieldsToPropertyNames$0(QueryMapper.java:234) ~[spring-data-mongodb-4.1.1.jar:4.1.1]
	at java.base/java.util.Map.forEach(Map.java:713) ~[na:na]
	at org.springframework.data.mongodb.core.convert.QueryMapper.mapFieldsToPropertyNames(QueryMapper.java:232) ~[spring-data-mongodb-4.1.1.jar:4.1.1]
	at org.springframework.data.mongodb.core.convert.QueryMapper.getMappedFields(QueryMapper.java:220) ~[spring-data-mongodb-4.1.1.jar:4.1.1]
	at org.springframework.data.mongodb.core.QueryOperations$QueryContext.getMappedFields(QueryOperations.java:363) ~[spring-data-mongodb-4.1.1.jar:4.1.1]
	at org.springframework.data.mongodb.core.MongoTemplate.doFind(MongoTemplate.java:2573) ~[spring-data-mongodb-4.1.1.jar:4.1.1]
	at org.springframework.data.mongodb.core.ExecutableFindOperationSupport$ExecutableFindSupport.doFind(ExecutableFindOperationSupport.java:177) ~[spring-data-mongodb-4.1.1.jar:4.1.1]
	at org.springframework.data.mongodb.core.ExecutableFindOperationSupport$ExecutableFindSupport.all(ExecutableFindOperationSupport.java:135) ~[spring-data-mongodb-4.1.1.jar:4.1.1]
	at org.springframework.data.mongodb.repository.support.SimpleMongoRepository$FluentQueryByExample.all(SimpleMongoRepository.java:391) ~[spring-data-mongodb-4.1.1.jar:4.1.1]
	at org.springframework.graphql.data.query.QueryByExampleDataFetcher$ManyEntityFetcher.getResult(QueryByExampleDataFetcher.java:751) ~[spring-graphql-1.2.1.jar:1.2.1]

Here is my set up :

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
@EqualsAndHashCode
@QueryEntity
@Document(collection = "articles")
public class Article {

    @Id
    private String id;
    private String title;
    private Integer minutesRead;
    @DBRef(lazy = true)
    private User author;
}
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
@Builder
@QueryEntity
@Document(collection = "users")
public class User {

    @Id
    private String id;

    private String name;
    private Integer age;
    private Date createdAt;
    private String nationality;
    private List<String> friendsIds;
    @DBRef(lazy = true)
    private List<Article> articles;
}
@GraphQlRepository
public interface UserRepository extends MongoRepository<User, String>, QuerydslPredicateExecutor<User> {
}

here is my graphql schema:

type Query {
    users: [User]
     ...
}

👍 So while the following graphql seems to work fine:

query {
  users {
    id
    age
    name
    articles {
      id
    }
  }
}	

❌ adding extra properties on the nested articles object does not:

query {
  users {
    id
    age
    name
    articles {
      id
      title
    }
  }
}	

throwing org.springframework.data.mapping.MappingException: Invalid path reference articles.title; Associations can only be pointed to directly or via their id property.

Can you please shed some light as to what is the problem here ?

Also, I have noticed that when using MongoRepository along with QuerydslPredicateExecutor then QueryByExampleDataFetcher takes precedence over QuerydslDataFetcher. Is this expected behaviour?

mp911de avatar Jul 17 '23 08:07 mp911de

The problem that we see here is that the fields to load are handed down to the actual query to be run, whereas this task cannot be completed since the actual articles field within the raw document in the database does not contain a title field but only holds the dbref { "$ref": "articles", "$id": ... }. So any attempt to project on fields of the referenced object will not be applied, and this makes the framework error at that point.

Generally speaking for this kind of scenario I'd recommend a custom implementation using an aggregation or rethink the schema design. However, in the combination with querydsl / graphql I'm not entirely sure what's the best way to move forward.

Simply removing the restriction cannot be the solution here as it would open up other invalid scenarios such as attempts to include values in referenced documents into a query expression that cannot be met.

We've created #4457 to explore the possibility of expanding a Query into an Aggregation that would use a $lookup stage to cover the scenario.

christophstrobl avatar Jul 18 '23 12:07 christophstrobl