How to make node changes in a TransactionEventHandler that are returned within the same CREATE query - neo4j

I am trying to implement a plugin for neo4j to add an autoincrement ID using GraphAware library. To this end, I've written the following classes:
public class ModuleBootstrapper implements RuntimeModuleBootstrapper
{
public RuntimeModule bootstrapModule(String moduleId, Map<String, String> config, GraphDatabaseService database)
{
return new MyModule(moduleId, config, database);
}
}
And:
public class MyModule extends BaseTxDrivenModule<Void>
{
int counter = 0;
public Void beforeCommit(ImprovedTransactionData transactionData)
throws DeliberateTransactionRollbackException
{
if (transactionData.mutationsOccurred()) {
for (Node newNode : transactionData.getAllCreatedNodes()) {
newNode.setProperty("id", counter++);
}
}
}
}
And for the testing I can execute:
CREATE (n);
And then:
MATCH (n) RETURN n;
And I can see the effect of my plugin as some id property added to the node. But when I run:
CREATE (n) RETURN n;
The returned node does not have the mentioned id property but again, when I match the node in a separate query, I see the things have worked out just fine. It's just that in the CREATE query, the returned node infos are the ones before my plugin modified them.
The questions are; why is that? Didn't I modify the nodes through my plugin within the transaction? Shouldn't the returned nodes be showing the modifications I've made on them? Is there any way I can make this happen?

While you're still within the transaction, the Cypher result is already computed and there is no clean way to add additional informations to it.
I guess a feature request on the neo4j repository could be cool but in total honesty this would require a serious change into the neo4j core codebase.
BTW, the incremental ID is already implemented in the graphaware-uuid plugin : https://github.com/graphaware/neo4j-uuid#specifying-the-generator-through-configuration

Related

I want to add a Relationship between two already existing nodes in Neo4j - how to do that using Spring Data Neo4jRepository?

The cypher query to perform the above task would be as follows:
MATCH (a:Buyer), (b:Seller)
MERGE (a) -[:BUY {quantity: 150}]-> (b);
I want the equivalent Neo4jRepository function or some equivalent code that can serve the same above purpose. Please post the answer if you know some solution.
[Updates]
I have posted an answer below. But I am also expecting some other kind of solutions to this purpose. Please feel free to post alternative solutions as answers.
One Possible Way
If we have two Nodes/Entities as Buyer and Stock as POJO classes in SpringBoot Code, and if we are trying to add a relationship called [:HAS] between two such nodes then we can do the following.
#Node("Stock")
class Stock
{
#Id #GeneratedValue(...)
private Long id;
/* Other attributes here --- */
}
#Node("Buyer")
class Buyer
{
#Id #GeneratedValue(...)
private Long id;
/* Other Attribute Variables Here --- */
#Relationship(type="HAS")
private List<Stock> stockList;
public List<Stock> getStockList()
{
return stockList;
}
public void setStockList(List stockList)
{
this.stockList = stockList;
}
}
So we can do this to create the desired relationship.
buyer.getStockList().add(newStock);
buyerRepo.save(buyer);
Here buyerRepo is the object of a Repository that implements Neo4jRepository<Buyer, Long>

Unwinding and merging the results with dynamic labels in Neo4J Client for C#

