func (ixReader *IndexReader) Search(query *Query, offset, limit uint, idField string, contentField string, includeMatchedTerms bool) (uint, []*SearchResult) { // Should probably have some sort // of `Results` object/iterator so that we don't have to specify // offset/limit and where I can attach matched terms to the result. lIdField, lContentField := cb_newf(idField), cb_newf(contentField) // total hack, need to return more than one field defer C.DECREF(lIdField) defer C.DECREF(lContentField) hits := C.LucyIxSearcherHits(ixReader.lucySearcher, query.lucyQuery, C.uint32_t(offset), C.uint32_t(limit), nil) defer C.DECREF(hits) totalNumHits := uint(C.LucyHitsTotal(hits)) num2Return := minUInt(limit, totalNumHits) results := make([]*SearchResult, num2Return) var hit *C.LucyHitDoc compiler := C.LucyQueryMakeCompiler(query.lucyQuery, ixReader.lucySearcher, 1.0, false) defer C.DECREF(compiler) matchedTerms := func(docId C.int32_t, result *SearchResult) { docVec := C.LucyIxSearchFetchDocVec(ixReader.lucySearcher, docId) defer C.DECREF(docVec) spans := C.LucyCompilerHighlightSpans(compiler, ixReader.lucySearcher, docVec, lContentField) defer C.DECREF(spans) spanCnt := C.VaGetSize(spans) if spanCnt == 0 { // should never get here, but just in case... return } result.MatchedTerms = make([]string, spanCnt) var i C.uint32_t for i = 0; i < spanCnt; i++ { span := C.VaFetch(spans, i) offset := C.LucySpanGetOffset(span) length := C.LucySpanGetLength(span) result.MatchedTerms[i] = string([]rune(result.Text)[offset : offset+length]) } // make terms unique? result.MatchedTerms = set(result.MatchedTerms) } var i uint for i = 0; i < num2Return; i++ { hit = C.LucyHitsNext(hits) if hit == nil { break } docId := C.LucyHitDocGetDocId(hit) contentValue := cb_ptr2char(C.LucyHitDocExtract(hit, lContentField, nil)) // do i need to free this idValue := cb_ptr2char(C.LucyHitDocExtract(hit, lIdField, nil)) // do i need to free this results[i] = &SearchResult{ Id: C.GoString(idValue), Text: C.GoString(contentValue), Score: float32(C.LucyHitDocGetScore(hit)), } if includeMatchedTerms { matchedTerms(docId, results[i]) } C.DECREF(hit) } return totalNumHits, results }
// This will need to be a bit more generic, // but for testing this will work fine. func (search *Search) GetSearcher() IndexReader { idxLocation := cb_newf(search.Location) search.lucySearcher = C.LucyIxSearcherNew(idxLocation) C.DECREF(idxLocation) return func(q string, field string, offset, limit uint) (uint, []string) { query := cb_new_from_utf8(q) getField := cb_newf(field) hits := C.LucyIxSearcherHits(search.lucySearcher, query, C.uint32_t(offset), C.uint32_t(limit), nil) totalNumHits := uint(C.LucyHitsTotal(hits)) requestedNumHits := minUInt(limit, totalNumHits) results := make([]string, requestedNumHits) var hit *C.LucyHitDoc for i := uint(0); i < requestedNumHits; i++ { hit = C.LucyHitsNext(hits) if hit == nil { break } value_cb := C.LucyHitDocExtract(hit, getField, nil) // do i need to free this value := cb_ptr2char(value_cb) // do i need to free this results[i] = C.GoString(value) C.DECREF(hit) } C.DECREF(query) C.DECREF(getField) C.DECREF(hits) return totalNumHits, results } }