GraphQL, filtering results from full text search in neo4j

Neo4j and neo4j-graphql-js is a really nice combo for creating a GraphQL-based backend and I particularly find the nested filtering parameter powerful. I was hoping to use this together with full-text indexing of nodes to build an app where you do keyword searches combined with various filtering selection. Unfortunately, this does not seem to be possible because filters are not supported yet for @cypher directive fields which is needed to utilize the full text-index.

Currently I use the following type:

type Query {
works (query: String):[Work] @cypher(
statement: "CALL db.index.fulltext.queryNodes('work', $query) yield node RETURN node"
)
}

and want to do queries like:

{
works(query: "Baskervilles"
filter:{
contributors_some:{name_contains: "Doyle"}
}){
title
contributors{
name
}
}
}

Any advice for workaround to solve this? or something that will be supported in the near future?
(Current idea for workaround is to rather rely on neo4j substring search using CONTAINS but this is somewhat awkward and not very efficient.)

1 Like

A lot of times the best thing to do is add the longer cypher query, so like as you said, the substring contains, variant. You can use the full power and functionality of cypher so if you can get it done with that it's the easiest route. Most of the built in filter methods, etc are for things like dates and numbers.

Stumbled upon the same limitation as @taalberg recently. I'm running the latest everything neo4j-graphql-js and Neo4j 4.0.0 but this functionality is still not there. Too bad.

This blog post seems to imply it's possible:

@vanbenj,

I don't see any filters being used in the article you're referring to. It's just traditional attribute selection when results are back. What we want to achieve is to use the keyword "filter" such as:

Podcast(podcastId: $podcastId, first: 1, filter: {title_ends_with: "something"}) {
    podcastId
    title
...

Does anyone know if this feature will ever see the light of day?

2 Likes

I'm trying to figure out the same thing. Intuitively, as a developer, you'd expect the return's _TypeFilter to be available as a param...but its not.

Under custom cypher based query functions, only pagination but not filtration is available out of the box:. IMHO, this makes zero sense, since we're building queries, filtration must be included pre-rolled for you, not just pagination.

We're hacking away trying to figure this out too

The post you shared refers to "filteration" 0 times...how therefore, does it imply its possible? :laughing:

If you have a code snippet, please do share :pray:

1 Like

I found a workaround for this. It'a a little fragile, but it works.

Create a custom query in the schema. Include filter input and a string input for your search string.

type Query {
  fuzzyItemByName(searchString: String, filter: _ItemFilter): [Item]
}

Create a custom resolver for the query

import { neo4jgraphql, cypherQuery } from 'neo4j-graphql-js';
import { extractQueryResult } from 'neo4j-graphql-js/dist/utils';

const Query = {
  async fuzzyItemByName(object, args, ctx, resolveInfo) {
    if (!args.searchString) {
      return neo4jgraphql(object, args, ctx, resolveInfo);
    }

    const fullTextQuery = `CALL db.index.fulltext.queryNodes('itemNameIndex', $searchString + '~') YIELD node AS item`;

    let [query, queryParams] = cypherQuery(args, ctx, resolveInfo);

    query = query.replace(
      'MATCH (`item`:`Item` {searchString:$searchString})',
      fullTextQuery
    );

    let session = ctx.driver.session();

    let result;

    try {
      result = await session.readTransaction((tx) => {
        return tx.run(query, queryParams);
      });
    } finally {
      session.close();
    }
    return extractQueryResult(result, resolveInfo.returnType);
  },
};

What I'm doing in the resolver is checking to see if the search string is in the query.

If it isn't then pass the query through to neo4jgraphql

But, if it is, then I do some find and replace of MATCH ... with CALL .... Yes this part is fragile, but hopefully it's helpful.

Then start a session and execute the query.

Use extractQueryResult to get the return payload in the correct format.

To use, run a query like this

  fuzzyItemByName(
    searchString: "t-shart"
    filter: { price_lt: 3000 }
  ) {
    name
    price
  }

:v: