Поиск подмножества объектов с использованием Compass / Lucene - PullRequest
1 голос
/ 27 ноября 2009

Я использую плагин с возможностью поиска для Grails (который предоставляет API для Compass, который сам по себе является API поверх Lucene). У меня есть класс Order, который я хотел бы найти, но я не хочу искать все экземпляры Order, только их подмножество. Примерно так:

// This is a Hibernate/GORM call
List<Order> searchableOrders = Customer.findAllByName("Bob").orders

// Now search only these orders with the searchable plugin - something like
searchableOrders.search("name: foo")

В действительности реляционный запрос для получения searchableOrders является более сложным, чем этот, поэтому я не могу выполнить весь запрос (Hibernate + compass) в одиночку. Есть ли способ поиска только по предметам экземпляров определенного класса с использованием Compass / Lucene.

Ответы [ 2 ]

4 голосов
/ 27 января 2010

Один из способов сделать это с помощью пользовательского фильтра. Например, если вы хотите фильтровать по идентификаторам для вашего класса домена, вы бы добавили идентификатор в конфигурацию поиска для класса домена:

static searchable = {
   id name: "id"
}

Тогда вы бы написали свой собственный фильтр (который может быть в [project] / src / java):

import org.apache.lucene.search.Filter;
import java.util.BitSet;
import org.apache.lucene.index.TermDocs;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.IndexReader;

import java.io.IOException;
import java.util.List;

public class IdFilter extends Filter {

    private List<String> ids;

    public IdFilter(List<String> ids) {
        this.ids = ids;
    }

    public BitSet bits(IndexReader reader) throws IOException {
        BitSet bits = new BitSet(reader.maxDoc());
        int[] docs = new int[1];
        int[] freqs = new int[1];
        for( String id : ids ) {
            if (id != null) {
                TermDocs termDocs = reader.termDocs(new Term("id", id ) );
                int count = termDocs.read(docs, freqs);
                if (count == 1) {
                    bits.set(docs[0]);
                }
            }
        }
        return bits;
    }
}

Тогда вы бы добавили фильтр в качестве аргумента для своего поиска (обязательно импортировав класс Filter, если он находится в другом пакете):

def theSearchResult = MyDomainClass.search( 
{
    must( queryString(params.q) )       
}, 
params,
filter: new IdFilter( [ "1" ] ))

Здесь я просто создаю жестко закодированный список с одним значением «1», но вы можете получить список идентификаторов из базы данных, из предыдущего поиска или где-либо еще.

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

2 голосов
/ 27 ноября 2009

Два способа сделать это:

Самым простым с точки зрения реализации является два поиска (один findAll и search) по всем объектам, а затем поиск пересечения между ними. Если вы кешируете результат вызова findAll, то вы действительно должны выполнить только один запрос.

Более «чистый» способ сделать это - убедиться, что индексированные идентификаторы объектов домена были проиндексированы с помощью Searchable, а когда вы получите результат findAll, передайте эти идентификаторы в поисковый запрос, тем самым ограничивая его.

Я не помню синтаксис Lucene на макушке, но вы должны были бы сделать что-то вроде

searchableOrders.search("name: foo AND (ID:4 or ID:5 or ID:8 ...)" )

Вы можете столкнуться с ограничениями размера запроса в Lucene, но я думаю, что есть настройки, которые позволяют вам контролировать длину запроса.

...