Neo4j/Spring Data Neo4j 4 index and case of characters - neo4j

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).

Related

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]

Searching for Parse objects where an object's array property contains a specified string

I have a PFObject subclass which stores an array of strings as one of its properties. I would like to query for all objects of this class where one or more of these strings start with a provided substring.
An example might help:
I have a Person class which stores a firstName and lastName. I would like to submit a PFQuery that searches for Person objects that match on name. Specifically, a person should be be considered a match if if any ‘component’ of either the first or last name start with the provided search term.
For example, the name "Mary Beth Smith-Jones" should be considered a match for beth and bet, but not eth.
To assist with this, I have a beforeSave trigger for the Person class that breaks down the person's first and last names into separate components (and also lowercases them). This means that my "Mary Beth Smith-Jones" record looks like this:
firstName: “Mary Beth”
lastName: “Smith-Jones”
searchTerms: [“mary”, “beth”, “smith”, “jones”]
The closest I can get is to use whereKey:EqualTo which will actually return matches when run against an array:
let query = Person.query()
query?.whereKey(“searchTerms”, equalTo: “beth”)
query?.findObjectsInBackgroundWithBlock({ (places, error) -> Void in
//Mary Beth is retuned successfully
})
However, this only matches on full string equality; query?.whereKey(“searchTerms”, equalTo: “bet”) does not return the record in question.
I suppose I could explode the names and store all possible sequential components as search terms (b,e,t,h,be,et,th,bet,etc,beth, etc) but that is far from scalable.
Any suggestions for pulling these records from Parse? I am open to changing my approach if necessary.
Have you tried whereKey:hasPrefix: for this? I am not sure if this can be used on array values.
https://parse.com/docs/ios/guide#queries-queries-on-string-values

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 to write dynamic search query for Spring-Data-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);

Adding index on neo4j node property value

I have imported freebase dump to neo4j. But currently i am facing issue with get queries because of size of db. While import i just created node index and indexed URI property to index for each node. For each node i am adding multiple properties like label_en, type_content_type_en.
props.put(URI_PROPERTY, subject.stringValue());
Long subjectNode = db.createNode(props);
tmpIndex.put(subject.stringValue(), subjectNode);
nodeIndex.add(subjectNode, props);
Now my cypher queries are like this. Which are timing out. I am unable to add index on label_en property. Can anybody help?
match (n)-[r*0..1]->(a) where n.label_en=~'Hibernate.*' return n, a
Update
BatchInserter db = BatchInserters.inserter("ttl.db", config);
BatchInserterIndexProvider indexProvider = new LuceneBatchInserterIndexProvider(db);
BatchInserterIndex index = indexProvider.nodeIndex("ttlIndex", MapUtil.stringMap("type", "exact"));
Question: When i have added node in nodeindex i have added with property URI
props.put(URI_PROPERTY, subject.stringValue());
Long subjectNode = db.createNode(props);
nodeIndex.add(subjectNode, props);
Later in code i have added another property to node(Named as label_en). But I have not added or updated nodeindex. So as per my understanding lucene does not have label_en property indexed. My graph is already built so i am trying to add index on label_en property of my node because my query is on label_en.
Your code sample is missing how you created your index. But I'm pretty sure what you're doing is using a legacy index, which is based on Apache Lucene.
Your Cypher query is using the regex operator =~. That's not how you use a legacy index; this seems to be forcing cypher to ignore the legacy index, and have the java layer run that regex on every possible value of the label_en property.
Instead, with Cypher you should use a START clause and use the legacy indexing query language.
For you, that would look something like this:
START n=node:my_index_name("label_en:Hibernate.*")
MATCH (n)-[r*0..1]->(a)
RETURN n, a;
Notice the string label_en:Hibernate.* - that's a Lucene query string that says to check that property name for that particular string. Cypher/neo4j is not interpreting that; it's passing it through to Lucene.
Your code didn't provide the name of your index. You'll have to change my_index_name above to whatever you named it when you created the legacy index.

Resources