Neo4jファンクション作成チュートリアル: パスの文字列表記ファンクションの作成

  • 0
    いいね
  • 0
    コメント

    Neo4jには、UserFunctionという機能があり、ユーザーが独自にファンクション(関数)を定義できます。DBMSのCREATE FUNCTION相当のものです。Neo4jのファンクションは、SQLでは書けません。Javaで書く必要があります。本記事では、公式サンプルよりも若干実務に役立つファンクションを作りながら、解説していきます。

    本記事では、Cypherを知っているという前提で説明しています。

    作成するファンクション(パスの文字列表記)

    次のような感じで使う、example.pathjoin()を作ります。

    MATCH (a{name:'あ'})
    MATCH (o{name:'お'})
    MATCH p=(a)-[*]->(o)
    RETURN example.pathjoin(p,'name','>')
    

    上記の例では、nameが「あ」のノードと、nameが「お」のノードの間のパスを>で繋げた文字列(例:あ>い>う>え>お)を返します。

    Javaでの作業

    テンプレートプロジェクトのダウンロード

    Neo4j Procedure Templateをダウンロードします。以降の作業は、このプロジェクトに対して実施します。

    $ git clone --depth 1 https://github.com/neo4j-examples/neo4j-procedure-template
    

    Javaクラスの作成

    適当なクラスを作成します。クラス名・パッケージ名は何でも結構です。

    メソッドの作成

    作成したクラスの中に、メソッドを追加します。@UserFunctionのJavadocページの中の、「Input declaration」と「Output declaration」を参照し、引数と復帰値に使って良い型を確認します。

    今回は、org.neo4j.graphdb.Path(p)、String('name')、String('>')を引数に取り、生成された文字列をStringで返すことにします。メソッド定義は、次のようになります。

    public String pathJoin(Path p, String key, String delimiter)
    

    アノテーションの追加

    Neo4jに、Cypherのファンクションと認識してもらえるように、UserFunction, Descriptionアノテーションをメソッドに、Nameアノテーションを引数に追加します。

    @UserFunction(メソッド)

    アノテーションの引数には、Cypherで使用するファンクション名を指定します。引数なしで@UserFunctionとだけ書くと、パッケージ名.メソッド名がCypherでのファンクション名になります。

    @UserFunction("example.pathjoin")
    

    @Description(メソッド)

    call dbms.functionsdescription欄に表示される文字列を指定します。

    @Description("指定したPathオブジェクトについて、各ノードのkeyの値をdemiliterで連結し、文字列で返します。")
    

    @Name(引数)

    call dbms.functionssignature欄に表示される文字列を指定します。defaultValueを指定すると、引数を省略した場合のデフォルト値となります。

    public String pathJoin(@Name("path") Path p,
                           @Name(value = "key in property", defaultValue = "name") String key,
                           @Name(value = "delimiter", defaultValue = ",") String delimiter) {
    

    中身の実装

    PathNodeなど、使うクラスやインターフェースのJavadocを参照しながら実装していきます。完成すると、次のようになります。

    SampleFunction.java
    package example;
    
    import org.neo4j.graphdb.Node;
    import org.neo4j.graphdb.Path;
    import org.neo4j.procedure.Description;
    import org.neo4j.procedure.Name;
    import org.neo4j.procedure.UserFunction;
    
    import java.util.List;
    import java.util.stream.Collectors;
    
    public class SampleFunction {
    
        @UserFunction("example.pathjoin")
        @Description("指定したPathオブジェクトについて、各ノードのkeyの値をdemiliterで連結し、文字列で返します。")
         public String pathJoin(@Name("path") Path p,
                               @Name(value = "key in property", defaultValue = "name") String key,
                               @Name(value = "delimiter", defaultValue = ",") String delimiter) {
            return ((List<Node>) p.nodes())
                    .stream().map(
                    node -> node.getProperty(key).toString()
                ).collect(Collectors.joining(delimiter));
        }
    }
    

    話を単純にするため、対象とするプロパティのキー(例:name)が必ずある前提で作成しています。

    Java8を前提とした実装となっていますので、Java7でNeo4jを動作させている場合は適宜読み替えてください。

    Jarファイルの作成

    テンプレートに付属のpom.xmlを使って、Mavenでビルドします。

    $ mvn package
    

    targetディレクトリにJarファイルが作成されます。

    デフォルトでprocedure-template-1.0.0-SNAPSHOT.jarという名前で作成されますので、気に入らない場合はpom.xml内の<artifactId><version>を変更してください。

    Neo4jでの作業

    プラグインJarファイルの配置

    先ほど作成したJarファイルを、Neo4jインストールディレクトリのplugins/ディレクトリの中に置きます。

    Neo4jの再起動

    Neo4jを再起動します。

    ファンクションの存在確認

    Webインターフェース(http://localhost:7474) を開き、以下を入力します。

    call dbms.functions
    

    次のように、ファンクションの凡例と説明が表示されます。

    dbms.functions

    example.joinはテンプレートに最初からあるファンクションです。

    テスト

    テストデータのロード

    Webインターフェースのコンソールに、以下のリンク先のCypherクエリをコピー&ペーストします。
    https://gist.github.com/Makopo/59c5479214769c4fc476715c3b85f8c7

    図のようなツリー状のデータができあがります。

    データ

    クエリ実行

    まずは、「あ」から「お」までのパスの要素を、>で繫いでみます。

    MATCH (a{name:'あ'})
    MATCH (o{name:'お'})
    MATCH p=(a)-[*]->(o)
    RETURN example.pathjoin(p,'name','>')
    

    結果1

    「あ」から「お」までのパスは1個しかありませんので、結果は1個だけ返ってきます。

    次に、「あ」から任意の要素までのパスの要素を、で繫いでみます。

    MATCH (a{name:'あ'})
    MATCH p=(a)-[*]->()
    RETURN example.pathjoin(p,'name','→')
    

    結果2

    「あ」を起点として繋がっている要素は全部で11個。それぞれのパスについて、ファンクションを使って文字列表記に変換されたものが表示されています。