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.functions
のdescription
欄に表示される文字列を指定します。
@Description("指定したPathオブジェクトについて、各ノードのkeyの値をdemiliterで連結し、文字列で返します。")
@Name(引数)
call dbms.functions
のsignature
欄に表示される文字列を指定します。defaultValue
を指定すると、引数を省略した場合のデフォルト値となります。
public String pathJoin(@Name("path") Path p,
@Name(value = "key in property", defaultValue = "name") String key,
@Name(value = "delimiter", defaultValue = ",") String delimiter) {
中身の実装
PathやNodeなど、使うクラスやインターフェースのJavadocを参照しながら実装していきます。完成すると、次のようになります。
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
次のように、ファンクションの凡例と説明が表示されます。
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個だけ返ってきます。
次に、「あ」から任意の要素までのパスの要素を、→
で繫いでみます。
MATCH (a{name:'あ'})
MATCH p=(a)-[*]->()
RETURN example.pathjoin(p,'name','→')
「あ」を起点として繋がっている要素は全部で11個。それぞれのパスについて、ファンクションを使って文字列表記に変換されたものが表示されています。