Difference between session.run and session.readTransaction or session.writeTransaction

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()}) ?

2 Likes

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.

  1. 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:// or neo4j:// 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.

  2. 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.

3 Likes

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

1 Like

Check the "Transactions Functions" documentation in the driver manual, they have examples in all supported languages.

https://neo4j.com/docs/driver-manual/1.7/sessions-transactions/#driver-transactions-transaction-functions

Specifically, you can use tx.run to do the things inside of the transaction sequentially that you need.

2 Likes

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. :slight_smile:

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.

1 Like

That worked! :slight_smile:
Thanks a ton, David for the detailed code and explanation!
Much appreciated!

1 Like

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.

1 Like

great i will try this out today... and many thanks for finding the time to answer,....