In Javascript driver, what is the difference between running Cypher queries using session.run() and session.readTransaction((tx) => {tx.run()}) or session.writeTransaction((tx) => {tx.run()}) ?
This is a great question, a common one, and a difference with big consequences, so let's break it down.
Session.run is the simplest, easiest form, something called an "autocommit transaction". While it's an easy way to get started, it's not recommended in production. You should instead use readTransaction and writeTransaction, for a bunch of reasons.
-
You should signal to the driver whether you're trying to read or write. In Neo4j clustered setups, when you're using the
bolt+routing://
orneo4j://
schemes, this tells the driver how to appropriately route your query to the right machine. If you want to know more about this topic, have a look at Querying Neo4j Clusters. -
With
session.run
, note you're passing a cypher query. With the other two, you're passing a function that performs the work of the transaction. This is a critical difference. One lets you run a single query. In the other, you might have a function that does a number of different sub-queries, all of which you want to wrap. up into a single overall TX that either succeeds or fails. See (Transaction Management for much more detail). In complex setups this comes up a lot that doing a particular update to your system might take 3 cypher queries, not 1. And you don't want to "partially succeed" you want either all 3 to go through, or none. To do something like that you want a tx work function.
Bottom line -- prefer the use of readTransaction and writeTransaction. Get into that habit and you'll always be in good shape.
Thanks for the clarification, David!
So, if I have a bunch of queries that should be performed sequentially (dependent queries) then how should I write them in a transaction so that the second one is performed only when the first one is successfully completed?
I'd highly appreciate it if you could direct me to any such detailed examples to understand and implement the best-practices in this regard.
Thank you,
Sheekha
Check the "Transactions Functions" documentation in the driver manual, they have examples in all supported languages.
Specifically, you can use tx.run to do the things inside of the transaction sequentially that you need.
I tried the following code and I couldn't get results from the database -
session.readTransaction((transaction) => {
var getUserProfileResult = transaction.run("match (u:user{id:'" + userid + "'}) return u");
result = { ...result, userProfile: getUserProfileResult.summary }
var getUserLanguagesResult = transaction.run("match (u:user{id:'" + userid + "'})-[k:knows]->(v) return u,k,v");
result = { ...result, languages: getUserLanguagesResult.summary };
var getUserFriendsResult = transaction.run("match (u:user{id:'" + userid + "'})-[f:friendOf]->(v) return u,f,v");
result = { ...result, friends: getUserFriendsResult.summary };
console.log('result is ', result);
return result
}).then(result => {
return res.json({ result });
}).catch(function(error) {
console.log("get user error: " + error);
}).finally((result) => {
session.close();
});
I get 'undefined' when I print the result.summary or result.records.
Could you help me figure out what am I doing wrong here?
You're not threading those promises. Each time you run transaction.run() you're creating a new promise, and they each need .then() and .catch() handlers.
I'm not sure if this is what you recommended.
I'm only getting the result for txresult1. But I'm getting a tx2 error which says ''Cannot run statement, because transaction has already been successfully closed.''. Am I missing something here?
Really appreciate all your help!
const getUserProfilePromise = session.readTransaction((transaction) => {
transaction.run("match (u:user{id:'" + userid + "'}) return u")
.then(txresult1 => {
result = { ...result, userProfile: txresult1 };
transaction.run("match (u:user{id:'" + userid + "'})-[k:knows]->(v) return u,k,v")
.then(txresult2 => {
result = { ...result, languages: txresult2 };
transaction.run("match (u:user{id:'" + userid + "'})-[f:friendOf]->(v) return u,f,v")
.then(txresult3 => {
result = { ...result, friends: txresult3 };
}).catch(txerror3 => {
console.log('error from tx3 - ', txerror3);
})
}).catch(txerror2 => {
console.log('tx2 error - ', txerror2);
})
}).catch(txerror1 => {
console.log('tx1 error - ', txerror1);
})
return result
}).then(result => {
return res.json({ result });
}).catch(function(error) {
console.log("get user profile error 3213: " + error);
}).finally((result) => {
session.close();
});
});
You're running into some basic javascript promise threading issues here. You did get what I mean, but you're not returning your inner promises out, you're instead nesting them.
I.e. don't do this:
somePromise()
.then(() => {
somePromise2().then(() => {
somePromise3();
})
})
This is called "Pyramid of doom" in JS land.
Instead do something like this:
somePromise()
.then(() => {
return somePromise2()
})
.then(() => {
return somePromise3()
})
When you don't "return" the promises, the async processing they're doing gets lost and doesn't resolve. As a result, you get to the bottom of your code and the TX gets closed before the inner stuff has finished executing.
That worked!
Thanks a ton, David for the detailed code and explanation!
Much appreciated!
Hi sir, I am new to this, your answer helped me... I hv a couple of questions.
so i hv a workflow like -> MATCH x where x.a=1 CREATE (x)-[has]->(y) SET x.b = y.c
here do I hv to the break the workflow into READ and WRITE?
But if i do that... how will i get x as a node from the READ query to create relationship with y in my WRITE query? Or hv I completely misunderstood the system?
Can you please help?
The entire query is either read or write. In your case, it's write. If you ever use the keywords MERGE or CREATE, it's a dead give-away that the entire query is a write query.
great i will try this out today... and many thanks for finding the time to answer,....
Hi @david_allen. Thank you for this nice summary!
I've created a new node package neo-forte to simplify working with the driver. I actually have put links in the README to this post of yours as well as your medium article about clusters.
The goal of neo-forte
is to make running queries in code as simple as in the data browser. Fundamentally, neo-forte
has a single function run()
. But under the cover it runs transactions.
To enable selection between readTransaction
and writeTransaction
, I have a crude but generally effective approach of searching a query string for the reserved cypher update clause words ("create", "merge", "set", etc.). If any are found, it runs the query within a WRITE transaction, and otherwise READ. Of course, there are "false positives", so you can overwrite the automatic selection to specify READ or WRITE if you like. (That was part of the invaluable feedback I received from @antonio_barcelo.)
If you want to pass in a function rather than a simple query and params, you must use the session transaction methods provided by the neo4j-driver.
If you have any suggestions for neo-forte, I'd really appreciate hearing them!