How to implement query filters and sorting using Graphql SPQR? - graphql

How to implement query filters and sorting using Graphql SPQR?
I'm looking for a solution with Graphql SPQR for schema which looks something like this.
schema {
query: Query
mutation: Mutation
}
enumSortOrder {
ASC
DESC
}
type Article {
id: String
name: String
createdByUserId: String
createdOn: String
lastUpdatedOn: String
}
type Feedback {
id: String
feedbackText: String
articleId: String
createdByUserId: String
createdOn: String
lastUpdatedOn: String
}
type Query {
getAllArticles(pageNumber: Int!, pageSize : Int!, sortOrder: SortOrder!, sortBy: String!): [Article]
getFeedBacksForArticle(articleId: String!): [Feedback]
}
type Mutation {
createArticle(name: String!, createdByUserId: String!): Article
createNewFeedback(feedbackText: String!, articleId: String!, createdByUserId: String!): Feedback
}

GraphQL SPQR relies on a code-first approach. You need to create your java classes and resolver and annotate them properly:
public enum SortOrder {
#GraphQLEnumValue(name = "ASC") ASC,
#GraphQLEnumValue(name = "DESC") DESC
}
public class Article {
//Article implementation here
}
public class Feedback {
//Feedback implementation here
}
public class GraphQLResolver {
#GraphQLQuery(name = "getAllArticles", description = "Search articles")
public List<Article> getAllArticles(
#NotNull #GraphQLArgument(name = "pageNumber") int pageNumber,
#NotNull #GraphQLArgument(name = "pageSize") int pageSize,
#NotNull #GraphQLArgument(name = "sortOrder") SortOrder sortOrder,
#NotNull #GraphQLArgument(name = "sortBy") String sortBy) {
//Query implementation here
}
//implement other queries and mutation
}
Follow the readme at https://github.com/leangen/graphql-spqr to expose your graphql resolver.

Related

HotChocolate GraphQL query to page/filter/sort on nested array fields

HotChocolate Version=12.3.2.0
I want to be able to page/filter/sort on nested fields. For example, where user id = 1234, get the user's 1st document set, then the 1st docFile in the document set, ordered by docFile createdDate.
public class User
{
public int Id {get;set}
[UsePaging]
[UseFiltering]
[UseSorting]
public List<Document> Documents { get; set; }
}
public class Document
{
[UsePaging]
[UseFiltering]
[UseSorting]
public List<DocFile> DocFiles { get; set; }
public User User {get;set;}
}
public class DocFile
{
public string Name {get;set}
public DateTime CreatedDate {get;set;}
public Document Document {get;set;}
}
[UseAppDbContext]
[UsePaging]
[UseProjection]
[UseFiltering]
[UseSorting]
public async Task<Connection<User>> GetUsersAsync(
IResolverContext context,
[ScopedService] DbContext dbContext,
CancellationToken cancellationToken
)
{
var dbResult = dbContext.Users.Filter(context).Sort(context).Project(context).ToArray();
var result = await dbResult.ApplyCursorPaginationAsync(context, cancellationToken);
return result;
}
GraphQL Query
users(
where: {id: {eq: 1234}}
) {
nodes {
documents(first:1){
id
files(first:1 order:{createdDate: DESC}) {
nodes {
name
createdDate
}
}
}
}
}
But when I execute the GraphQL query I currently get the following error:
"exceptionType": "InvalidOperationException",
"message": "No generic method 'OrderByDescending' on type 'System.Linq.Enumerable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic. "
Any idea on how to do this?
If the [UseSorting] annotation comes from HotChocolate.Types it's the old way of filtering (uses a different syntax). Then it should be order_by in your query.
Try [HotChocolate.Data.UseSorting] to match your query.

Find all embedded documents from manual reference in MongoDB

