Searching
Config
To start working with a Bluge index, one begins by creating the appropriate Config
structure. To create a default config structure for working with an index stored on the filesystem use the following:
config := bluge.DefaultConfig(path)
Readers
Next, to search an index with this config structure, we need to open a Reader
, which can be done as follows:
reader, err := bluge.OpenReader(beerCfg)
if err != nil {
log.Fatalf("unable to open reader: %v", err)
}
The Reader
represents a stable snapshot of the index a point in time. This means that changes made to the index after the reader is obtained never affect the results returned by this reader. This also means that this Reader
is holding onto resources and MUST be closed when it is no longer needed.
err = reader.Close()
if err != nil {
log.Fatalf("error closing reader: %v", err)
}
Queries
Now that we have a Reader
ready for searching, we need to describe to Bluge what documents we’re looking for. This is done with an object satisfying Bluge’s Query
interface. A common query used for finding text matches in the index is a MatchQuery
:
q := NewMatchQuery("bluge")
This creates a MatchQuery looking for the term bluge
. We have not specified a field restriction, so this will search the DefaultSearchField
in the Config
structure. To explicitly restrict this to a particular field we can use:
q.SetField("name")
Search Requests
While the Query
describes what we’re looking for, the Request
describes how we want the search executed. For example, do we want the top 10 hits?
req := NewTopNSearch(10, q)
The first argument is the number of hits we want, and the second is the Query
.
Alternatively, maybe want to see page 2 of the results by skipping over the first 10 hits?
req := NewTopNSearch(10, q).SetFrom(10)
Or do we want to see all matches?
req := NewAllMatches(q)
All of these return a structure satisfying Bluge’s SearchRequest
interface, so we work with them the same. To execute the search:
dmi, err := reader.Search(context.Background(), req)
if err != nil {
log.Fatalf("error executing search: %v", err)
}
The first argument is a context.Context
, which allows you to cancel (or timout) a search if needed. The second argument is the SearchRequest
we just built. Executing a search returns a DocumentMatchIterator
, which allows us to iterate through the DocumentMatches
that satisfy the request.
Iterating through Results
The standard pattern to iterate through search results looks like this:
next, err := dmi.Next()
for err == nil && next != nil {
err = next.VisitStoredFields(func(field string, value []byte) bool {
if field == "_id" {
fmt.Println(string(value))
}
return true
})
if err != nil {
log.Fatalf("error accessing stored fields: %v", err)
}
next, err = dmi.Next()
}
if err != nil {
log.Fatalf("error iterating results: %v", err)
}
Each time we invoke Next()
we are returned the next DocumentMatch
. DocumentMatches
each have an identifying number, however this number ONLY has meaning inside the context of this reader’s snapshot.
Here we see the VisitStoreFields
helper method being used to invoke a callback for each stored field. In this example, we only look for a field named _id
which allows us to print the documents actual identifier.
After the last hit has been seen by the iterator, nil
is returned by Next()
.
Standard Aggregations
Often it is desirable to return a set of standard aggregations along with the search results. These include: - Total Number Documents that matched the Query (not the number returned by this request) - Maximum score of all matches (not the highest returned by this request) - Search Duration (time.Duration to execute this request)
To include this information, add the following to your SearchRequest
:
req := bluge.NewTopNSearch(10, q).
WithStandardAggregations()
Then, after iterating through the all of the DocumentMatches
you can access the values from the Aggregations
structure:
total := dmi.Aggregations().Count()
maxScore := dmi.Aggregations().Metric("max_score")
duration := dmi.Aggregations().Duration()
For more information, see the page on Aggregations
Near-Real-Time Readers
If your application already has a Writer
open to modify an index, you can get a reader using a method on the Writer:
reader, err := writer.Reader()
if err != nil {
log.Fatalf("error accessing reader: %v", err)
}
This Reader
is a special reader that has access to portions of the index not yet persisted to disk.