Статьи

Глубокое погружение при полнотекстовой индексации с Neo4j

В предыдущем сообщении в блоге я объяснил различия различных типов индексов, доступных в Neo4j. Общим требованием для многих проектов является использование полнотекстовых индексов. В текущих версиях Neo4j (2.1.5 на данный момент) это может быть достигнуто только с использованием ручных индексов.

В этой статье я хочу объяснить, как вы можете использовать специфичные для языка анализаторы для полнотекстовой индексации и как выполнять их поиск по регулярному выражению.

При рассмотрении справочного руководства по полнотекстовой индексации  существует идея предоставления пользовательского класса анализатора путем указания параметра конфигурации analyzerпри создании индекса. Это значение — полное имя класса анализатора. Существует два способа создания индекса вручную, либо с использованием Java API

GraphDatabaseService graphDb = ....
IndexManager indexManager = graphDb.index()
try (Transaction tx = graphDb.beginTx()) {
    Map<String,String> params = Collections.singletonMap("analyzer", 
        "my.package.Analyzer")
    Index index = indexManager.forNodes("myfulltextindex", params);
}

или используя REST API (используя замечательный клиент командной строки httpie http )

http -v -j localhost:7474/db/data/index/node \
   name=myfulltextindex config:='{"analyzer":"my.package.Analyzer"}'

Lucene предоставляет дополнительный набор анализаторов языка. Эти анализаторы имеют некоторые знания о языке, на котором они работают, и используют его для определения слова, см. Http://www.evelix.ch/unternehmen/Blog/evelix/2013/11/inner-workings-of-the-german. -Analyzer-in-Lucene для получения подробной информации о внутренностях GermanAnalyzer. Например, немецкое слово для домов «Häuser» связано с его единственной формой «Haus». Следовательно, запрос «Haus» извлекает все оба случая «Haus» и «Häuser».

Специфичные для языка анализаторы находятся в необязательном jar-файле lucene-analyzers-3.6.2.jar, который не поставляется по умолчанию с Neo4j. Поэтому скопируйте lucene-analyzers-3.6.2.jarв pluginsпапку Neo4j .

Например, при попытке использовать Lucene’s GermanAnalyzer с помощью

http -v -j localhost:7474/db/data/index/node name=fulltext_de \
   config:='{"analyzer":"org.apache.lucene.analysis.de.GermanAnalyzer"}'

вы получаете HTTP-статус 500. Файлы журнала показывают странное исключение java.lang.InstantiationException: org.apache.lucene.analysis.de.GermanAnalyzer. Причиной этого исключения является то, что Neo4j пытается создать экземпляр класса анализатора, используя конструктор по умолчанию noarg. К сожалению, специфичные для языка анализаторы Lucene не имеют такого конструктора, см. Javadocs . Решением для этого является написание тонкого класса анализатора с конструктором по умолчанию. Внутренне этот класс использует предоставленный Lucene анализатор в качестве делегата.

Чтобы упростить процесс настройки, я создал небольшой проект на github под названием neo4j-fti . Содержит упомянутые обертки в упаковке org.neo4j.contrib.fti.analyzersдля всех языков, имеющих анализатор люцена. Он также предоставляет расширение ядра для Neo4j для автоматического создания полнотекстовых индексов с помощью параметра конфигурации. В neo4j.propertiesвам необходимо установить:

fullTextIndexes=fulltext_de:org.neo4j.contrib.fti.analyzers.German,\
    fulltext_en:org.neo4j.contrib.fti.analyzers.English

Кроме того, в этом проекте представлен пример использования регулярного выражения для поиска в индексе. Используя Java API, вам нужно передать Lucene RegexQueryна основе Termхранения вашего регулярного выражения. Этот RegexQueryкласс также не является частью lucene-core, поэтому обязательно поместите его lucene-queriesв папку Neo4j plugins. Этот пример представлен в неуправляемом расширении с использованием следующего фрагмента кода :

try (Transaction tx = graphDatabaseService.beginTx()) {
    IndexManager indexManager = graphDatabaseService.index();
    if (!indexManager.existsForNodes(indexName)) {
        throw new IllegalArgumentException("index " + indexName + " does not exist");
    }
    Index index = indexManager.forNodes(indexName);
    IndexHits hits = index.query(new RegexQuery(new Term(field, regex)));
 
    List result = new ArrayList<>();
    for (Node node: hits) {
        result.add(node.getId());
    }
}

Предполагая, что названный индекс fulltext_deбыл настроен с использованием немецкого анализатора (см. Выше), используйте следующий код, снова используя httpie, чтобы создать узел, добавьте его в полнотекстовый индекс и выполните запрос индекса регулярного выражения:

# create a node
http -j localhost:7474/db/data/cypher query="create (n:Blog {description:'Auf der Straße stehen fünf Häuser'}) return id(n)"
 
# put it to the index:
http -j localhost:7474/db/data/index/node/fulltext_de \
   uri="http://localhost:7474/db/data/node/xxxx" \
   key="description" value="Auf der Straße stehen fünf Häuser"
 
# query the index for words starting with "h" and ending with "s"
http localhost:7474/regex/fulltext_de/description/h.*s