spring-projects / spring-data-mongodb

Provides support to increase developer productivity in Java when using MongoDB. Uses familiar Spring concepts such as a template classes for core API usage and lightweight repository style data access.
https://spring.io/projects/spring-data-mongodb/
Apache License 2.0
1.62k stars 1.09k forks source link

Automatic index creation should distinguish between types targeting collection/view #4465

Open arj-mat opened 1 year ago

arj-mat commented 1 year ago

TLDR;

When annotating a query with @Annotation on a MongoRepository of a view, this UncategorizedMongoDbException is thrown: Command failed with error 166 (CommandNotSupportedOnView): 'Namespace my_database.my_view is a view, not a collection'

But reproducing the same query via the aggregate method of MongoTemplate causes no problems and returns the expected result.

Detailed explanation

christophstrobl commented 1 year ago

Thank you for getting in touch. The description outlines usage of an imperative repository but the stacktrace indicates a reactive flow via ReactiveMongoTemplate which is puzzling. Also the use of @Aggregation is not in line with the documentation.

// single stage
@Aggregation("{ '$project': { '_id' : '$lastname' } }")

// multiple stages
@Aggregation(pipeline = { "{ '$match' : { 'firstname' : '?0' } }", "{ '$project' : { '_id' : 0, 'lastname' : 1 } }" })

Maybe you can take the time to provide a complete minimal sample (something that we can unzip or git clone, build, and deploy) that reproduces the problem.

arj-mat commented 1 year ago

Hi. I took some time to dig deeper into this issue and came to a couple findings:

Firstly, the way I "resumed" the code for posting here was not adequate and ended-up removing necessary parts to reproduce the problem.

Secondly, the way I was using @Aggregation was indeed wrong and it induced me to believe the annotation was the root problem.

And finally, after some debugging, I realized that the failed command was an attempt to create indexes onto the nested document inside the view when having auto-index-creation set to true.

Here's the repository I made with reproducible code: https://github.com/arj-mat/spring-data-mongodb-issue-4465

// Document with an index
@Document("orders")
@Data
public class Order {
    @Indexed
    private Integer category;

    private String description;
    private Integer userId;
}
//View containing the Order document
@Document("users_orders_view")
@Data
public class UserOrderView {
    private Integer userId;
    private Set<Order> userOrders;
}

Raw "users_orders_view" aggregation pipeline:

[
  {
    $match: {
      userId: 1,
    },
  },
  {
    $group: {
      _id: "$userId",
      descriptions: {
        $first: {
          $concatArrays:
            "$userOrders.description",
        },
      },
    },
  },
]

Debugged command message:

{
  "createIndexes": "users_orders_view",
  "indexes": [
    {
      "key": {
        "userOrders.category": 1
      },
      "name": "userOrders.category"
    }
  ]
}

The invocation stack begins at MongoPersistentEntityIndexCreator#checkForAndCreateIndexes, which is not able to distinct between documents and views, therefore attempting to create nested indexes in it.

Otherwise, using another class (not annotted with @Document) to hold the contents of a document inside a view is a workaround in this scenario.

christophstrobl commented 1 year ago

thanks @arj-mat for following on this topic. Let me take this to the team to discuss how we want to move forward. Currently the components do not distinguish between a collection and a view. However, since MongoDB views are read only I can imagine extending the current @Document annotation.

@interface Document {
    boolean readOnly() default false;
    // ...
}

@Document(readOnly = true)
@interface View {
    @AliasFor(annotation = Document.class, attribute = "collection")
    String collection() default "";
    // ...
}

The above could serve multiple scenarios like