I use MongoDB and Spring Boot in a project. I used manual reference to point out a collection, My structure is as follows:
Reel collection
{
_id : "reel_id_1",
name: "reel 1",
category :[
{
_id : "category_id_1",
name: "category 1",
videos: ["video_id_1","video_id_2"]
}
]
}
Video collection
{
_id: "video_id_1", // first document
name: "mongo"
}
{
_id: "video_id_2", // seconddocument
name: "java"
}
Java classes are
#Document
#Data
public class Reel {
#Id
private ObjectId _id;
private String name;
List<Category> category;
}
#Data
public class Category {
#Id
private ObjectId _id=new ObjectId();
private String name;
Video videos;
}
#Document
#Data
public class Video {
#Id
private ObjectId _id = new ObjectId();
private String name;
}
I tried to join both document via mongoTemplate
public List<Reel> findById(ObjectId _id) {
LookupOperation lookupOperation = LookupOperation.newLookup()
.from("video")
.localField("category.videos")
.foreignField("_id")
.as("category.videos");
UnwindOperation unwindOperation = Aggregation.unwind("category");
Aggregation agg = newAggregation(unwindOperation,match(Criteria.where("_id").is(_id)),lookupOperation);
Aggregation aggregation = newAggregation(lookupOperation);
List<Reel> results = mongoTemplate.aggregate(aggregation, "reel", Reel.class).getMappedResults();
return results;
}
But it throws an error.
Failed to instantiate java.util.List using constructor NO_CONSTRUCTOR with arguments
But since I use "unwind", I created a new Entity UnwindReel and add Category category instead of List<Category> category. And used
List<UnwindReel> results = mongoTemplate.aggregate(aggregation, "reel", UnwindReel.class).getMappedResults();
It combines only first video (video_id_1) object. How can I get all objects inside videos array? Is there any method to fetch?
Your JSON stored in database has wrong structure. Your Reel class expects list of Category, but in database you have stored as nested object.
You need to add this stage just after $lookup
{
"$addFields": {
"category": {
"$map": {
"input": "$category.videos",
"in": {
"videos": "$$this"
}
}
}
}
}
Java code
public List<Reel> findById(String _id) {
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(Criteria.where("_id").is(_id)),
Aggregation.lookup(mongoTemplate.getCollectionName(Video.class), "category.videos", "_id", "category.videos"),
new AggregationOperation() {
#Override
public Document toDocument(AggregationOperationContext context) {
return new Document("$addFields",
new Document("category", new Document("$map", new Document("input", "$category.videos")
.append("in", new Document("videos", "$$this")))));
}
})
.withOptions(AggregationOptions.builder().allowDiskUse(Boolean.TRUE).build());
LOG.debug(
aggregation.toString().replaceAll("__collection__", mongoTemplate.getCollectionName(Reel.class)));
return mongoTemplate.aggregate(aggregation, mongoTemplate.getCollectionName(Reel.class), Reel.class)
.getMappedResults();
}
Recomendations
Do not hard-code collection name, use better mongoTemplate.getCollectionName method
Always log aggregation pipeline before performing, it helps debugging.
If your collection will grow in the future, use {allowDiskUse: true} MongoDb aggregation option.

How to query nested objects from MongoDB using Spring Boot (REST) and TextQuery?

I am writing RESTful API to search MongoDB collection named 'global' by criteria using TextQuery. My problem is that I cannot access nested object fields when doing query.
For example this works:
GET localhost:8080/search?criteria=name:'name'
And this does not:
GET localhost:8080/search?criteria=other.othername:'Other Name'
I have MongoDB json structure (imported from JSON into 'global' collection as whole nested objects)
[{
"name": "Name",
"desc": "Desc",
"other" {
"othername": "Other Name",
}
},
{
"name": "Name",
"desc": "Desc",
"other" {
"othername": "Other Name",
}
}
]
And classes (with getters & setters & etc):
#Document(collection="global")
public class Global{
#TextIndexed
String name;
#TextIndexed
String desc;
Other other;
...
}
public class Other{
String othername;
...
}
My controller has method
#GetMapping("/search")
public Iterable<Global> getByCriteria(#RequestParam("criteria") String criteria) {
...
}
And I am trying to write text search with
public Iterable<Global> findByCriteria(String criteria) {
TextCriteria criteria = TextCriteria.forDefaultLanguage().matching(criteria);
TextQuery query = TextQuery.queryText(criteria);
return mongoTemplate.find(query, Global.class);
}
You need to add #TextIndexed to your other field.
public class Global{
#TextIndexed
String name;
#TextIndexed
String desc;
#TextIndexed
Other other;
...
}
Note: All nested object fields are searchable
or you may add #TextIndexed for each nested object field:
public class Other {
#TextIndexed
String othername;
...
}

