How to write dynamic search query for Spring-Data-Neo4j - neo4j

I am implementing search on node attributes. Suppose I have a millions node of User with attributes name, location, income.
n:user (name:"abc",location:"xyz", income:"123")
n:user (name:"def",location:"ghj", income:"1877")
I want to search User either with name or location or income or the combination of attributes like name and location etc. I can perform this search with simple cypher query
#Query("MATCH(n:user{location:{xyz},}) RETURN n") or
#Query("MATCH(n:userr{location:{xyz},name:{abc}}) RETURN n")
But i don't want to do like that, i just want to pass the search parameter and create search query at runtime with the requested parameter. In neo4template class, got this method query(String statement, Map<String,Object> params) Now i am clueless, any detailed answer would be appreciated, as i am new to neo4j.

Unfortunately, parameter maps cannot be used in MATCH patterns.
So I believe you cannot use universal query like:
#Query("MATCH(n:user{0}) RETURN n")
public List<User> complexFind(Map<String, Object> props);
I'd suggest building query string dynamically, using string concatenation, and executing it later on with smth like that:
neo4template.query(YourQueryStringBuilder.build(search_params), other_params);

Related

Neo4j/Spring Data Neo4j 4 index and case of characters

I have a following SDN 4 node entity:
#NodeEntity
public class Product {
#Index(unique = false)
private String name;
...
}
Inside of this entity I have added name property and declared an index.
Right now I'm going to implement case insensitive search by product name.
I have created a SDN 4 repository method:
#Query("MATCH (p:Product) WHERE LOWER(d.name) = LOWER({name}) RETURN p")
Product findByName(#Param("name") String name);
In order to search a product I use following Cypher: LOWER(d.name) = LOWER({name})
I think the index doesn't effectively work in this case because I lowercase the strings.
What is a proper way in Neo4j/SDN 4 to make index working here ?
If you do not need to store the name in the original case, then convert the name to lowercase before storing it in the DB.
If you do need the name in the original case, then you could add an extra property (say, "lower_name") that stores the lowercased name. You can index that property and use it for indexed comparisons.
A third choice is to use legacy indexing, which is much more complex to use and no longer favored. However, it does support case-insensitive indexing (see the second example on this page).

Neo4j SDN 4 emulate sequence object(not UUID)

Is it possible in Neo4j or SDN4 to create/emulate something similar to a PostgreSQL sequence database object?
I need this thread safe functionality in order to be able to ask it for next, unique Long value. I'm going to use this value as a surrogate key for my entities.
UPDATED
I don't want to go with UUID because I have to expose these IDs within my web application url parameters and in case of UUID my urls look awful. I want to go with a plain Long values for IDs like StackOverflow does, for example:
stackoverflow.com/questions/42228501/neo4j-sdn-4-emulate-sequence-objectnot-uuid
This can be done with user procedures and functions. As an example:
package sequence;
import org.neo4j.procedure.*;
import java.util.concurrent.atomic.AtomicInteger;
public class Next {
private static AtomicInteger sequence = new AtomicInteger(0);
#UserFunction
public synchronized Number next() {
return sequence.incrementAndGet();
}
}
The problem of this example is that when the server is restarted the counter will be set to zero.
So it is necessary to keep the last value of the counter. This can be done using these examples:
https://maxdemarzi.com/2015/03/25/triggers-in-neo4j/
https://github.com/neo4j-contrib/neo4j-apoc-procedures/blob/master/src/main/java/apoc/trigger/Trigger.java
No. As far as I'm aware there isn't any similar functionality to sequences or auto increment identifiers in Neo4j. This question has also been asked a few times in the past.
The APOC project might be worth checking out for this though. There seems to be a request to add it.
If your main interest is in having a way to generate unique IDs, and you do not care if the unique IDs are strings, then you should consider using the APOC facilities for generating UUIDs.
There is an APOC function that generates a UUID, apoc.create.uuid. In older versions of APOC, this is a procedure that must be invoked using the CALL syntax. For example, to create and return a single Foo node with a new UUID:
CREATE (f:Foo {uuid: apoc.create.uuid()})
RETURN f;
There is also an APOC procedure, apoc.create.uuids(count), that generates a specified number of UUIDs. For example, to create and return 5 Foo nodes with new UUIDs:
CALL apoc.create.uuids(5) YIELD uuid
CREATE (f:Foo {uuid: uuid})
RETURN f;
The most simplest way in Neo4j is to disable ids reuse and use node Graph ID like sequencer.
https://neo4j.com/docs/operations-manual/current/reference/configuration-settings/
Table A.83. dbms.ids.reuse.types.override
Description: Specified names of id types (comma separated) that should be reused. Currently only 'node' and 'relationship' types are supported.
Valid values: dbms.ids.reuse.types.override is a list separated by "," where items are one of NODE, RELATIONSHIP
Default value: [RELATIONSHIP, NODE]

How to use parameter in the Cypher statement for variable length pattern matching?

In our project , PEPOLEs are connected by KNOWS relationships. We need to query one’s friends in depth n which n is a parameter inputed by user.We use Spring Data Neo4j to implement it.
public interface PeopleRepository extends GraphRepository<People>
{
#Query("MATCH (startnode{name:{name}})-[:KNOWS*1..{depth}]-(remote_friend) RETURN remote_friend.name");
List<People> getFriendsInDepth(#Param("name") String name, #Param("depth") Integer depth);
}
The above codes won’t work. But if I replace {depth} parameter with a fixed Integer value as follows:
#Query("MATCH (startnode{name:{name}})-[:KNOWS*1..2]-(remote_friend) RETURN remote_friend.name");
List<People> getFriendsInDepth(#Param("name") String name, #Param("depth") Integer depth);
it works. I know the problem is caused by the depth parameter. But I have tried a lot of methods to replace {depth}, for example: toInt({depth}), it still won’t work. Is there anyone know how to use the parameter in the Cypher statement for variable length pattern matching?
Cypher does not allow you to parameterize the depth of a relationship, hence #Query won't support it either.
If you use Spring Data Neo4j 4, then perhaps you can translate your #Query to a set of org.neo4j.ogm.cypher.Filter.
Then you can use the Session.loadAll methods which accept Filters as well as a depth.
MusicIntegrationTest contains a couple of Filter examples.

How do I use spring-data-neo4j with spatial indexes and cypher?

I want to use a spatial index in Neo4j with the spring-data-neo4j framework. Additionally I want to query the index using cypher. The database is embedded.
I'm a bit clueless as to how to get it to all wire together.
With a domain object like this,
#NodeEntity
class Junction {
#GraphId Long id;
#Indexed(indexType = IndexType.POINT, indexName = "junctionLocations") Point wkt;
}
SDN ought to be maintaining the index for me. It appears so, as I can do spatial queries using the repository:
interface JunctionGraph extends GraphRepository<Junction>, SpatialRepository<Junction> {}
with
junctionGraph.findWithinBoundingBox("junctionLocations", new Box(lowerBound.point, upperBound.point))
However, I understand that to query this index using cypher (via a #Query in the repository) this spatial index configuration won't work. I think that this is because each node needs to be manually added to the spatial index (or at least a proxy for the node). That means that adding this to JunctionGraph:
#Query("START n=node:junctionLocations('withinDistance:[{0}, {1}, {2}]') MATCH n-[*]->(i:Item) return i")
Collection<Item> getItemsWithin(double lat, double lon, double radius)
doesn't work.
Does anyone have a working recipe? It appears to be a bit black magic to me, and I'm unsure what the best way to proceed within SDN is.
It works, you just have to create the whole query string outside and pass it as parameter, placeholders within string constants are not substituted.
#Query("START n=node:junctionLocations({0}) MATCH n-[*]->(i:Item) return i")
Collection<Item> getItemsWithin(String query)
you have to do the replacement yourself, .e.g. using String.format
String.format("withinDistance:[%f, %f, %f]",lat,lon,radius)

spring data neo4j #Query countQuery

The annotation #Query in package org.springframework.data.neo4j.annotation provides certain count attributes like countQuery, countQueryName.
/**
* #return simpler count-query to be executed for #{see Pageable}-support {self} will be provided by the node-id of the current entity other parameters (e.g. {name}) by the given named params
*/
String countQuery() default "";
/**
* #return name of the named count query to be used for this annotated method, instead of Class.method.count
*/
String countQueryName() default "";
Can somebody please explain the usage of these? To be more specific I've written a query to fetch the posts on a topic. The query results will be paginated. The query below works fine and gives me the result.
#Query("start post=node:__types__(className='com.xxx.entity.Post'), topic=node({0}) match post-[:TOPIC_POST]-topic return post")
Page<Post> getPostsByTopic(Topic topic, Pageable page);
Now I also want the total number of results as well, do I have to write another query for this or is there a way to accommodate count query in this?
I think countQuery should work for you, but you still need to write the count query
If it is required that paged results return the correct total count,
the #Query annotation can be supplied with a count query in the
countQuery attribute. This query is executed separately after the
result query and its result is used to populate the totalCount
property of the returned Page.
http://docs.spring.io/spring-data/neo4j/docs/current/reference/htmlsingle/#d0e2712

Resources