Search code examples
memgraphdbneo4j-java-api

How to know if CREATE query failed in Memgraph through Neo4j Java driver?


Description

I'm attempting to use Memgraph in my application by connecting to it using the Neo4j Java driver. I have a constraint that nodes of a certain label must have a certain property. When I try to create a node without that property, the create fails (as verified by a separate read query); however, the query and transaction in which I attempt to create the node give no indication of failure. Running the same create query in Memgraph Lab gives a clear error message. Is there a way for me to get a similar message using the driver, so that I can know if the create failed without needing to do a separate read?

Example

Here is a straightforward Kotlin main function that demonstrates the problem. I know Kotlin isn't the most common of languages, but hopefully it's legible enough.

import org.neo4j.driver.AuthTokens
import org.neo4j.driver.GraphDatabase
import org.neo4j.driver.Query
import org.neo4j.driver.Record
import org.neo4j.driver.Session
import org.neo4j.driver.summary.ResultSummary

fun main() {
    // setup: delete all nodes (so that I can run this repeatedly) and create a constraint on n:Foo
    val driver = GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic("myUsername", "myPassword")) // replace with the correct auth for your Memgraph database.
    driver.session().run("MATCH (n) \n DETACH DELETE n;")
    driver.session().run("CREATE CONSTRAINT ON (n:Foo) ASSERT EXISTS (n.bar);").consume()

    // attempt to create n:Foo with bar so that I know what a success looks like
    lateinit var successfulRecord: Record
    lateinit var successfulSummary: ResultSummary
    driver.session().use { session: Session ->
        session.beginTransaction().run {
            val query = Query("CREATE (node:Foo {bar: 3}) \n RETURN node")
            val result = run(query)
            successfulRecord = result.single()
            successfulSummary = result.consume()
            commit()
            close()
        }
    }

    // attempt to create n:Foo without bar. I want this to throw an error or give me a result indicating failure
    lateinit var failureRecord: Record
    lateinit var failureSummary: ResultSummary
    driver.session().use { session: Session ->
        session.beginTransaction().run {
            val query = Query("CREATE (node:Foo) \n RETURN node")
            val result = run(query)
            failureRecord = result.single()
            failureSummary = result.consume()
            commit() //commit() doesn't return anything
            close()
        }
        // I know of no way to get info from the session on whether the CREATE succeeded or not
    }

    // This is where I would like to see a piece of information that is meaningfully different between the two
    println(successfulRecord)
    println(failureRecord)
    println()
    println(successfulSummary)
    println(failureSummary)

    // Read from the db to make sure that the successful one was successful and the bad one failed.
    driver.session().use { session: Session ->
        session.beginTransaction().run {
            val query = Query("MATCH (node:Foo) \n RETURN node")
            val result = run(query)
            val nodeList = result.list()
            // These assertions correctly pass; the node without bar was not created and the node with bar was created
            assert(nodeList.size == 1)
            assert(nodeList[0].get(0).asMap()["bar"] == 3L)
        }
    }
}

It prints the following:

Record<{node: node<59>}>
Record<{node: node<60>}>

InternalResultSummary{query=Query{text='CREATE (node:Foo {bar: 3}) 
 RETURN node', parameters={}}, serverInfo=InternalServerInfo{address='localhost:7687', version='Neo4j/4.3.0'}, databaseInfo=InternalDatabaseInfo{name='null'}, queryType=WRITE_ONLY, counters=InternalSummaryCounters{nodesCreated=1, nodesDeleted=0, relationshipsCreated=0, relationshipsDeleted=0, propertiesSet=0, labelsAdded=1, labelsRemoved=0, indexesAdded=0, indexesRemoved=0, constraintsAdded=0, constraintsRemoved=0, systemUpdates=0}, plan=null, profile=null, notifications=[], resultAvailableAfter=-1, resultConsumedAfter=-1}
InternalResultSummary{query=Query{text='CREATE (node:Foo) 
 RETURN node', parameters={}}, serverInfo=InternalServerInfo{address='localhost:7687', version='Neo4j/4.3.0'}, databaseInfo=InternalDatabaseInfo{name='null'}, queryType=WRITE_ONLY, counters=InternalSummaryCounters{nodesCreated=1, nodesDeleted=0, relationshipsCreated=0, relationshipsDeleted=0, propertiesSet=0, labelsAdded=1, labelsRemoved=0, indexesAdded=0, indexesRemoved=0, constraintsAdded=0, constraintsRemoved=0, systemUpdates=0}, plan=null, profile=null, notifications=[], resultAvailableAfter=-1, resultConsumedAfter=-1}

Process finished with exit code 0

which does not show any difference between the successful creation and the failed creation.

I have tried the following

  • Using the driver from org.memgraph:bolt-java-driver:0.4.7 instead of org.neo4j.driver:neo4j-java-driver:5.4.0. There was no difference in the results.
  • Using writeTransaction() and executeWrite() instead of beginTransaction(). The syntax was different, but I still saw no way to get an error in the block of code that's trying to create the bad node.
  • Calling getLastBookmarks() within a session before and after the transaction to compare results. It gives an empty list both before and after (both for successful and unsuccessful create).
  • Inspecting variables line by line in my debugger. I could see no differences in the records or the summaries, and it looked like the info that was printed represents the variables quite well.
  • Verifying on Memgraph Lab that the successful creation is, indeed, successful. It is. After running the code, the node:Foo with bar: 3 does show up in Memgraph Lab. Therefore, I must be committing the transactions correctly.
  • Creating the node in an auto-commit transaction (driver.session().run("CREATE (node:Foo) RETURN node"). This did throw an error (org.neo4j.driver.exceptions.ClientException: Unable to commit due to existence constraint violation on :Foo(bar)). It's nice that it successfully gave clear feedback that way; However, running all my code as auto-commit isn't feasible for me because of the role that Memgraph will play in my application. Is there a way to get similar behavior with a managed or unmanaged transaction that does not have auto-commit?

Versions:

  • SDK 17
  • Kotlin 1.7.20
  • Gradle 7.2
  • Memgraph 2.8.0

Solution

  • This was indeed a bug with Memgraph, not with my code. The Memgraph team has worked on it, and I have confirmed that this bug is fixed in version 2.13.0 of Memgraph.