neo4j / neo4j-ogm

Java Object-Graph Mapping Library for Neo4j
https://neo4j.com/docs/ogm-manual/
Apache License 2.0
333 stars 165 forks source link

Constraint causes exception with custom key converter #787

Closed sdaschner closed 4 years ago

sdaschner commented 4 years ago

When using a custom key type with a custom serializer, loading and saving nodes via OGM works as expected, until a constraint is added.

Current Behavior

The following setup:

@NodeEntity
public class MyNode {

    @Id
    @Convert(MyKeyTypeStringConverter.class)
    public MyKeyType key;

    private MyNode() {}

    public MyNode(String key) {
        this.key = new MyKeyType(key);
    }

    // equals, hashCode, toString
}
public class MyKeyType implements Serializable {

    private final String key;

    public MyKeyType(String key) {
        this.key = key;
    }

    // getter, equals, hashCode, toString
}
public class MyKeyTypeStringConverter implements AttributeConverter<MyKeyType, String> {

    // maps string to `key.getKey()` and from `new MyKeyType(key)` ...

}

The following works as expected:

// some test
MyNode node = session.load(MyNode.class, new MyKeyType("1234"));
System.out.println("node = " + node);

node = new MyNode("2345");
session.save(node);
System.out.println("saved = " + node);

Now, if we add a key constraint (enterprise edition, haven't tested other constraints):

CREATE CONSTRAINT ON (n:MyNode) ASSERT (n.key) IS NODE KEY;

Then loading fails with the following exception:

org.neo4j.ogm.exception.CypherException: Cypher execution failed with code 'Neo.ClientError.Statement.TypeError': Property values can only be of primitive types or arrays thereof.

    at org.neo4j.ogm.drivers.bolt.response.BoltResponse.next(BoltResponse.java:51)
    at org.neo4j.ogm.response.Response.toList(Response.java:43)
    at org.neo4j.ogm.context.GraphRowModelMapper.map(GraphRowModelMapper.java:50)
    at org.neo4j.ogm.session.delegates.LoadOneDelegate.lambda$load$0(LoadOneDelegate.java:89)
    at org.neo4j.ogm.session.Neo4jSession.doInTransaction(Neo4jSession.java:590)
    at org.neo4j.ogm.session.Neo4jSession.doInTransaction(Neo4jSession.java:564)
    at org.neo4j.ogm.session.delegates.LoadOneDelegate.load(LoadOneDelegate.java:86)
    at org.neo4j.ogm.session.delegates.LoadOneDelegate.load(LoadOneDelegate.java:53)
    at org.neo4j.ogm.session.Neo4jSession.load(Neo4jSession.java:184)
    at com.sebastian_daschner.graph.GraphDBIT.testGetTestNode(GraphDBIT.java:44)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: org.neo4j.driver.exceptions.ClientException: Property values can only be of primitive types or arrays thereof
    at org.neo4j.driver.internal.util.Futures.blockingGet(Futures.java:143)
    at org.neo4j.driver.internal.InternalResult.blockingGet(InternalResult.java:128)
    at org.neo4j.driver.internal.InternalResult.hasNext(InternalResult.java:64)
    at org.neo4j.ogm.drivers.bolt.response.GraphModelResponse.fetchNext(GraphModelResponse.java:42)
    at org.neo4j.ogm.drivers.bolt.response.GraphModelResponse.fetchNext(GraphModelResponse.java:29)
    at org.neo4j.ogm.drivers.bolt.response.BoltResponse.next(BoltResponse.java:48)
    ... 35 more
    Suppressed: org.neo4j.driver.internal.util.ErrorUtil$InternalExceptionCause
        at org.neo4j.driver.internal.util.ErrorUtil.newNeo4jError(ErrorUtil.java:79)
        at org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher.handleFailureMessage(InboundMessageDispatcher.java:105)
        at org.neo4j.driver.internal.messaging.v1.MessageReaderV1.unpackFailureMessage(MessageReaderV1.java:83)
        at org.neo4j.driver.internal.messaging.v1.MessageReaderV1.read(MessageReaderV1.java:59)
        at org.neo4j.driver.internal.async.inbound.InboundMessageHandler.channelRead0(InboundMessageHandler.java:83)
        at org.neo4j.driver.internal.async.inbound.InboundMessageHandler.channelRead0(InboundMessageHandler.java:35)
        at org.neo4j.driver.internal.shaded.io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:99)
        at org.neo4j.driver.internal.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
        at org.neo4j.driver.internal.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
        at org.neo4j.driver.internal.shaded.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355)
        at org.neo4j.driver.internal.shaded.io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:321)
        at org.neo4j.driver.internal.shaded.io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:295)
        at org.neo4j.driver.internal.async.inbound.MessageDecoder.channelRead(MessageDecoder.java:47)
        at org.neo4j.driver.internal.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
        at org.neo4j.driver.internal.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
        at org.neo4j.driver.internal.shaded.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355)
        at org.neo4j.driver.internal.shaded.io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:321)
        at org.neo4j.driver.internal.shaded.io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:295)
        at org.neo4j.driver.internal.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
        at org.neo4j.driver.internal.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
        at org.neo4j.driver.internal.shaded.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355)
        at org.neo4j.driver.internal.shaded.io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
        at org.neo4j.driver.internal.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
        at org.neo4j.driver.internal.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
        at org.neo4j.driver.internal.shaded.io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
        at org.neo4j.driver.internal.shaded.io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
        at org.neo4j.driver.internal.shaded.io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714)
        at org.neo4j.driver.internal.shaded.io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650)
        at org.neo4j.driver.internal.shaded.io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576)
        at org.neo4j.driver.internal.shaded.io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
        at org.neo4j.driver.internal.shaded.io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
        at org.neo4j.driver.internal.shaded.io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at org.neo4j.driver.internal.shaded.io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:844)

Context

Note that it does works as expected when using a custom key type for which Neo provides a converter, e.g. UuidStringConverter. So the issue seems to be with custom converters.

Your Environment

meistermeier commented 4 years ago

Thanks for reporting this. The underlying problem is that the converter does not get called for the conversion of the MyKeyType when the load is executed. In general it is possible to use a map-like construct like {key: '1234'} as a parameter but with the constraint applied it has to be a scalar Cypher type.

Without the constraint you should not be able to find anything with the load method above if you save and load in different Neo4j-OGM sessions or do a session.clear()between them, right?

It works for UUID because the internal mapping of OGM calls the jackson object mapper and it gets converted to a String behind the scenes.