A newer version of this documentation is available.

View Latest

Full Text Search (FTS) Using the Go SDK with Couchbase Server

You can use the Full Text Search service (FTS) to create queryable full-text indexes in Couchbase Server.

Couchbase offers Full-text search support, allowing you to search for documents that contain certain words or phrases. In the Go SDK you can search full-text indexes by using the SearchQuery object and executing it with the Bucket.ExecuteSearchQuery() API.

The following example shows how to send a simple Search query (note that in this example the cbft package also has to be imported independently):

import (
  "github.com/couchbase/gocb"
  "github.com/couchbase/gocb/cbft"
  "fmt"
)

// ...
query := gocb.NewSearchQuery("travel-search", cbft.NewMatchQuery("office"))
res, _ := bucket.ExecuteSearchQuery(query)
for _, hit := range res.Hits() {
        fmt.Printf("%s\n", hit.Id)
}

The Bucket.ExecuteSearchQuery() method returns a SearchResultsinterface which provides access to all of the information returned by the query.

Other search result data may be accessed using the various other available methods on the SearchResults interface.

res, _ := bucket.ExecuteSearchQuery(query)
for _, facet := range res.Facets() {
        // ...
}
fmt.Printf("Total Hits: %d", res.Status().Total)

Query Types

Query types may be found inside the gocb/cbft package. The package contains query classes corresponding to those enumerated in Query Types. Query object should be instantiated by using the associated New*Query functions, passing the search term (usually a string) as the first argument, followed by some query modifiers.

It is important to distinguish between query options and general search options. Some options affect the search process in general (such as the Limit, indicating how many results to return) while others only affect a specific query (such as Fuzziness for a given query). Because multiple queries can be combined in a single search operation, query specific options can be specified only in the query object itself, while search options are specified through methods on the SearchQuery object itself.

Query Facets

Query facets may also be added to the general search parameters by using the AddFacet method. The AddFacet method accepts a facet name as well as a facet definiton You can create facets by instantiating a Facet object found in the gocb.cbft package.

query := gocb.NewSearchQuery("travel-search", cbft.NewMatchQuery("office"))
query.AddFacet("countries", cbft.NewTermFacet("country", 5))
res, _ := bucket.ExecuteSearchQuery(query)
fmt.Printf("Total Countries: %d", res.Facets()["countries"].Total)

Detailed Examples

The code example below demonstrates the Go SDK Full Text Search API. The example assumes that Couchbase Server is running, and that the username Administrator and the password password provide authorization for performing the searches. It also assumes that the travel-sample bucket has been installed. For information on creating users and managing roles, see Authorization. For information on installing sample buckets, see Sample Buckets.

The example also assumes the existence of three specific Full Text Indexes, defined on the travel-sample bucket. These are:

  • travel-sample-index-unstored: Uses only the default settings.

  • travel-sample-index-stored: Uses default settings, with one exception: dynamic fields are stored, for the whole index.

  • travel-sample-index-hotel-description: Indexes only the description fields of hotel documents, and disables the default type mapping. The index has a custom analyzer named myUnicodeAnalyzer defined on it: the analyzer’s main characteristic is that it uses the unicode tokenizer.

See Creating Indexes for details on how to create these indexes: they can be created interactively, by means of the Couchbase Web Console; however, there may be greater efficiency in using the Couchbase REST API, as described in the section Index Creation with the REST API. The JSON objects that constitute index-definitions (for inclusion as bodies to the index-creation REST calls), are provided in Demonstration Indexes.

The example features the following Full Text Searches on the travel-sample bucket, within Couchbase Server:

  • Simple Text Query on a single word, targeting an index with dynamic fields unstored.

  • Simple Text Query on Non-Default Index, specifying an index that consists only of content derived from a specific field from a specific document-type.

  • Simple Text Query on Stored Field, specifying the field to be searched; targeting an index with dynamic fields stored, to ensure that field-content is included in the return object.

  • Match Query with Facet, showing how query-results can be displayed either by row or by hits; and demonstrating use of a facet, which provides aggregation-data.

  • DocId Query, showing results of a query on two document IDs.

  • Unanalyzed Term Query with Fuzziness Level of 0, demonstrating how to query on a term with no analysis. Zero fuzziness is specified, to ensure that matches are exact.

  • Unanalyzed Term Query with Fuzziness Level of 2, which is almost identical to the immediately preceding query; but which this time specifies a fuzziness factor of 2, allowing partial matches to be made. The output from this query can be compared to that of the one immediately preceding.

  • Match Phrase Query, using Analysis, for searching on a phrase.

  • Phrase Query, without Analysis, for searching on a phrase without analysis supported.

  • Query String Query, showing how a query string is specified as search-input.

  • Conjunction Query, whereby two separate queries are defined and then run as part of the search, with only the matches returned by both included in the result-object.

  • Wild Card Query, whereby a wildcard is used in the string submitted for the search.

  • Numeric Range Query, whereby minimum and maximum numbers are specified, and matches within the range returned.

  • Regexp Query, whereby a regular expression is submitted, to generate the conditions for successful matches.

