Mark Needham

Thoughts on Software Development

neo4j/cypher: CypherTypeException: Failed merging Number with Relationship

with 3 comments

The latest thing that I added to my football graph was the matches that are shown on TV as I have the belief that players who score on televised games get more attention than players who score in other games.

I thought it’d be interesting to work out who the top scorers are on each of these game types.

I added the following relationship type to allow me to do this:

game-[:on_tv]-channel

I then wrote a query to get a list of all the players along with a collection of the games they played in and whether or not this game was televised:

START player=node:players('name:*')
MATCH player-[:played|subbed_on]-stats-[:in]-game-[t?:on_tv]-channel
RETURN player.name, COLLECT([stats.goals, t]) AS games
LIMIT 10

Unfortunately when I ran this query I ended up with the following exception:

CypherTypeException: Failed merging Number with Relationship

From some previous conversations with Wes I’d noticed that this exception didn’t seem to happen with the 1.9.M05 release but I was using 1.9.M04.

Just to see what happened I tried returning the type of the relationship in the collection literal rather than the relationship and that worked:

START player=node:players('name:*')
MATCH player-[:played|subbed_on]-stats-[:in]-game-[t?:on_tv]-channel
RETURN player.name, COLLECT([stats.goals, TYPE(t)]) AS games
LIMIT 10
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| player.name          | games                                                                                                                                                                                                                                                                             |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| "Djibril Cissé"      | [[0,<null>],[1,"on_tv"],[0,<null>],[0,<null>],[0,"on_tv"],[0,<null>],[0,<null>],[0,<null>],[0,<null>],[0,<null>],[1,<null>],[0,"on_tv"],[0,"on_tv"],[0,<null>],[0,<null>],[0,"on_tv"],[0,<null>],[1,<null>]]                                                                      |
| "Markus Rosenberg"   | [[0,<null>],[0,<null>],[0,<null>],[0,<null>],[0,<null>],[0,<null>],[0,<null>],[0,<null>],[0,<null>],[0,<null>],[0,<null>],[0,"on_tv"],[0,"on_tv"],[0,<null>],[0,"on_tv"],[0,<null>]]                                                                                              |
| "Gabriel Agbonlahor" | [[0,"on_tv"],[1,<null>],[0,"on_tv"],[0,<null>],[0,<null>],[0,"on_tv"],[1,<null>],[0,"on_tv"],[0,<null>],[0,"on_tv"],[0,<null>],[0,<null>],[1,"on_tv"],[1,<null>],[0,<null>],[0,"on_tv"],[1,<null>],[0,<null>],[0,<null>],[0,<null>],[0,<null>]]                                   |
| "Shaun Derry"        | [[0,<null>],[0,<null>],[0,<null>],[0,"on_tv"],[0,<null>],[0,<null>],[0,"on_tv"],[0,<null>],[0,<null>],[0,<null>],[0,<null>],[0,<null>],[0,<null>],[0,"on_tv"]]                                                                                                                    |
| "Marouane Fellaini"  | [[0,<null>],[0,"on_tv"],[0,<null>],[1,<null>],[1,"on_tv"],[1,<null>],[0,<null>],[0,"on_tv"],[1,"on_tv"],[0,<null>],[0,"on_tv"],[2,<null>],[1,<null>],[1,<null>],[1,<null>],[0,<null>],[0,<null>],[0,"on_tv"],[0,<null>],[0,<null>],[2,<null>],[0,"on_tv"],[0,<null>],[0,"on_tv"]] |
| "Jermaine Jenas"     | [[0,<null>],[1,<null>],[0,<null>],[0,<null>],[0,"on_tv"],[0,<null>],[1,<null>]]                                                                                                                                                                                                   |
| "Sean Morrison"      | [[0,<null>],[0,"on_tv"],[0,<null>],[0,<null>],[1,<null>],[0,"on_tv"],[1,"on_tv"],[0,<null>],[0,"on_tv"]]                                                                                                                                                                          |
| "Claudio Yacob"      | [[0,"on_tv"],[0,<null>],[0,<null>],[0,<null>],[0,<null>],[0,<null>],[0,"on_tv"],[0,<null>],[0,<null>],[0,<null>],[0,<null>],[0,"on_tv"],[0,<null>],[0,<null>],[0,"on_tv"],[0,"on_tv"],[0,"on_tv"],[0,<null>],[0,<null>],[0,<null>],[0,<null>],[0,<null>]]                         |
| "Michael Owen"       | [[0,<null>],[0,<null>],[0,<null>],[0,<null>],[1,<null>],[0,<null>]]                                                                                                                                                                                                               |
| "Tony Hibbert"       | [[0,"on_tv"],[0,"on_tv"],[0,<null>],[0,<null>],[0,<null>]]                                                                                                                                                                                                                        |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
10 rows

