Skip to Content

(Edit Jan 24, 2015: added Transaction handling and side note (this is the last update here, all further tutorial versions you’ll find here.)

The other day I’ve played a bit with the Open Source Graph database Neo4j (for no reasons, just to learn something new), and the usual “what if…” came to my mind and I’ve started to code.


The result is a Neo4j ABAP Connector called Neo4a, available under Apache License 2.0 on Github: Neo4a

The whole project is coded under Netweaver ABAP Stack 7.40 SP8. It will not work on lower releases and will not be downported (sorry).

Installing and running Neo4j (example for Linux)

In my case I’ve created a new virtual Linux machine (CentOS) under VMware to separate the database from my SAP server, but it should work also, if you are installing Neo4j on the same server (don’t do this in production).

Just download the tar (choose your OS: http://neo4j.com/download/other-releases/), extract into a folder of your choice, call ‘./bin/neo4j start’ and you are done.


Installation of Neo4a

You need the most current ABAP JSON Document class (zJSON Version 2.28, available on Github https://github.com/se38/zJSON ).

Install the zJSON and Neo4a nuggets via SAPlink and activate the sources. It may be a good idea to move the classes into a new development package, but this is not required.

A small Tutorial for Neo4a

The following examples are taken from the Neo4j tutorial and are “translated” into ABAP.


(side note: if you screwed up and want to start all over again, just stop Neo4j with ‘./bin/neo4j stop’ and delete the content of the ‘data’ folder with ‘rm -rf data/*’. An empty database will be recreated automatically if you start the database again)

Create nodes

    “*— points to the Neo4j DB host —*

    DATA(neo4a) = NEW zcl_neo4a( ‘192.168.38.52’ ).

    TYPES: BEGIN OF ty_actor,

             name TYPE string,

           END OF ty_actor,

           BEGIN OF ty_movie,

             title TYPE string,

           END OF ty_movie.

    DATA(actor) = VALUE ty_actor( name = ‘Tom Hanks’ ).

    DATA(movie) = VALUE ty_movie( title = ‘Sleepless IN Seattle’ ).

    TRY.

        DATA(node_actor) = neo4a->create_node(

                       i_properties = actor

                       i_label     = ‘Actor’

                   ).

        DATA(node_movie) = neo4a->create_node(

                       i_properties = movie

                       i_label     = ‘Movie’

                   ).

      CATCH zcx_neo4a INTO DATA(n4a_ex).

        WRITE:/ n4a_ex->get_text( ).

        RETURN.

    ENDTRY.

Result: Two nodes

neo4a-1.PNG

Create a relationship between the two nodes

    DATA(neo4a) = NEW zcl_neo4a( ‘192.168.38.52’ ).

    TRY.

        DATA(node_actor) = neo4a->get_node( 1 ).

        DATA(node_movie) = neo4a->get_node( 2 ).

        DATA(relationship) = node_actor->create_relationship(

                         i_type        = ‘acted_in’

                         i_to_node     = node_movie

                     ).

      CATCH zcx_neo4a INTO DATA(n4a_ex).

        WRITE:/ n4a_ex->get_text( ).

        RETURN.

    ENDTRY.

Result: the two nodes are conected

neo4a-2.PNG

Set/Get properties

    DATA(neo4a) = NEW zcl_neo4a( ‘192.168.38.52’ ).

    TRY.

        DATA(node) = neo4a->get_node( 1 ).

        node->set_property(

          EXPORTING

            i_name   = ‘year_of_birth’

            i_value  = ‘1944’

        ).

        DATA(name) = node->get_property( ‘name’ ).

        DATA(yob) = node->get_property( ‘year_of_birth’ ).

        cl_demo_output=>display( |{ name } : { yob }| ).

      CATCH zcx_neo4a INTO DATA(n4a_ex).

        WRITE:/ n4a_ex->get_text( ).

        RETURN.

    ENDTRY.

Get all properties

You can get the properties of a node as data object, JSON string of Name/Value pairs.

    DATA(neo4a) = NEW zcl_neo4a( ‘192.168.38.52’ ).

    DATA: BEGIN OF actor,

            name          TYPE string,

            year_of_birth TYPE string,

          END OF actor.

    TRY.

        neo4a->get_node( 1 )>get_properties(

          IMPORTING

            e_properties      = actor

            e_properties_json = DATA(properties_json)

            e_properties_table = DATA(properties_table)

        ).

        cl_demo_output=>begin_section( ‘DATA’ ).

        cl_demo_output=>write( actor ).

        cl_demo_output=>begin_section( ‘JSON’ ).

        cl_demo_output=>write( properties_json ).

        cl_demo_output=>begin_section( ‘Name/Value pairs’ ).

        cl_demo_output=>write( properties_table ).

        cl_demo_output=>display( ).

      CATCH zcx_neo4a INTO DATA(n4a_ex).

        WRITE:/ n4a_ex->get_text( ).

        RETURN.

    ENDTRY.

Result:

neo4a-5.PNG

Get all nodes with a label

DATA(neo4a) = NEW zcl_neo4a( ‘192.168.38.52’ ).

    TYPES: BEGIN OF ty_movie,

             title TYPE string,

           END OF ty_movie.

    DATA(movie) = VALUE ty_movie( title = ‘Forrest Gump’ ).

    TRY.

        “*— create another movie —*

        DATA(node_movie) = neo4a->create_node(

                 i_properties = movie

                 i_label     = ‘Movie’

             ).

        “*— and connect to Tom —*

        neo4a->get_node( 1 )>create_relationship(

          EXPORTING

            i_type        = ‘acted_in’

            i_to_node     = node_movie

        ).

        “*— get all movies —*

        neo4a->get_nodes_with_label(

          EXPORTING

            i_label     = ‘Movie’

          IMPORTING

            e_nodes     = DATA(movies)

        ).

        LOOP AT movies ASSIGNING FIELD-SYMBOL(<movie>).

          cl_demo_output=>write_text( <movie>>get_property( ‘title’ ) ).

        ENDLOOP.

        cl_demo_output=>display( ).

      CATCH zcx_neo4a INTO DATA(n4a_ex).

        WRITE:/ n4a_ex->get_text( ).

        RETURN.

    ENDTRY.

The second movie is created and connected to Tom:

neo4a-3.PNG

The list contains all movies:

neo4a-4.PNG

Queries

To submit queries to the database we are using the Neo4j Cypher Query Language

    DATA(neo4a) = NEW zcl_neo4a( ‘192.168.38.52’ ).

    “*— get all movies where Tom acted in —*

    DATA(query) = `MATCH (actor:Actor {name:’Tom Hanks’})-[r:acted_in]->(movie:Movie) RETURN movie.title`.

    TRY.

        neo4a->query(

          EXPORTING

            i_cypher = query

          IMPORTING

            e_result = DATA(result)

        ).

        cl_demo_output=>display_json( result ).

      CATCH zcx_neo4a INTO DATA(n4a_ex).

        WRITE:/ n4a_ex->get_text( ).

        RETURN.

    ENDTRY.

Result:

neo4a-6.PNG

Transactions

(time to remember the side note at the top of this tutorial)


With transactions you can make a number of requests, each of which executes additional statements, and keeps the transaction open by resetting the transaction timeout. After the timeout an automatically rollback occurres. You can see the timeout in every response of a request to a new or open transaction.

  “transaction” : {

    “expires” : “Fri, 24 Jan 2015 22:53:51 +0000”

  },

Now let’s have a look at the following example:

    DATA(neo4a) = NEW zcl_neo4a( ‘192.168.38.52’ ).

    DATA(statements) = VALUE string_table(

      (

       `CREATE (matrix1:Movie { title : ‘The Matrix’, year : ‘1999-03-31’ })` &&

       `CREATE (matrix2:Movie { title : ‘The Matrix Reloaded’, year : ‘2003-05-07’ })` &&

       `CREATE (matrix3:Movie { title : ‘The Matrix Revolutions’, year : ‘2003-10-27’ })` &&

       `CREATE (keanu:Actor { name:’Keanu Reeves’ })`  &&

       `CREATE (laurence:Actor { name:’Laurence Fishburne’ })` &&

       `CREATE (carrieanne:Actor { name:’Carrie-Anne Moss’ })` &&

       `CREATE (keanu)-[:ACTS_IN { role : ‘Neo’ }]->(matrix1)` &&

       `CREATE (keanu)-[:ACTS_IN { role : ‘Neo’ }]->(matrix2)` &&

       `CREATE (keanu)-[:ACTS_IN { role : ‘Neo’ }]->(matrix3)` &&

       `CREATE (laurence)-[:ACTS_IN { role : ‘Morpheus’ }]->(matrix1)` &&

       `CREATE (laurence)-[:ACTS_IN { role : ‘Morpheus’ }]->(matrix2)` &&

       `CREATE (laurence)-[:ACTS_IN { role : ‘Morpheus’ }]->(matrix3)` &&

       `CREATE (carrieanne)-[:ACTS_IN { role : ‘Trinity’ }]->(matrix1)` &&

       `CREATE (carrieanne)-[:ACTS_IN { role : ‘Trinity’ }]->(matrix2)` &&

       `CREATE (carrieanne)-[:ACTS_IN { role : ‘Trinity’ }]->(matrix3)`

      )

      (

       `CREATE (whatever:Movie { title : ‘Just another Movie’, year : ‘2015-01-24’ })`

      )

    ).

    DATA(next_statements) = VALUE string_table(

      (

       `CREATE (whatever2:Movie { title : ‘Yet another Movie’, year : ‘2015-01-24’ })`

      )

    ).

    TRY.

        “*— create the transaction —*

        DATA(transaction) = neo4a->create_transaction( ).

        “*— send the DB statements —*

        transaction>send( i_statements = statements ).

        transaction>send( i_statements = next_statements ).

        “*— placeholders without properties should be visible now —*

        cl_demo_output=>display( ‘have a look in the Neo4j browser’ ).

        “*— and rollback the work —*

        transaction>rollback( ).

        “*— look Mom, they are gone —*

        cl_demo_output=>display( ‘and look again’ ).

      CATCH zcx_neo4a INTO DATA(n4a_ex).

        WRITE:/ n4a_ex->get_text( ).

        RETURN.

    ENDTRY.

After the first stop, you can see the placeholders in the Neo4j browser

neo4a-7.PNG

Close the output window. After the next stop look again -> the placeholders are gone.

Now replace the last two statements with:

        “*— commit the work —*

        transaction>commit( ).

        “*— tahtah, the Matrix —*

        cl_demo_output=>display( ‘the red pill please’ ).

and try again -> after the last stop the Matrix is alive…

neo4a-8.PNG

http://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Red_and_blue_pill.jpg/320px-Red_and_blue_pill.jpg

Image by W. Carter (Wikimedia)

More methods are already implemented (ie. deletes, just have a look at the class interfaces), others are in the pipeline (see the open issues on Github)

You can find me on Twitter and G+

To report this post you need to login first.

7 Comments

You must be Logged on to comment or reply to a post.

  1. Pankaj Kumar

    Very nice. I have been tinkering with graph dbs myself but more on the people graph side. Now with this blog you are making me think what data in ERP ( let us leave HCM aside for now) would be a good candidate for graphs or may be there are opportunities to marry some people graph data with relational data and get some insights.

    Overall kudos to start on this path. I dunno if anyone else has done this before.

    (0) 
  2. Praveer Sen

    Nice, i could say, wit this we can provide different kind of views to display information.

    and anyhow, pictorial information is always good and understandable rather then tabular data display..

    Nice try..

    🙂 Learning.

    Praveer.

    (0) 
    1. Uwe Fetzer Post author

      Hi Praveer,

      Graph Databases are primarilary not for visualizing data but for answering questions which are not or not easily possible with relational databases / SQL like “who are the friends of your friends” or “customers who bought book x also bought books y and z”. Both queries are just one line of code in Graph Databases.

      Will blog about it later.

      (0) 
      1. Abinash Mohapatra

        I would not say “not easily possible”, but rather that it’s exorbitant. Graphs (especially Neo4j which is a native graph database) providing index-free adjacency makes it cost effective. Also that answering reciprocal queries and recommendation engines are niche areas for graph databases.As a matter of fact, I think today’s data is so connected that virtually one could model most use cases as graphs.

        (0) 

Leave a Reply