How does GraphQL support AND-OR query conditions? - graphql

I'm new to GraphQL. There is a requirement that query all the qualified data from CUSTOMERS table, which meets condition: GENDER == 'MALE' or 'AGE' >= 20. What GQL should looks like for it?

Someone proposed a similar thing before but it is rejected. That means GraphQL does not support it natively and you have to roll it out by yourself.
Several ways to do it based on what I see :
(1) Define your own query language such as what stackoverflow or Shopify does :
type Query{
customers (query:String) : [Customer]
}
The query becomes :
{
customers (query : "GENDER == 'MALE' or 'AGE' >= 20"){
id
name
}
}
(2) Define your own input object models that can cover all the required searching requirement.Prisma try to define one in OpenCRUD specification .You may take a look on it for the idea . For example , you can define an input model like :
input CustomerFilter {
AND : [CustomerFilter]
OR : [CustomerFilter]
# Define the fields that you support to search
gender : String
gender_not : String
....
....
...
...
age : Int
age_gte : Int
}
type Query{
customers (filter:CustomerFilter) : [Customer]
}
And the query becomes :
{
customers (filter : {
OR: [
{ gender : 'MALE' } ,
{ age_gte: 20 }
]
}){
id
name
}
}
This is another filter model for reference. The idea is to tailor-made it such that it is just enough to handle all your application requirements without introducing any unnecessary filtering complexity.
Also , you most probably need to consider something like pagination if it potentially will return many data. It means you have to add an offset and limit to the input arguments for each query to somehow limit the number of record returned if you are doing offset-based pagination or take a look on Relay Specification if you want to do it in the cursor-based pagination style.

You need to define query, schema and resolver. Your Query will be like :
type Query {
nameItAsPerUsecase: Customer
}
Schema will be like these :
type Customer{
name : String
age: Int
.. add more fields as per as your need
}
Your resolver will be like this: -
#Component
public class CustomerQuery implements GraphQLQueryResolver {
#Autowired
private CustomerRepository customerRepository ;
public Customer getNameItAsPerUsecase() {
return customerRepository.findByGenderAndAgeGreaterThanEqual(String gender, int age);
}
}

Nested logic with the same and/or conjunction can be simplified into a single list.
For example, the following complex query:
or: [
{ or: [ { foo: { eq: "A" } }, { bar: { eq: "B" } } ] },
{ or: [ { baz: { eq: "C" } }, { quz: { eq: "D" } } ] }
]
} ) { ... }
Moreover, can be simplified into the following simplified query syntax:
queryPost(filter: {
or: [
{ foo: { eq: "A" } },
{ bar: { eq: "B" } },
{ baz: { eq: "C" } },
{ quz: { eq: "D" } }
]
} ) { ... }

Related

Dynamically create pages with Gatsby based on many Contentful references

I am currently using Gatsby's collection routes API to create pages for a simple blog with data coming from Contentful.
For example, creating a page for each blogpost category :
-- src/pages/categories/{contentfulBlogPost.category}.js
export const query = graphql`
query categoriesQuery($category: String = "") {
allContentfulBlogPost(filter: { category: { eq: $category } }) {
edges {
node {
title
category
description {
description
}
...
}
}
}
}
...
[React component mapping all blogposts from each category in a list]
...
This is working fine.
But now I would like to have multiple categories per blogpost, so I switched to Contentful's references, many content-type, which allows to have multiple entries for a field :
Now the result of my graphQL query on field category2 is an array of different categories for each blogpost :
Query :
query categoriesQuery {
allContentfulBlogPost {
edges {
node {
category2 {
id
name
slug
}
}
}
}
}
Output :
{
"data": {
"allContentfulBlogPost": {
"edges": [
{
"node": {
"category2": [
{
"id": "75b89e48-a8c9-54fd-9742-cdf70c416b0e",
"name": "Test",
"slug": "test"
},
{
"id": "568r9e48-t1i8-sx4t8-9742-cdf70c4ed789vtu",
"name": "Test2",
"slug": "test-2"
}
]
}
},
{
"node": {
"category2": [
{
"id": "75b89e48-a8c9-54fd-9742-cdf70c416b0e",
"name": "Test",
"slug": "test"
}
]
}
},
...
Now that categories are inside an array, I don't know how to :
write a query variable to filter categories names ;
use the slug field as a route to dynamically create the page.
For blogposts authors I was doing :
query authorsQuery($author__slug: String = "") {
allContentfulBlogPost(filter: { author: { slug: { eq: $author__slug } } }) {
edges {
node {
id
author {
slug
name
}
...
}
...
}
And creating pages with src/pages/authors/{contentfulBlogPost.author__slug}.js
I guess I'll have to use the createPages API instead.
You can achieve the result using the Filesystem API, something like this may work:
src/pages/category/{contentfulBlogPost.category2__name}.js
In this case, it seems that this approach may lead to some caveats, since you may potentially create duplicated pages with the same URL (slug) because the posts can contain multiple and repeated categories.
However, I think it's more succinct to use the createPages API as you said, keeping in mind that you will need to treat the categories to avoid duplicities because they are in a one-to-many relationship.
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const result = await graphql(`
query {
allContentfulBlogPost {
edges {
node {
category2 {
id
name
slug
}
}
}
}
}
`)
let categories= { slugs: [], names: [] };
result.data.allContentfulBlogPost.edges.map(({node}))=> {
let { name, slug } = node.category2;
// make some checks if needed here
categories.slugs.push(slug);
categories.names.push(name);
return new Set(categories.slugs) && new Set(categories.names);
});
categories.slugs.forEach((category, index) => {
let name = categories.names[index];
createPage({
path: `category/${category}`,
component: path.resolve(`./src/templates/your-category-template.js`),
context: {
name
}
});
});
}
The code's quite self-explanatory. Basically you are defining an empty object (categories) that contains two arrays, slugs and names:
let categories= { slugs: [], names: [] };
After that, you only need to loop through the result of the query (result) and push the field values (name, slug, and others if needed) to the previous array, making the needed checks if you want (to avoid pushing empty values, or that matches some regular expression, etc) and return a new Set to remove the duplicates.
Then, you only need to loop through the slugs to create pages using createPage API and pass the needed data via context:
context: {
name
}
Because of redundancy, this is the same than doing:
context: {
name: name
}
So, in your template, you will get the name in pageContext props. Replace it with the slug if needed, depending on your situation and your use case, the approach is exactly the same.

How to adapt query to API?

I'm trying to wrap my head around GraphQL.
Right now I'm just playing with the public API of Artsy (an art website, playground at https://metaphysics-production.artsy.net). What I want to achieve is following:
I want to get all node types entities without declaring them by hand (is there a shortcut for this)?
I want every node with a field type from which I can read the type, without parsing through imageUrl etc. to fint that out.
What I constructed as of right now is this:
{
search(query: "Berlin", first: 100, page: 1, entities: [ARTIST, ARTWORK, ARTICLE]) {
edges {
node {
displayLabel
imageUrl
href
}
}
}}
Very primitive I guess. Can you guys help me?
TL;DR:
1) There is no shortcut, it's not something GraphQL offers out of the box. Nor is it something I was able to find via their Schema.
2) Their returned node of type Searchable does not contain a property for type that you're looking for. But you can access it via the ... on SearchableItem (union) syntax.
Explanation:
For question 1):
Looking at their schema, you can see that their search query has the following type details:
search(
query: String!
entities: [SearchEntity]
mode: SearchMode
aggregations: [SearchAggregation]
page: Int
after: String
first: Int
before: String
last: Int
): SearchableConnection
The query accepts an entities property of type SearchEntity which looks like this:
enum SearchEntity {
ARTIST
ARTWORK
ARTICLE
CITY
COLLECTION
FAIR
FEATURE
GALLERY
GENE
INSTITUTION
PROFILE
SALE
SHOW
TAG
}
Depending on what your usecase is, if you're constructing this query via some code, then you can find out which SearchEntity values they have:
{
__type(name: "SearchEntity") {
name
enumValues {
name
}
}
}
Which returns:
{
"data": {
"__type": {
"name": "SearchEntity",
"enumValues": [
{
"name": "ARTIST"
},
{
"name": "ARTWORK"
},
...
}
}
}
then store them in an array, omit the quotation marks from the enum and pass the array back to the original query directly as an argument.
Something along the lines of this:
query search($entities: [SearchEntity]) {
search(query: "Berlin", first: 100, page: 1, entities: $entities) {
edges {
node {
displayLabel
imageUrl
href
}
}
}
}
and in your query variables section, you just need to add:
{
"entities": [ARTIST, ARTWORK, ...]
}
As for question 2)
The query itself returns a SearchableConnection object.
type SearchableConnection {
pageInfo: PageInfo!
edges: [SearchableEdge]
pageCursors: PageCursors
totalCount: Int
aggregations: [SearchAggregationResults]
}
Digging deeper, we can see that they have edges, of type SearchableEdge - which is what you're querying.
type SearchableEdge {
node: Searchable
cursor: String!
}
and finally, node of type Searchable which contains the data you're trying to access.
Now, the type Searchable doesn't contain type:
type Searchable {
displayLabel: String
imageUrl: String
href: String
}
But, if you look at where that Searchable type is implemented, you can see SearchableItem - which contains the property of displayType - which doesn't actually exist in Searchable.
You can access the property of SearchableItem and get the displayType, like so:
{
search(query: "Berlin", first: 100, page: 1, entities: [ARTIST, ARTWORK, ARTICLE]) {
edges {
node {
displayLabel
imageUrl
href
... on SearchableItem {
displayType
}
}
}
}
}
and your result will look like this:
{
"data": {
"search": {
"edges": [
{
"node": {
"displayLabel": "Boris Berlin",
"imageUrl": "https://d32dm0rphc51dk.cloudfront.net/CRxSPNyhHKDIonwLKIVmIA/square.jpg",
"href": "/artist/boris-berlin",
"displayType": "Artist"
}
},
...

How to filter table with multiple values against one field

I am new to AWS App Sync, I want to query which accepts multiple values against one field and return result based on the input. Is this possible?
listBookByName(bookID: [String], limit: Int, nextToken: String): listBookByNameConnection
You can use DynamoDB Batch Resolvers to accomplish this.
You would attach a resolvers to the listBookByName field. The request mapping template (Takes the GraphQL query and converts it to a DynamoDB query) would look like the following:
#set($ids = [])
#foreach($id in ${ctx.args.bookID})
#set($map = {})
$util.qr($map.put("id", $util.dynamodb.toString($id)))
$util.qr($ids.add($map))
#end
{
"version" : "2018-05-29",
"operation" : "BatchGetItem",
"tables" : {
"Books": {
"keys": $util.toJson($ids),
"consistentRead": true
}
}
}
Your response mapping template will then have to marshall the results into your listBookByNameConnection type.
Here is an example in the documentation where they have batchGet query which takes a list of identifiers, and returns a list of posts.
https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-dynamodb-batch.html#single-table-batch
I have configured the setting as mentioned in the link. but it is returning null all the time.
type Query {
listBookByName(bookID: [String], limit: Int, nextToken: String): istBookByNameConnections
}
type listBookByNameConnections {
items: [Books]
}
My Resolver Mapping:
#set($ids = [])
#foreach($id in ${ctx.args.bookID})
#set($map = {})
$util.qr($map.put("id", $util.dynamodb.toString($id)))
$util.qr($ids.add($map))
#end
{
"version" : "2018-05-29",
"operation" : "BatchGetItem",
"tables" : {
"Books": {
"keys": $util.toJson($ids),
"consistentRead": true
}
}
}
Response Mapping:
$util.toJson($ctx.result.data.Books)
Query i used to check the response:
query get {
listBookByName(bookID:["JAVA","JUNIT"]){
items{
name
}
}
}
output :
{
"data": {
"listDriverTripsByName": null
}
}

Add or delete from connection depending on response

Essentially, I have users who can have payments, and theses payments can be filtered with an arg.
Here is my schema, simplified :
type User {
payments($filter: PaymentsFilter): PaymentsConnection,
}
enum PaymentsFilter {
MissingDetails,
}
type PaymentsConnection {
edges { ... }
pageInfo { ... }
}
type Payment {
id
description
}
The MissingDetails filter returns only the Payment who are missing a description.
For example, if I have 2 Payment :
[
{ id: 1, description: null },
{ id: 2, description: 'A great payment' },
]
A query as such :
query {
loggedUser {
payments(filter: MissingDetails) {
...
}
}
}
Would return only the first Payment, with id: 1.
I want to achieve an UpdatePaymentMutation, that would update the payment and depending on if the description is set or not in the response, I would RANGE_ADD it to the connection with the filter MissingDetails, or RANGE_DELETE it.
How can I achieve that ?

GraphQL - how to filter a hierarchy? ("customers who ordered gizmos last month")

Let's assume a type hierarchy of Customer -(hasMany)-> Orders -(hasMany)-> OrderLines
Something like this:
Customer {
Name
Orders [
{
OrderId
Date
OrderLines [
{
ItemCount
ItemName
}
]
}
]
}
I want to query for this whole tree, and filter on properties at any level in the tree.
For instance: Get all customers who ordered 'gizmos'.
This is what I tried: at each level of the hierarchy, I specify optional arguments that would filter based on the properties available at that level:
Customer (Name) {
Name
Orders (OrderId, Date) [
{
OrderId
Date
OrderLines (ItemCount, ItemName) [
{
ItemCount
ItemName
}
]
}
]
}
GraphQL needs me to define how to resolve each type in the hierarchy, so when resolving, I filter based on the arguments in the query.
But what if I only specify a filter at a deep level? e.g. ItemName : 'gizmo'
Assuming there's only one order line in the system containing a gizmo, I would expect to get a response like this:
[{
Name: "cust12",
Orders [{
OrderId: "ade32f",
OrderLines: [{
ItemCount: 50000, //customer really likes gizmos
ItemName: "gizmo"
}]
}]
}]
But what I actually get is all customers (no filter there), all their orders (no filter there) and all order items, mostly empty (the items inside are filtered).
[{
Name: "cust12",
Orders [
{
OrderId: "aaaaaa",
OrderLines: [ ]
},
{
OrderId: "ade32f",
OrderLines: [{
ItemCount: 50000,
ItemName: "gizmo"
}]
},
{
OrderId: "bbbbbb",
OrderLines: [ ]
},
{
OrderId: "cccccc",
OrderLines: [ ]
}
]
},
{
Name: "cust345",
Orders [
{
OrderId: "eeeeee",
OrderLines: [ ]
},
{
OrderId: "ffffff",
OrderLines: [ ]
}
]
}]
GraphQL calls the resolvers top-down:
- get all (filtered) clients
- for each of these get all (filtered) orders
- for each of those get all (filtered) order lines
Because of the top-down nature of calling the resolvers, I get a lot more data than I bargained for.
How should I approach this?
Relation filters
This is actually a more complex topic than it first seems. The problem is that your current filter condition expresses
get all customers, but only include items named 'gizmo'
but what you really want is
get all customers that are related to at least one item named 'gizmo'
get all customers that are related to at least one item named 'gizmo'
An elegant solution for this problem is the addition of relation filters to the schema. In your case, it could look like this:
query {
Customer(filter: {
orders_some: {
orderLines_some: {
item: {
itemName: "gizmo"
}
}
}
}) {
Name
Orders {
OrderId
Date
OrderLines {
ItemCount
ItemName
}
}
}
}
Using
orders_some: {
orderLines_some: {
item: {
itemName: "gizmo"
}
}
}
we only fetch customers that are indirectly related to an item named 'gizmo', exactly what we wanted.
Two more examples:
get all customers that are not related to any item named 'gizmo'
query {
Customer(filter: {
orders_none: {
orderLines_some: {
item: {
itemName: "gizmo"
}
}
}
}) {
Name
Orders {
OrderId
Date
OrderLines {
ItemCount
ItemName
}
}
}
}
get all customers where all their orders contain some order line with an item named 'gizmo'
query {
Customer(filter: {
orders_every: {
orderLines_some: {
item: {
itemName: "gizmo"
}
}
}
}) {
Name
Orders {
OrderId
Date
OrderLines {
ItemCount
ItemName
}
}
}
}
The every, some and none relation filters are an essential part of the Graphcool APIs - you can read more here.

Resources