package main

import (
	"encoding/json"
	"fmt"

	"github.com/couchbase/gocb"
	"github.com/couchbase/gocb/cbft"
)

func simpleTextQuery(b *gocb.Bucket) {
	indexName := "travel-sample-index-unstored"
	query := gocb.NewSearchQuery(indexName, cbft.NewMatchQuery("swanky")).
		Limit(10)

	result, err := b.ExecuteSearchQuery(query)
	if err != nil {
		fmt.Println()
		fmt.Println("Simple Text Query Error:", err.Error())
	}

	printResult("Simple Text Query", result)
}

func simpleTextQueryOnStoredField(b *gocb.Bucket) {
	indexName := "travel-sample-index-stored"
	query := gocb.NewSearchQuery(indexName,
		cbft.NewMatchQuery("MDG").Field("destinationairport")).
		Limit(10).Highlight(gocb.DefaultHighlightStyle)

	result, err := b.ExecuteSearchQuery(query)
	if err != nil {
		fmt.Println()
		fmt.Println("Simple Text Query on Stored Field Error:", err.Error())
	}

	printResult("Simple Text Query on Stored Field", result)
}

func simpleTextQueryOnNonDefaultIndex(b *gocb.Bucket) {
	indexName := "travel-sample-index-hotel-description"
	query := gocb.NewSearchQuery(indexName, cbft.NewMatchQuery("swanky")).
		Limit(10)

	result, err := b.ExecuteSearchQuery(query)
	if err != nil {
		fmt.Println()
		fmt.Println("Simple Text Query on Non-Default Index Error:", err.Error())
	}

	printResult("Simple Text Query on Non-Default Index", result)
}

func textQueryOnStoredFieldWithFacet(b *gocb.Bucket) {
	indexName := "travel-sample-index-stored"
	query := gocb.NewSearchQuery(indexName, cbft.NewMatchQuery("La Rue Saint Denis!!").
		Field("reviews.content")).Limit(10).Highlight(gocb.DefaultHighlightStyle).
		AddFacet("Countries Referenced", cbft.NewTermFacet("country", 5))

	result, err := b.ExecuteSearchQuery(query)
	if err != nil {
		fmt.Println()
		fmt.Println("Match Query with Facet, Result by Row Error:", err.Error())
	}

	printResult("Match Query with Facet, Result by hits:", result)

	fmt.Println()
	fmt.Println("Match Query with Facet, Result by facet:")
	for _, row := range result.Facets() {
		jRow, err := json.Marshal(row)
		if err != nil {
			fmt.Println("Print Error:", err.Error())
		}
		fmt.Println(string(jRow))
	}
}

func docIdQueryMethod(b *gocb.Bucket) {
	indexName := "travel-sample-index-unstored"
	query := gocb.NewSearchQuery(indexName, cbft.NewDocIdQuery("hotel_26223", "hotel_28960"))

	result, err := b.ExecuteSearchQuery(query)
	if err != nil {
		fmt.Println()
		fmt.Println("DocId Query Error:", err.Error())
	}

	printResult("DocId Query", result)
}

func unAnalyzedTermQuery(b *gocb.Bucket, fuzzinessLevel int) {
	indexName := "travel-sample-index-stored"
	query := gocb.NewSearchQuery(indexName, cbft.NewTermQuery("sushi").Field("reviews.content").
		Fuzziness(fuzzinessLevel)).Limit(50).Highlight(gocb.DefaultHighlightStyle)

	result, err := b.ExecuteSearchQuery(query)
	if err != nil {
		fmt.Println()
		fmt.Printf("Unanalyzed Term Query with Fuzziness Level of %d Error: %s\n", fuzzinessLevel, err.Error())
	}

	printResult(fmt.Sprintf("Unanalyzed Term Query with Fuzziness Level of %d", fuzzinessLevel), result)
}

func matchPhraseQueryOnStoredField(b *gocb.Bucket) {
	indexName := "travel-sample-index-stored"
	query := gocb.NewSearchQuery(indexName,
		cbft.NewMatchPhraseQuery("Eiffel Tower").Field("description")).
		Limit(10).Highlight(gocb.DefaultHighlightStyle)

	result, err := b.ExecuteSearchQuery(query)
	if err != nil {
		fmt.Println()
		fmt.Println("Match Phrase Query, using Analysis Error:", err.Error())
	}

	printResult("Match Phrase Query, using Analysis", result)
}