How to handle List<Map<String, Object>> return type from query resolver in .graphqls

I have a graphql implementation, below is my .graphqls file
schema {
query: Query
}
type Product {
id: Int!
name: String!
description: String!
productTypes: [ProductType]!
}
type City {
id: Int!
name: String!
}
type ProductType {
id: Int!
type: String!
product: Int
}
type Query {
products: [Product]!
cities: [City]!
productTypes: [ProductType]!
}
This is my query resolver code
public class Query implements GraphQLQueryResolver {
#Autowired
ProductRespository productRespository;
#Autowired
EntityManager em;
#Autowired
CityRepository cityRepository;
#Autowired
ProductTypeRepository productTypeRepository;
public List<Map<String, Object>> products() {
javax.persistence.Query q = em.createQuery("select new
map(p.name as productName, p.id as productId) from Product p");
List<Map<String, Object>> list = q.getResultList();
return list;
}
public Iterable<City> cities() {
return cityRepository.findAll();
}
}
when I am running the application I am getting below error
Type java.util.Map cannot be
mapped to a GraphQL type! Since GraphQL-Java deals with erased types at
runtime, only non-parameterized classes can represent a GraphQL type.
This allows for reverse-lookup by java class in interfaces and union
types
So how I can handle this kind of return type in Query Section of .graphqls
Thanks in Advance

How to use another variable name or How to flatten entity in JPA Projection for nested object

I'm making an api for querying nested entity with Spring Data JPA Projection.
My Code
The Entity:
#Entity
class User {
#Id
var userId: String
var name: String
var age: Int
#OneToOne
#JoinColumn(name = "userId")
var address: Address
}
#Entity
class Address {
var userId: String
var street: String
var city: String
var country: String
}
The Repository:
interface UserView {
val name: String
val address: AddressView
interface AddressView {
val city: String
val country: String
}
}
#Repository
interface UserRepository : JPARepository<User, String> {
fun findAll(): List<UserView>
}
Expected Response
{
"name": "example",
"city": "example-city",
"country": "example-country"
}
My code produces
{
"name": "example",
"address": {
"city": "example-city",
"country": "example-country"
}
}
I Tried
I tried another view to flatten object:
interface UserView {
val name: String
val addressCity: String
val addressCountry: String
}
But this case, the variable naming is too complicate.
I want to solve this problem with projection. How can I solve this problem?
In JPA, you can do this using #NamedNativeQuery only:
#NamedNativeQuery(
name = "getUser",
query = "SELECT u.name, a.city, a.country FROM User u and Address a where a.userId = u.id ", resultClass=UserView .class)
#Entity
class User {
...
}
For Reference hibernate-named-query
Try this:
data class UserView(name: String, city: String, country: String)
#Repository
interface UserRepository : JPARepository<User, String> {
#Query(value = "select new your.pkg.UserView(u.name, u.address.city, u.address.country) from User u")
fun findAllFlat(): List<UserView>
}
You can use #Value and combine more fields or even access the fields of objects.
From the spring-data-rest documentation:
You can create a projection that combines the two data fields in the preceding example together, as follows:
#Projection(name = "virtual", types = { Person.class })
public interface VirtualProjection {
#Value("#{target.firstName} #{target.lastName}")
String getFullName();
}
Spring’s #Value annotation lets you plug in a SpEL expression that takes the target object and splices together its firstName and lastName attributes to render a read-only fullName.
Flattening also works for me:
#Value("#{target.document.title}")
String getDocumentTitle();

Resources