The final query ends up looking like this out of interest:

START player=node:players('name:*')
MATCH player-[:played|subbed_on]-stats-[:in]-game-[t?:on_tv]-channel
WITH COLLECT([stats.goals, TYPE(t)]) AS games, player
RETURN player.name,
       REDUCE(goals = 0, h IN FILTER(g IN games : HEAD(TAIL(g)) IS NULL): goals + HEAD(h)) AS nonTvGoals,
       REDUCE(goals = 0, h IN FILTER(g IN games : HEAD(TAIL(g)) <> NULL): goals + HEAD(h)) AS tvGoals,
       REDUCE(goals = 0, h in games : goals + HEAD(h)) AS totalGoals
ORDER BY tvGoals DESC
LIMIT 10
+--------------------------------------------------------+
| player.name        | nonTvGoals | tvGoals | totalGoals |
+--------------------------------------------------------+
| "Gareth Bale"      | 4          | 12      | 16         |
| "Robin Van Persie" | 8          | 11      | 19         |
| "Luis Suárez"      | 12         | 10      | 22         |
| "Theo Walcott"     | 3          | 8       | 11         |
| "Demba Ba"         | 7          | 8       | 15         |
| "Santi Cazorla"    | 4          | 7       | 11         |
| "Carlos Tevez"     | 3          | 6       | 9          |
| "Edin Dzeko"       | 6          | 6       | 12         |
| "Wayne Rooney"     | 6          | 6       | 12         |
| "Juan Mata"        | 4          | 6       | 10         |
+--------------------------------------------------------+
10 rows

So as we can see Gareth Bale pretty much only scores when TV cameras are about!

I was intrigued what had changed between 1.9.M04 and 1.9.M05 so I spent a few hours this morning browsing the cypher part of the code base and not really getting anywhere for the most part.

I thought that there had probably been a change around the way that collection literals were handled but a quick scan of git log suggested there hadn’t been any changes:

$ git log -- community/cypher/src/main/scala/org/neo4j/cypher/internal/parser/v1_9
commit 7311bbe33bc06b346e60e12a4eee2a7173cbd317
Author: Andres Taylor <andres@neotechnology.com>
Date:   Tue Mar 19 06:42:49 2013 +0100
 
    Handles single node patterns in MATCH
 
commit c9f580456572d3d267a15dc88b35e07fd450cf93
Author: Stefan Plantikow <stefan.plantikow@googlemail.com>
Date:   Fri Jan 11 21:27:55 2013 +0100
 
    scala 2.10 support: Kills type erasure warnings with a few casts and cleans up a bit
...

1.9.M04 was released on January 22nd and 1.9.M05 on March 6th and the only commit in this part of the code base in that time didn’t touch the bit of code I’d been looking at.

Interestingly I couldn’t find anywhere in the code base which had the string ‘Failed merging’ so I thought I’d do a quick scan of the diffs to see if this had been deleted:

$ git log -S"Failed merging"
commit b6501aac03cf70419e94b4cfc160695e4950914a
Author: Andres Taylor <andres@neotechnology.com>
Date:   Sat Feb 16 19:30:00 2013 +0100
 
    Changed how Cypher merges types

So in fact there was a commit which changed the way that the collection type was determined so that rather than throwing an exception for clashing types a parent type would be used instead.

In any case this merging problem doesn’t exist in 1.9.M05 and I’ve switched my graph to use that version of neo4j so I won’t be see in this exception anymore!

Be Sociable, Share!

Written by Mark Needham

March 24th, 2013 at 1:00 pm

Posted in neo4j

Tagged with ,

  • Pingback: When nokogiri fails with ‘Nokogiri::XML::SyntaxError: Element script embeds close tag’ Web Driver to the rescue at Mark Needham

  • Anonymous

    Hope you had fun looking at some scala. Yeah, now we can concatenate collections of [“a”, “b”, “c”] + [1, 2, 3], for better or worse. :) My pull request (which I later closed) just jumped to AnyType if they didn’t match, but Andres solved it properly by recursively going up the type tree until it fit both types.

  • http://www.markhneedham.com/blog Mark Needham

    @wfreeman:disqus haha yes I did! I worked on a scala application in 2011 and one of the guys on my team introduced parser combinators to do some CSV parsing! Obviously this is way more complicated so I’ve been trying to re-remember what all the different symbols mean.