I am currently trying to unwind a list of objects that I want to merge to the database using the Neo4J Client. What I would like to do is unwind the list and create the nodes with a label generated based on a property from the items themselves instead of hardcoding a label name. From what I can find I have to use the APOC merge method to do so. However, I am unable to translate this to the Neo4J client. In the neo4J explanation they yield a node after the apoc.merge.node call and then return the node. However, I cannot simply return the node nor can I set the node (I got to the point of just messing about, and at one point I got the labels to work but it overwrote all properties with the last item in the list).
I seem to miss something fundamental but i'm not quite sure what. Does anyone here know how to do this with neo4J client (and if possible, give a bit of an explanation what is going on)? I am very new to the development world and I feel I am just missing a crucial piece of understanding when it comes to this..
The code that I tried that turned all properties into the last node's properties but at least created the labels as I expected:
public async void CreateBatchItems(List<TToDataBase> itemList)
{
await Client.Cypher
.Unwind(itemList, "row")
.Merge("(n)")
.With("row, n")
.Call("apoc.merge.node([n.Name], n)").Yield("node")
.Set("n += node")
.ExecuteWithoutResultsAsync();
}
Thank you in advance!
Edit:
Some clarification about the input:
The objects are actually very basic, as (at least for now), they merely contain a name and an objectID (and these object ID's are later used to create relations). So its a very basic class with two properties:
public class Neo4JBaseClass
{
public Neo4JBaseClass() { }
public Neo4JBaseClass(string name, string objectId)
{
Name = name;
ObjectId = objectId;
}
[JsonProperty(PropertyName = "ObjectId")]
public string ObjectId { get; set; }
[JsonProperty(PropertyName = "Name")]
public string Name { get; set; }
}
I have also tried a slight variation where this class also has the added property
[JsonProperty(PropertyName = "PropertyMap")]
public IProperty PropertyMap { get; set; }
where PropertyMap is another basic object holding the name and objectId. This seemed like a good idea for future proofing anyway, so the propertylist can be easily expanded without having to change the base object.
[EDITED]
The main issue is that Merge("(n)") matches any arbitrary node that already exists.
You have not shown the data structure for each element of itemList, so this answer will assume it looks like this:
{Name: 'SomeLabel', id: 123, Props: {foo: 'xyz', bar: true}}
With above data structure, this should work:
public async void CreateBatchItems(List<TToDataBase> itemList)
{
await Client.Cypher
.Unwind(itemList, "row")
.Call("apoc.merge.node([row.ObjectId], row.id)").Yield("node")
.Set("node += row.Props")
.ExecuteWithoutResultsAsync();
}
[UPDATE]
The data structure you added to your question is very different than what I had imagined. Since neither of the properties in a row is a map, .Set("node += row.Props") would generate an error.
Using your data structure for each row, this might work:
public async void CreateBatchItems(List<TToDataBase> itemList)
{
await Client.Cypher
.Unwind(itemList, "row")
.Merge("(n:Foo {id: row.ObjectId})")
.Set("n += row.Name")
.ExecuteWithoutResultsAsync();
}
This code assigns the node label Foo to all the generated nodes. A node should always have a label, which improves clarity and also tends to improve efficiency -- especially if you also create indexes. For example, an index on :Foo(id) would make the above query more efficient.
This code also assumes that the id property is supposed to contain a unique Foo node identifier.

How do we generate a cypher query by using Java and APOC for Neo4j?

I am trying to create my own procedure in Java in order to use it for Neo4j.I wanted to know how we can execute Cypher code in Java ?
I tried to use graphDB.execute() function but it doesn't work.
I just want to execute a basic code in Java by using Neo4j libraries.
Example of a basic code I want to execute:
[EDIT]
public class Test
{
#Context public GraphDatabaseService graphDb;
#UserFunction
public Result test() {
Result result = graphDb.execute("MATCH (n:Actor)\n" +
"RETURN n.name AS name\n" +
"UNION ALL MATCH (n:Movie)\n" +
"RETURN n.title AS name", new HashMap<String, Object>());
return result;
}
}
If you want to display nodes (as in the graphical result view in the browser), then you have to return the nodes themselves (and/or relationships and/or paths), not the properties alone (names and titles). You'll also need this to be a procedure, not a function. Procedures can yield streams of nodes, functions can only return single values.
Change this to a procedure, and change your return type to be something like Stream<NodeResult> where NodeResult is a POJO that has a public Node field.
You'll need to change your return accordingly.

How to correctly address "can't add a _parent field that points to an already existing type, that isn't already a parent" on app startup

I'm running into a problem with my Elasticsearch Document index creation failing on startup with "java.lang.IllegalArgumentException: can't add a _parent field that points to an already existing type, that isn't already a parent". I'm not sure if this is due to a version upgrade or b/c I am starting with a brand new Elasticsearch server install.
Contrived example that shows what I'm seeing:
// UserSearchResult.java
#Document(indexName = "hr_index", type = "user")
public class UserSearchResult implements Serializable {
...
#Field(type=FieldType.keyword)
#Parent(type="department")
private String departmentCode;
...
}
// DepartmentSearchResult.java
#Document(indexName = "hr_index", type = "department")
public class DepartmentSearchResult implements Serializable {
...
}
When I start my application I get that exception. If I check the ElasticSearch server, I see the "hr_index" index and the "department" mapping, but the "user" mapping is not created.
If I understand the error, it's because "department" is being created and then when Spring tries to create "user" with "department" as its parent, it doesn't like that, since department wasn't previously marked as a parent when it was created.
Is there some way (via annotation?) to denote DepartmentSearchResult as being a parent when it's created somehow?
Or, is it possible to give a hint to Spring Data Elasticsearch as to what order it should create the indices/mappings? I have seen some other posts (Spring Data Elasticsearch Parent/Child Document Repositories / Test execution error) but disabling auto creation and then manually creating it myself (either as part of my Spring codebase or external to the app) seems kind of "un-Spring-y" to me?
Or, is there some other approach I should be taking?
(This is a working Spring application that had been using Spring 4.2.1 and Spring Data Release Train Gosling, that I'm attempting to upgrade to use Spring 5.0.0 and Spring Data Release Train Kay. As part of this I am starting with a fresh Elasticsearch install, and so I'm not sure if this error is coming from the upgrade or just b/c the install is clean).
In the SD ES, issues related to the parent-child relationship at now really poorly developed.
The problem is most likely due to the fact that you are using a clean installation of Elasticsearch. Before the update, the problem did not arise, because mappings have already been created. For the solution, you can use elasticsearchTemplate, which is part of SD ES, and ApplicationListener. It's simple. Just 3 steps.
Drop index in ES (it only needs one time):
curl -XDELETE [ES_IP]:9200/hr_index
Tell SD ES not to create indices and mappings automatically
// UserSearchResult.java
#Document(indexName = "hr_index", type = "user", createIndex = false)
public class UserSearchResult implements Serializable {
...
#Field(type=FieldType.keyword)
#Parent(type="department")
private String departmentCode;
...
}
// DepartmentSearchResult.java
#Document(indexName = "hr_index", type = "department", createIndex = false)
public class DepartmentSearchResult implements Serializable {
...
}
Add a ApplicationListener:
#Component
public class ApplicationStartupListener implements ApplicationListener<ContextRefreshedEvent> {
#Autowired
private ElasticsearchTemplate elasticsearchTemplate;
//Mapping for child must be created only if mapping for parents doesn't exist
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
elasticsearchTemplate.createIndex(DepartmentSearchResult.class);
try {
elasticsearchTemplate.getMapping(DepartmentSearchResult.class);
} catch (ElasticsearchException e) {
elasticsearchTemplate.putMapping(UserSearchResult.class);
elasticsearchTemplate.putMapping(DepartmentSearchResult.class);
}
}
}
P.S. Among other things, it is worth paying attention to the fact that with the release of ES 5.6, a process for removing types began. This inevitably entails the removal of the parent-child relationship. In one of the next releases of the SD ES, we will provide the opportunity to work with joins. Working with parent-child relationships is unlikely to be improved