func unAnalyzedPhraseQuery(b *gocb.Bucket) {
	indexName := "travel-sample-index-stored"
	query := gocb.NewSearchQuery(indexName,
		cbft.NewPhraseQuery("dorm", "rooms").Field("description")).
		Limit(10).Highlight(gocb.DefaultHighlightStyle)

	result, err := b.ExecuteSearchQuery(query)
	if err != nil {
		fmt.Println()
		fmt.Println("Phrase Query, without Analysis Error:", err.Error())
	}

	printResult("Phrase Query, without Analysis", result)
}

func conjunctionQueryMethod(b *gocb.Bucket) {
	indexName := "travel-sample-index-stored"
	firstQuery := cbft.NewMatchQuery("La Rue Saint Denis!!").Field("reviews.content")
	secondQuery := cbft.NewMatchQuery("boutique").Field("description")

	conjunctionQuery := cbft.NewConjunctionQuery(firstQuery, secondQuery)

	result, err := b.ExecuteSearchQuery(gocb.NewSearchQuery(indexName, conjunctionQuery).
		Limit(10).Highlight(gocb.DefaultHighlightStyle))
	if err != nil {
		fmt.Println()
		fmt.Println("Conjunction Query Error:", err.Error())
	}

	printResult("Conjunction Query", result)
}

func queryStringMethod(b *gocb.Bucket) {
	indexName := "travel-sample-index-unstored"
	query := gocb.NewSearchQuery(indexName, cbft.NewQueryStringQuery("description: Imperial")).
		Limit(10)

	result, err := b.ExecuteSearchQuery(query)
	if err != nil {
		fmt.Println()
		fmt.Println("Query String Query Error:", err.Error())
	}

	printResult("Query String Query", result)
}

func wildCardQueryMethod(b *gocb.Bucket) {
	indexName := "travel-sample-index-stored"
	query := gocb.NewSearchQuery(indexName, cbft.NewWildcardQuery("bouti*ue").Field("description")).
		Limit(10).Highlight(gocb.DefaultHighlightStyle)

	result, err := b.ExecuteSearchQuery(query)
	if err != nil {
		fmt.Println()
		fmt.Println("Wild Card Query Error:", err.Error())
	}

	printResult("Wild Card Query", result)
}

func numericRangeQueryMethod(b *gocb.Bucket) {
	indexName := "travel-sample-index-unstored"
	query := gocb.NewSearchQuery(indexName, cbft.NewNumericRangeQuery().
		Min(10100, true).Max(10200, true).Field("id")).
		Limit(10).Highlight(gocb.DefaultHighlightStyle)

	result, err := b.ExecuteSearchQuery(query)
	if err != nil {
		fmt.Println()
		fmt.Println("Wild Card Query Error:", err.Error())
	}

	printResult("Wild Card Query", result)
}

func regexpQueryMethod(b *gocb.Bucket) {
	indexName := "travel-sample-index-stored"
	query := gocb.NewSearchQuery(indexName, cbft.NewRegexpQuery("[a-z]").
		Field("description")).Limit(10).Highlight(gocb.DefaultHighlightStyle)

	result, err := b.ExecuteSearchQuery(query)
	if err != nil {
		fmt.Println()
		fmt.Println("Regexp Query Error:", err.Error())
	}

	printResult("Regexp Query", result)
}

func printResult(label string, results gocb.SearchResults) {
	fmt.Println()
	fmt.Println("= = = = = = = = = = = = = = = = = = = = = = =")
	fmt.Println("= = = = = = = = = = = = = = = = = = = = = = =")
	fmt.Println()
	fmt.Println(label)
	fmt.Println()

	for _, row := range results.Hits() {
		jRow, err := json.Marshal(row)
		if err != nil {
			fmt.Println("Print Error:", err.Error())
		}
		fmt.Println(string(jRow))
	}
}

func main() {
	cluster, err := gocb.Connect("couchbase://10.111.180.101")
	if err != nil {
		panic("error conencting to cluster:" + err.Error())
	}
	cluster.Authenticate(gocb.PasswordAuthenticator{
		Username: "Administrator",
		Password: "password",
	})
	bucket, err := cluster.OpenBucket("travel-sample", "")
	if err != nil {
		panic("error opening bucket:" + err.Error())
	}

	simpleTextQuery(bucket)
	simpleTextQueryOnStoredField(bucket)
	simpleTextQueryOnNonDefaultIndex(bucket)
	textQueryOnStoredFieldWithFacet(bucket)
	docIdQueryMethod(bucket)
	unAnalyzedTermQuery(bucket, 0)
	unAnalyzedTermQuery(bucket, 2)
	matchPhraseQueryOnStoredField(bucket)
	unAnalyzedPhraseQuery(bucket)
	conjunctionQueryMethod(bucket)
	queryStringMethod(bucket)
	wildCardQueryMethod(bucket)
	numericRangeQueryMethod(bucket)
	regexpQueryMethod(bucket)

	cluster.Close()
}