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;
Related
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.
I am following this article to setup a system where users can follow each other and also become friends : http://neo4j.com/docs/stable/cypher-cookbook-newsfeed.html
A user (A) can friend another user (B) and hence A is automatically following B. User A can also follow B without adding B as a friend. Hence there should be a distinction made to the feed results. If A and B are not confirmed friends, A should get status updates from B that are marked public only. If A is a confirmed friend of B, A should get all status updates from B. A, even if is B's friend, can also unfollow his/her feed. (typical facebook model?). So basically I need to check who A follows and grab their updates. However, while doing this I also need to check if A has access to these status updates.
Is there an easy cypher to implement this? Or do you have a better model in mind? Assuming all updates are public following query should work. How would you add privacy setting dimension to it if there are friends only posts too?
MATCH (me { name: 'Joe' })-[rels:FOLLOWS*0..1]-(anotherUser)
WITH anotherUser
MATCH (anotherUser)-[:STATUS]-(latestupdate)-[:NEXT*0..1]-(statusupdates)
RETURN anotherUser.name AS name, statusupdates.date AS date, statusupdates.text AS text
ORDER BY statusupdates.date DESC LIMIT 3
Yes, you can implement all of these requirements, it seems to me to boil down to a few extra carefully chosen WHERE clauses.
Here's your base query, with modifications:
MATCH (me { name: 'Joe' })-[rels:FOLLOWS*0..1]-(anotherUser)
WITH anotherUser
MATCH (anotherUser)-[:STATUS]-(latestupdate)-[:NEXT*0..1]-(statusupdates)
WHERE statusupdates.visibility='PUBLIC'
RETURN anotherUser.name AS name, statusupdates.date AS date, statusupdates.text AS text
ORDER BY statusupdates.date DESC LIMIT 3
Here, I've just added a WHERE to check for visibility=PUBLIC (which I made up, because the sample app doesn't specify those things; that would have to be part of your model one way or another).
You might consider doing that query along with a UNION to another query, which would be intended to fetch only those status updates from friends. (If it's a friend, then it doesn't matter what the visibility is)
MATCH (me { name: 'Joe' })-[:FRIEND]-(friend)-[:STATUS|NEXT*1..]->(statusupdates)
RETURN statusupdates
ORDER BY statusupdates.date DESC LIMIT 3;
Instead of using UNION you could also combine the two queries with an OPTIONAL MATCH clause on the second pattern. But either way, basically, your query needs to get the list of all status updates that are either people you follow whose posts are public, friends posts, or both. So conceptually it's easy to break that into those two separate cases, and then UNION the two result sets together.
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.
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/
I'm trying to wrap my head around how Neo4j works and how I can apply it to my problem. I thought it should be really easy and a matter of minutes, but I'm stuck.
I have data in MongoDB, say User and Item. What I want is connecting User and Item in a graph with a LIKE relationship (maybe with a score). Later I want to do things like recommending items based on connections, basic stuff.
But how do I get the data into Neo4j? Every document in MongoDB has an unique _id, so I though I could just throw both _ids into Neo4j and have them connected. What I found so far is that it's not even possible to have unique nodes based on the _id field (Neo4j has numeric incremented ids), which is only possible with some "hack" (https://github.com/jexp/app-net-graph/blob/master/lib/appnet.rb#L11) or using MERGE (I'm stuck on < 2.0). Even their examples on the website add the same node again if executed multiple times. I think I have a fundamental misunderstanding of how to use Neo4j. Maybe I'm too spoiled by redis, where I can put strings in and and it just works. Redis' sets aren't feasible though for complex graphs, only for simple connections.
Maybe someone can help me with a simple cypher example of how to add two nodes foo and bar and have them connected with a LIKE connection. And the operation should be idempotent, no matter if none or all of the nodes/relationships already existed before execution.
I'm accessing Neo4j via REST, in particular using this node module https://github.com/thingdom/node-neo4j
You could define your external ID as extra property on your nodes. Then depending on if your are using SpringData or not, you can insert the data.
If you are using SpringData, you can configure your external ID as unique index and then normally save you nodes(consider though, that inserting a duplicated ID will overwrite the existing one).
If you are using the plain java API, you can create unique nodes as described here:
http://docs.neo4j.org/chunked/stable/tutorials-java-embedded-unique-nodes.html#tutorials-java-embedded-unique-get-or-create
EDIT:
As for a sample query, does this help you?
http://console.neo4j.org/?id=b0z486
With the java api you would do it like this
firstNode = graphDb.createNode();
firstNode.setProperty( "externalID", "1" );
firstNode.setProperty( "name", "foo" );
secondNode = graphDb.createNode();
secondNode.setProperty( "externalID", "2" );
secondNode.setProperty( "name", "bar" );
relationship = firstNode.createRelationshipTo( secondNode, RelTypes.Likes );
I suggest you read some tutorials here: http://docs.neo4j.org/chunked/stable/tutorials-java-embedded-hello-world.html
Given you are using Neo4J1.9, have you tried creating a unique index on your _ID column?
Try this article from the docs
If you were using Neo4j2, then this article is helpful