How can I find the Neo4J Index Key's for Indexes created on nodes using Spring Data?

I have created nodes using Spring using the following basic process (see below). I have a POJO for my Concepts and using this object and a Neo4J template to create nodes with indexes. I am still unable to discover what the 'KEY' for the created index is. I know the name of the index is 'CID' but assumed the 'KEY' would be 'conceptId'. However, when I use the following query (see below), no data is returned. It is confirmed that the Index does exist, but I am unable to find out what the proper 'KEY' for said index is so I can utilize it to improve query performance. I am able to query a specified node using WHERE clause searching for a specific value for a property of said node. However, when I try to find the node using the Index 'CID' with Key 'conceptId' no nodes are returned.
// Concept POJO
#NodeEntity
public class Concept {
#GraphId
private Long nodeId;
#Indexed(indexName="CID", fieldName="conceptId")
private Long conceptId;
...
// service where code to create Concept Nodes exists
#Repository
public class ConceptService {
#Autowired
private Neo4jTemplate n4jTemplate;
#Autowired
private ConceptRepository cr;
// Call to create node in a 'service'
public void addConceptNode(Concept concept) {
concept = n4jTemplate.save(concept);
}
...
//Cypher Queries used to retrieve nodes using index
START a=node:CID( conceptId = "66573009")
RETURN a;
// this returns 0 nodes quickly
START a=node:CID( conceptid = "66573009")
RETURN a;
// this returns 0 nodes quickly
START a=node:CID( CID = "66573009")
RETURN a;
// this returns 0 nodes quickly
START a=node:CID( cid = "66573009")
RETURN a;
// this returns 0 nodes quickly
START a=node:CID( CONCEPTID = "66573009")
RETURN a;
// this returns 0 nodes quickly
// Cypher query not using index to retrieve same node
START a=node(*)
WHERE HAS(a.conceptId) AND a.conceptId = 66573009
RETURN a;
// this returns 1 node in 77365ms
//'quickly' = approx.(43-87ms).
There is more to the code than what is shown, but this gives you the basic gist of how I am creating nodes with indexes in a Neo4J DB. There are more properties and more indexes. When using Spring to retrieve the nodes it seems to 'auto' use (I am assuming it is using the index) the index created because it returns the results faster than using the Neo4J data browser.
Any help would be greatly appreciated.
Thanks!

Resources