Consider the Neo4J 2.0 Cypher query
MERGE (u:User {id_str:"123"})
ON CREATE
SET {giant_params_string_from_twitter_api}
ON MATCH
SET u.lastSeen = timestamp()
RETURN u
Here I've downloaded the user's metadata from Twitter, and if the user doesn't exist, then I insert all of his metadata. If the user already exists, then I just modify his timestamp.
The call out to Twitter API needed to retrieve the params is long and expensive (especially when you consider that I keep getting rate limited). And a lot of time the node already exists in the database. Here's what would rather do:
MERGE (u:User {id_str:"123"})
ON CREATE
SET get_twitter_params("123")
ON MATCH
SET u.lastSeen = timestamp()
RETURN u
In ON CREATE I would like to somehow link back out to a callback to pull down this data.
Is there any way to call create my own function to be used in Cypher?
Not yet! They're considering ways of implementing user defined functions (UDFs), though, so I don't think it will be too far out.
You might consider checking for existence before making your request to the twitter, if that is the expensive call--unfortunately you'd have to do that outside of your single Cypher request.
As of Neo4j 3.0, you can now write your own functions. They are however written in Java.
Look into this link for more details: https://neo4j.com/developer/procedures-functions/
Related
I'm going to try in my application Neo4j Manual Index on Relationship Properties in order to fix the performance issue I faced Neo4j Cypher query performance optimization
I have a few question which is not clear to me from the official Neo4j documentation:
MATCH (:Flight)-[r:DESTINATION]->(:Airport)
CALL apoc.index.addRelationship(r,['taxi_time'])
RETURN count(*)
The statement will create the relationship index with the same name as
relationship-type, in this case DESTINATION and add the relationship
by its properties to the index.
When do I need to create this relationship index? It should be done once(let's say at the application startup) or do I need to invoke this APOC function each time new -[r:DESTINATION]-> relationship is added between Flight and Airport nodes?
In case of existing -[r:DESTINATION]-> relationship update, how to update this information in the appropriate manual index?
In case of deleting some of Flight or Airport node - do I need to manually find and remove appropriate -[r:DESTINATION]-> relationships from the manual index or it will be done automatically by APOC and Neo4j?
In case of Spring Data Neo4j project - how to properly execute queries that contain APOC functions? For example, I want to call apoc.index.addRelationship in order to create the manual index for the relationship properties. Can I use org.neo4j.ogm.session.Session.query for this purpose?
What the consistency model is used for the manual indexes - Do they use eventual consistency or strong consistency model between the index and the original data?
I agree Neo4J documentation on this issue is really insufficient.
To answer your questions:
1.If you upgraded your Neo4J from an older version that used automatic relationship index you would need to run APOC (only once) to index your existing relationships using something like
MATCH ()-[r]->() CALL apoc.index.addRelationship(r,['property_1','property_2']) RETURN count(*);
You would then need to set up a trigger to any new relationship you add to that index, running something like this once:
CALL apoc.trigger.add('RELATIONSHIP_INDEX',"UNWIND {createdRelationships} AS r MATCH ()-[r]->() CALL apoc.index.addRelationship(r,['property_1','property_2']) RETURN count(*)", {phase:'after'})
(you will need to activate apoc.trigger.enabled=true in neo4j.conf file before)
2.See above
3.You would need to remove them also from the index, it is not done automatically. Set up an APOC trigger with removeRelationshipByName() for that.
4.Should be possible.
5.Somebody from Neo4J should answer this.
Hope this helps and saves you some time!
I am working on an application where Neo4j data is being updated for multiple clients in realtime. The times on the different client machines may be different, so any timestamps need to be set by the Neo4j database itself.
What possibilities does the Neo4j REST API provide for running TIMESTAMP() locally and inserting it into a field?
I know that I can create a query like...
WITH TIMESTAMP() AS timestamp
MATCH (node)
WHERE id(node) = {id}
SET node.updatedAt = timestamp
, node.property = "new value"
RETURN node
... and use a transaction to execute it on the remote server. But is this the only solution? Are there ways to use the Node Properties URI so that the database will create the appropriate values dynamically?
Neo4j REST API is very limited in this case. As you mentioned you can use Cypher REST endpoint and set that value manually.
Another option is to writer your own Neo4j Unmanaged Extension, which will use Transaction Event API. It's similar to stored-procedures.
Here is very good article how to write that Extension by using GraphAware Framework - http://graphaware.com/neo4j/transactions/2014/07/11/neo4j-transaction-event-api.html
Also here is simple real-world example that kind of Extension - https://github.com/graphaware/neo4j-uuid
Is there way to set a transient property on nodes returned by a cypher query such that it is only visible to the user running the query.
This would allow us offload some controller logic directly into Neo4j and reduce business logic queries.
Currently I have a list that is returned by
List<Post> newsFeed (Long uid) {}
Post is a relationship between a User and News node.
I have two sub-classes of the Post object:
BroadcastedPost
MentionedPost
I have two cypher queries that return the posts that a user should see.
List broadcasts obtained from
MATCH (user:PlatformUser)-[:BROADCASTED]->post RETURN post;
List mentionedPost obtained from
MATCH (user:PlatformUser)-[:MENTIONED]->post RETURN post;
I then use Java instanceof to determine what kind of post this is. Depending on the type I am able to do some further application logic.
This however is inefficient because I should be able to combine both queries into one super query using the UNION operator
i.e List newsFeed is obtained directly by querying
MATCH (user:PlatformUser)-[:BROADCASTED]->post RETURN post UNION MATCH (user:PlatformUser)-[:MENTIONED]->post RETURN post;
However, how can I tell what kind of post this. I was hoping I could use the SET operator transiently to know which kind of post this is but I believe this is used to persist a property.
Neo4j 2.2 recently added authentication, which it had lacked in previous releases, but it's still only really one user; you set a login/password to secure access to the database, but adding additional users takes extra work and isn't something obvious to do out of the box.
Now what you're asking for has to do with securing per-user access to particular types of data. Since neo4j doesn't have much of a user management feature right now, what you're asking for can't be done inside of neo4j because in order to secure this data away from Joe or Bob, the DBMS would have to know that it's dealing with Joe or Bob.
What you're trying to do is usually enforced by the application layer by people writing neo4j applications right now. So it can be done, but it's done within your custom code and not by the database directly.
seems I am too tired to find the solution, maybe someone has a hint for me.
I have built a graph in Neo4J which I connect via neo4jphp (everyman). Using the java browser the graph looks ok, every user exists one time and might have several groups he belongs to.
While creating the user I use MERGE in order to avoid them to be doubled - like
MERGE (user:PERSON {
firstname: "Max",
name: "Muster",
password:"e52ddddddd9afb7b373f9da437",
title:"something",
login:"Nick",
status:"active"
})
ON CREATE SET user.uuid = "'.uniqid().'" // PHP function for a UUID
return user;
This works well as I see the correct number of users even when I resend the query or reload the page.
The users are connected with a query towards groups like this
MATCH (user:PERSON), (team:GROUP)
WHERE user.name= "Muster" AND user.firstname="Max" AND team.name="LOCAL_USER"
CREATE (user)-[:IS_MEMBER_OF {role:"user", status:"active"}]->(team);
Checking this in the GUI of Neo4J shows a correct graph (at least from what I can see). I have the right amount of users and their relations.
When I query the graph directly by Cypher in the browser GUI like this
MATCH (user:PERSON {status: "active"})-[relation:IS_MEMBER_OF{status:"active"}]->(team:GROUP {name:"LOCAL_USER"} )
RETURN user
ORDER BY user.name;
I get the correct number of users.
When I use the neo4jphp lib (everyman) I receive some users double - the resultset has several elements with the same user. I couldnt figure out why they behave differently but I assume that I might have messed up the relations somehow. But still I am wondering why the same cypher query returns different amount of records when you send it via GUI or via everyman lib and I would need a hint how to change maybe the queries to make sure that I only get one record per user as every user is only one time connected to the LOCAL_USER group.
Thanks for pushing me to the right direction.
I think you get doubles because you have multiple paths (e.g. multiple teams) for the user,
use RETURN distinct user
For your import statement, you do it the wrong way round, instead of your approach.
MERGE by unique id (e.g login in your case) and set the other properties with ON CREATE SET ...
Also use parameters not literal values in your query strings !!
MERGE (user:PERSON {login:{login}})
ON CREATE SET
user.firstname = {firstname}, user.name= {name}, user.password = {password},
user.title={title}, user.status = {status}, user.uuid = {uuid}
RETURN user;
I was wondering whether it is possible to create a relationship or return fail if it exists through a Cypher query via REST. Besides, I do not want to create any kind of index.
This is my use case: User can like a comment only once. So I want to create the relationship (User)-[:LIKES]->(Comment) or return fail if it exists, using a Cypher query via REST.
My approach is to use CREATE UNIQUE and RETURN some kind of code that I will interpret in my back-end to know if I have to send 409 Conflict to the back-end's client. But this approach seems messy...
Any idea? Thanks.
If you are willing to put a property in your LIKES relationship you could do something like this.
WITH timestamp() AS now
MERGE (user)-[like:LIKES]->(comment)
ON CREATE SET like.created_at = timestamp()
RETURN like.created_at >= now
If the query returns true you know the like was created otherwise it existed previously and you can handle it accordingly.