Skip to content

Instantly share code, notes, and snippets.

@Makopo
Created December 19, 2016 13:31
Show Gist options
  • Save Makopo/a308b3d6d86a8ffe01bcd50f08c93628 to your computer and use it in GitHub Desktop.
Save Makopo/a308b3d6d86a8ffe01bcd50f08c93628 to your computer and use it in GitHub Desktop.
Neo4JのCypherで使えるプロシージャの公式サンプルの日本語訳です。 https://github.com/neo4j-examples/neo4j-procedure-template/blob/3.1/src/main/java/example/FullTextIndex.java
package example;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.index.Index;
import org.neo4j.graphdb.index.IndexManager;
import org.neo4j.logging.Log;
import org.neo4j.procedure.*;
import static org.neo4j.helpers.collection.MapUtil.stringMap;
/**
* Neo4jの全文テキストインデックス機能を利用できるようにする方法の例です。
* 2つのプロシージャ、1つはインデックスの更新、1つはラベルやLuceneクエリでの検索です。
*/
public class FullTextIndex
{
// @Context-annotatedフィールドのみがProcedureクラスで利用可能です。
// この静的フィールドには全文テキストインデックスを作成するのに使用する設定が入ります。
private static final Map<String,String> FULL_TEXT =
stringMap( IndexManager.PROVIDER, "lucene", "type", "fulltext" );
// このフィールドでは、このクラスのどれかのプロシージャが呼び出される際に、
// GraphDatabaseServiceがコンテキストとして必要だと定義しています。
@Context
public GraphDatabaseService db;
// これは標準出力ログにメッセージを出力するログインスタンスです。
// 通常は`data/log/console.log`です。
@Context
public Log log;
/**
* このクラスの2つのプロシージャのうちの、最初のプロシージャを定義しています。
* レガシーインデックスでクエリを実行するプロシージャです。
*
* レコードStreamを返します。レコードはプロシージャごとに指定されています。
* このプロシージャは{@link SearchHit} レコードのStreamを返します。
*
* このプロシージャの引数は、{@link Name}でアノテーションされています。
* このプロシージャを呼び出す際に、引数の名前と型が必要です。
* 引数に使用できる型は限られています。以下の通りです。
*
* <ul>
* <li>{@link String}</li>
* <li>{@link Long} または {@code long}</li>
* <li>{@link Double} または {@code double}</li>
* <li>{@link Number}</li>
* <li>{@link Boolean} または {@code boolean}</li>
* <li>{@link java.util.Map} {@link String} のキーと {@link Object} の値で構成されるマップ</li>
* <li>{@link java.util.List} 適切なフィールド型の要素からなるリスト、{@link java.util.List}を含んでいても良い</li>
* <li>{@link Object} 任意の適切なフィールド型</li>
* </ul>
*
* @param label 検索するラベル名
* @param query Lucineクエリ。例えば `name:Brook*` で、`name`プロパティの値が
* `Brook`で始まるものを検索します。利用可能な構文の詳細は、
* Lucene Query Parserドキュメントを参照してください。
* @return 検索で見つかったノード
*/
// TODO: これは回避策。index().forNodes()が読み取り専用でないため。
@Procedure(value = "example.search", mode = Mode.WRITE)
@Description("引数で与えられたインデックスでLuceneクエリを実行し、見つけたノードを返します。")
public Stream<SearchHit> search( @Name("label") String label,
@Name("query") String query )
{
String index = indexName( label );
// インデックスの作成を回避します。そこになければ、探しようがないですから!
if( !db.index().existsForNodes( index ))
{
// ログに記録する方法を紹介する目的で記載
log.debug( "Skipping index query since index does not exist: `%s`", index );
return Stream.empty();
}
// インデックスがあれば、検索して結果を出力レコードに変換します。
return db.index()
.forNodes( index )
.query( query )
.stream()
.map( SearchHit::new );
}
/**
* このクラスの2番目のプロシージャです。ノードのインデックスを更新して、検索可能にします。
* 同じノードを複数回指定しても構いません。インデックスがすでに存在すれば、
* ノードの現在の状態に合うようにインデックスが更新されます。
*
* このプロシージャは概ね {@link #search(String, String)} と同じように動作します。
* 2つの違いがあります。1つ目は、{@link Mode}.WRITEでアノテーションされることです。
* これは、プロシージャでグラフの更新を行いたい場合は必須です。
*
* 2つ目は、Streamではなく{@code void}で返ることです。レコードがなくても空のStreamが
* 返ってくるよりもわかりやすいからという意図です。
*
* @param nodeId インデックスを付与するノードのID
* @param propKeys インデックスを付与するプロパティのリスト。ノードが実際に含んでいるプロパティのみ追加します。
*/
@Procedure(value = "example.index", mode=Mode.WRITE)
@Description("指定したノードIDのノードについて、指定したキーのプロパティをラベルに対するインデックスに追加します。")
public void index( @Name("nodeId") long nodeId,
@Name("properties") List<String> propKeys )
{
Node node = db.getNodeById( nodeId );
// ノードの全てのプロパティを一度にまとめてロードします。
// 結果セットには、ノードに実際にある、`propKeys`のプロパティのみが含まれます。
Set<Map.Entry<String,Object>> properties =
node.getProperties( propKeys.toArray( new String[0] ) ).entrySet();
// 各ラベルにインデックスを貼ります(あくまで例。インデックスを貼る対象をフィルタリングした方が良い)
for ( Label label : node.getLabels() )
{
Index<Node> index = db.index().forNodes( indexName( label.name() ), FULL_TEXT );
// 既にインデックスが貼られているノードの場合、古かったり、
// 重複したりするインデックスを拾ってしまわないように、全部削除しておきます。
index.remove( node );
// その後、全てのプロパティにインデックスを貼ります。
for ( Map.Entry<String,Object> property : properties )
{
index.add( node, property.getKey(), property.getValue() );
}
}
}
/**
* searchプロシージャの出力レコードです。結果を返すプロシージャは、全てレコードの
* Streamとして返します。その中で、レコードはこのように定義します。
* プロシージャが返そうとしているものに合うようにカスタマイズしてあります。
*
* これらのクラスはパブリックのfinalでないフィールドのみ含むことができます。
* これらのフィールドは、以下のいずれかの型でなければなりません。
*
* <ul>
* <li>{@link String}</li>
* <li>{@link Long} または {@code long}</li>
* <li>{@link Double} または {@code double}</li>
* <li>{@link Number}</li>
* <li>{@link Boolean} または {@code boolean}</li>
* <li>{@link org.neo4j.graphdb.Node}</li>
* <li>{@link org.neo4j.graphdb.Relationship}</li>
* <li>{@link org.neo4j.graphdb.Path}</li>
* <li>{@link java.util.Map} {@link String} のキーと {@link Object} の値で構成されるマップ</li>
* <li>{@link java.util.List} 適切なフィールド型の要素からなるリスト、{@link java.util.List}を含んでいても良い</li>
* <li>{@link Object} 任意の適切なフィールド型</li>
* </ul>
*/
public static class SearchHit
{
// レコードは 'nodeId' というただ一つのフィールドで構成されます。
public long nodeId;
public SearchHit( Node node )
{
this.nodeId = node.getId();
}
}
private String indexName( String label )
{
return "label-" + label;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment