[Neo4j] REST, Transactions and Uniqueness

Linan Wang tali.wang at gmail.com
Wed Sep 28 22:02:35 CEST 2011


Peter,
I feel uniqueness has been a recurring theme in neo4j applications,
especially when it's used heavily on algorithms traversing existing
data. it would be great if it's supported in kernel level:

interface NodeUniquenessConstraint
{
   public Node getNode();
   public void setupNode(Node newNode);
}

public Node getOrCreateNode(NodeUniquenessConstraint constraint)
{
  // Preparation, acquires lock, etc.
  Node n = constraint.getNode();
  if(n == null)
  {
     n = createNode();
     constraint.setupNode(n);
  }
  ...
  return n;
}

or, there is something similar already implemented?

On Tue, Sep 27, 2011 at 9:38 PM, Peter Neubauer
<peter.neubauer at neotechnology.com> wrote:
> Guys,
> Maps are now supported as parameters, look at the Gremlin plugin för
> reference in the docs. Will add that for parameters to the cypher plugin
> too.
>
> Thanks for chipping in!
>
> /peter
>
> Sent from my phone.
> On Sep 27, 2011 8:12 PM, "Tony Wooster" <twooster at gmail.com> wrote:
>> Hi Linan,
>>
>> That's essentially what I implemented, but the logic just became that
>> much more tortured when going over REST. Like I said, less of a Java
>> programmer. The implementation I came up with on the REST side (hacky
>> though it may be) was this:
>>
>> @Description( "An extension to help maintain unique relationships" )
>> public class IndexTester extends ServerPlugin
>> {
>>     @Name( "error_if_in_node_index" )
>>     @Description( "Will return a 4xx error if a key/value pair is found in
> "+
>>                   "a given index. Also errors if the index doesn't
> exist.")
>>     @PluginTarget( GraphDatabaseService.class )
>>     public Boolean errorIfInNodeIndex(
>>                 @Source GraphDatabaseService graphDb,
>>                 @Description( "Name of the index to earch." )
>>                     @Parameter( name = "indexName" ) String indexName,
>>                 @Description( "Name of key to search." )
>>                     @Parameter( name = "key" ) String key,
>>                 @Description( "Value to search for." )
>>                     @Parameter( name = "value" ) String value )
>>             throws BadInputException
>>     {
>>
>>         if ( !graphDb.index().existsForNodes( indexName ) )
>>             throw new BadInputException("Index doesn't exist", new
>> NotFoundException());
>>
>>         Index<Node> index = graphDb.index().forNodes( indexName );
>>
>>         if (index.get(key, value).size() > 0)
>>             throw new BadInputException("Key/value pair found in index");
>>         return null;
>>     }
>> }
>>
>> I'm still not entirely certain that this is the appropriate way to go;
>> probably a better solution would be a more general "add node with
>> unique, indexed fields" command that's slightly more functional than
>> this batch-operation-quirks based hack. As an aside -- does anyone
>> know when/if lists of maps for parameters will be implemented for REST
>> plugins?
>>
>> Thanks for the response!
>>
>> -T
>>
>> On Thu, Sep 22, 2011 at 4:57 PM, Linan Wang <tali.wang at gmail.com> wrote:
>>>
>>> Hi,
>>> i had the issue few days ago and thanks to McKinley I got a workable
>>> solution. i think the best way to do is through unmanaged extension.
>>> the overhead of multiple REST calls could make the matter more
>>> complex.
>>>
>>> here is part of my ObjectFactory class. in my situation it's an
>>> external id needs to be uniq. feel free to correct my codes :) the
>>> performance is not ideal though. on my imac I got around 50 insertions
>>> per sec. the bottle neck is not memory.
>>>
>>>        public T get(long externalId)
>>>        {
>>>                // try asynchronized read first;
>>>                Index<Node> idx = getDefaultNodeIndex();
>>>
>>>                IndexHits<Node> h = idx.get(IDX_KEY_EXTERNAL_ID,
> externalId);
>>>                Node n = h.getSingle();
>>>                h.close();
>>>
>>>                if(n != null)
>>>                        return wrap(n);
>>>
>>>                // if not found, try synchronized version;
>>>                return null;
>>>        }
>>>
>>>        public T getOrCreate(long externalId)
>>>        {
>>>
>>>                T ret = get( externalId );
>>>                if(ret != null)
>>>                        return ret;
>>>
>>>                // if not found, try synchronized version;
>>>                return synchronizedGetOrCreate(externalId);
>>>        }
>>>
>>>        private synchronized T synchronizedGetOrCreate(long externalId)
>>>        {
>>>                // Just in case!
>>>                T ret = get( externalId );
>>>                if(ret != null)
>>>                        return ret;
>>>
>>>                Index<Node> idx = getDefaultNodeIndex();
>>>                Node n = null;
>>>
>>>                Transaction tx = db.beginTx();
>>>
>>>                try{
>>>                        n = db.createNode();
>>>
>>>                        // set property
>>>                        n.setProperty(AbstractObject.EXTERNAL_ID_KEY,
> externalId);
>>>
>>>                        // add to default index;
>>>                        idx.add(n, IDX_KEY_EXTERNAL_ID, externalId);
>>>
>>>                        tx.success();
>>>                }catch(Exception e){
>>>                        tx.failure();
>>>                }finally{
>>>                        tx.finish();
>>>                }
>>>
>>>                return wrap(n);
>>>        }
>>>
>>> On Thu, Sep 22, 2011 at 9:33 PM, twooster <twooster at gmail.com> wrote:
>>> > Hi,
>>> >
>>> > I've seen this come up a number of times in various forms on the
> mailing
>>> > list, but -- while there are some scattered answers here and there --
> there
>>> > doesn't seem to be much of a definitive solution. The problem I'm
> looking to
>>> > solve is basic uniqueness (e.g. for creating an account -- unique
> username
>>> > or email address); to make matters more complicated, I'd like to do
> this
>>> > over the REST interface.
>>> >
>>> > In the a non-REST use of Neo4j, it sounds like I could achieve this by
> doing
>>> > the following:
>>> >
>>> > 1. Begin a transaction
>>> > 2. Acquire a write lock on a UserRef supernode, say by attempting to
> delete
>>> > the property __WRITE_LOCK__ (which will fail if the property doesn't
> exist,
>>> > say)
>>> > 3. Check if username is in users index
>>> > 4. If it is, cancel transaction and fail
>>> > 5. Otherwise, add a User node, relate it back to the UserRef node, and
> add
>>> > it to the index
>>> > 6. Release lock on UserRef supernode by re-adding __WRITE_LOCK__
> property
>>> > 7. Commit transaction
>>> >
>>> > First -- does this sound roughly sound? Assuming that any operation
> that
>>> > ever touches the index agrees to first "acquire" a write lock in this
>>> > manner, are there any tricky concurrency issues (maybe out-of-sync
> indexes,
>>> > or the index.get(key,v).size() function being "almost correct") that
> I'm
>>> > missing?
>>> >
>>> > To complicate matters, I'd _like_ to do this with REST, not the least
> reason
>>> > because my main project code is in Python, and neo4j.py seems to be
>>> > relatively unmaintained compared to the REST client. It's my
> understanding
>>> > that transactions are handled using the batch interface. The only way
> to
>>> > make the transaction fail is if any given operation returns a non-2xx
> status
>>> > code. Thus, the 'if value is found in an index' thing is somewhat
> difficult
>>> > to implement given the basic REST primitives.
>>> >
>>> > After cozying up with the code for a number of hours last night, my
> first
>>> > real foray into Java programming, it appears that I can achieve the
>>> > behaviour I want by introducing a REST plugin (an unmanaged extension
> would
>>> > be cleaner, but the big warning and limited documentation dissuaded me)
> that
>>> > throws an exception if a value _is_ found in a given index (which will
> cause
>>> > the plugin invoker to return a 4xx response). Now my workflow over REST
>>> > looks like this:
>>> >
>>> > 1. Begin transaction (e.g., start a batch request to the server),
> issuing
>>> > the following commands:
>>> > 2. Attempt to delete __WRITE_LOCK__ from UserRef node (will 4xx if
> property
>>> > is non-existent)
>>> > 3. POST to extension, to check if username is in users index (4xx if it
>>> > exists)
>>> > 3a. Transaction will fail at this point if user is in index
>>> > 4. Add a User node
>>> > 5. Relate it back to the UserRef node
>>> > 5. Add it to the index
>>> > 6. Release lock on UserRef supernode by re-adding __WRITE_LOCK__
> property
>>> > 7. Finish HTTP request, done
>>> >
>>> > This seems to work, but, again, are there any blind-spots that I'm
> unaware
>>> > of? How about if this goes to a HA cluster?
>>> >
>>> > Finally, somewhat related, are some concerns with the batch API
>>> > back-reference capability. This appears to manifest itself as a blind
>>> > string-replace of '{[id]}' in provided fields. This _seems_ like it
> could
>>> > have some security/annoying bug concerns relating to user-provided data
>>> > (local portion of email address includes the substring '{1}', for
> example,
>>> > which is valid per the email spec). I currently don't see any way
> around
>>> > this except to restrict user input. Any thoughts?
>>> >
>>> > Anyway, thanks for any comments and responses!
>>> >
>>> > -Tony Wooster
>>> >
>>> > --
>>> > View this message in context:
> http://neo4j-community-discussions.438527.n3.nabble.com/REST-Transactions-and-Uniqueness-tp3360054p3360054.html
>>> > Sent from the Neo4j Community Discussions mailing list archive at
> Nabble.com.
>>> > _______________________________________________
>>> > Neo4j mailing list
>>> > User at lists.neo4j.org
>>> > https://lists.neo4j.org/mailman/listinfo/user
>>> >
>>>
>>>
>>>
>>> --
>>> Best regards
>>>
>>> Linan Wang
>>> _______________________________________________
>>> Neo4j mailing list
>>> User at lists.neo4j.org
>>> https://lists.neo4j.org/mailman/listinfo/user
>> _______________________________________________
>> Neo4j mailing list
>> User at lists.neo4j.org
>> https://lists.neo4j.org/mailman/listinfo/user
> _______________________________________________
> Neo4j mailing list
> User at lists.neo4j.org
> https://lists.neo4j.org/mailman/listinfo/user
>



-- 
Best regards

Linan Wang


More information about the User mailing list