1

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
  • 1
    Hi, this looks like a driver compatibility issue, I have managed to replicate this and got the following logs: [2023-06-05 11:39:16.915][Info]Client connected 'neo4j-java/5.7.0' [2023-06-05 11:39:16.921][Debug][Run] 'CREATE CONSTRAINT ON (n:Foo) ASSERT EXISTS (n.bar);' [2023-06-05 11:39:16.971][Debug][Run] 'CREATE (node:Foo {bar: 3}) RETURN node' [2023-06-05 11:39:17.002][Debug][Run] 'CREATE (node:Foo) RETURN node' [2023-06-05 11:39:17.005][Trace]Error message: Unable to commit due to existence constraint violation on :Foo(bar) Did you manage to make any progress? – Ante Javor Jun 05 '23 at 11:51
  • No progress yet. I'm unable to see that in my logs... If I'm running Memgraph through Docker, then that should be recorded in the location specified by volumes: mg_log (in my case, /var/log/memgraph), right? Because that's where I've been looking at log files, but those logs seem to only contain warnings about authentication and licensing – shadowyfour Jun 05 '23 at 15:45
  • You can start Memgraph with some different logging levels, setting --log-level=Trace will yield more details about all logs. https://memgraph.com/docs/memgraph/reference-guide/configuration#other – Ante Javor Jun 06 '23 at 11:49
  • Oh, of course! Thank you, I now see that error message in the logs. Doesn't really solve my problem yet, but it's good to see more into what's happening. For any poor souls who follow my footsteps: using a docker-compose file to set the logging level at the same time as the bolt server name for init requires that you wrap all arguments in single quotes, like - MEMGRAPH="'--bolt-server-name-for-init=Neo4j/ --log-level=TRACE'" – shadowyfour Jun 07 '23 at 15:12

0 Answers0