Introduction to Neo4j Plugins

Bert Radke

Senior Graph Consultant @Neo4j

Fediverse: @taseroth@chaos.social

What are Neo4j Plugins

  • *.jar

  • Inside /plugins/

  • Loaded at Startup

  • Live inside Neo4j JVM

  • Annotated with @Procedure or @UserFunction

@Procedure("example.getRelationshipTypes",
           mode = Mode.READ)
@Description("Get the different relationships going
             in and out of a node.")
public Stream<RelationshipTypes>
         getRelationshipTypes(@Name("node") Node node) {

APIs

Injectables

@Context
public GraphDatabaseService db;

@Context
public Log log;

@Context
public Transaction tx;

Log

  • Class: org.neo4j.logging.Log

  • Log levels: error, warn, info, debug

  • Placeholders: LOG.info("deleted %d nodes", nbNodes)

Transaction

  • Class: org.neo4j.graphdb.Transaction

  • Starting point for most procedures

  • tx.createNode(Label.label("Movie"), Label.label("SciFi"));

  • tx.findNodes(Label.label("Person"), "surName", "Tom");

GraphDatabaseService

  • Class: org.neo4j.graphdb.GraphDatabaseService

  • Starting of new Transactions db.beginTx();

  • Name of the current database db.databaseName()

Java API

Cypher

MATCH (hanks:Person {name: 'Tom Hanks'})-[:DIRECTED]->(movie)
return collect(movie.title)

Java

var hanks = tx.findNode(Label.label("Person"),
            "name", "Tom Hanks");

  StreamSupport.stream(
    hanks.getRelationships(
    Direction.OUTGOING, RelationshipType.withName("DIRECTED"))
  .spliterator(), false)
    .map(Relationship::getEndNode)
    .map(movie -> movie.getProperty("title"))
    .collect(Collectors.toSet());

Threads and Transactions

  • Transactions bound to Threads

  • Entities proxies bound to Transactions

  • Do NOT pass Entities to Threads

var nodeId = node.getId();
var node = tx.getNodeById(nodeId);

Traversal API

TraversalDescription traverseDescription =
    db.beginTx().traversalDescription()
      .uniqueness(NODE_GLOBAL)
      .breadthFirst()
      .expandand(new AllExpander())
      .evaluationluator(new GreenEvaluator(minimumGreen))
Traverser traverser = traverseDescription.traverse(startNodes);

Testing

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class JoinTest {

    private static final Config driverConfig =
            Config.builder().withoutEncryption().build();
    private Neo4j embeddedDb;

    @BeforeAll
    void initializeNeo4j() {
        this.embeddedDb = Neo4jBuilders.newInProcessBuilder()
                .withDisabledServer()
                .withFunction(Join.class)
                .withFixture(..)
                .build();
    }
@Test
void joinsStrings() {
    try(
        Driver driver = GraphDatabase.driver(
                  embeddedDb.boltURI(),
                        driverConfig);
        Session session = driver.session()
        ) {

        var result = session.run("CALL our.procedure()");
    }
}

Should I build a Plugin?

PRO

CON

  • Harder to maintain and test

  • Deployment needs Server-Restart

  • Not available on Aura

  • No EXPLAIN or PROFILE

Starting point

Thank you for listening