Merge pull request #444 from mschoch/bigapimove
Move IndexMapping and Query into separate packages
This commit is contained in:
commit
913efda741
|
@ -14,5 +14,6 @@ query_string.y.go.tmp
|
|||
vendor/**
|
||||
!vendor/manifest
|
||||
/y.output
|
||||
/search/query/y.output
|
||||
*.test
|
||||
tags
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"io/ioutil"
|
||||
|
||||
"github.com/blevesearch/bleve"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -38,7 +39,7 @@ var createCmd = &cobra.Command{
|
|||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
var mapping *bleve.IndexMapping
|
||||
var mapping mapping.IndexMapping
|
||||
var err error
|
||||
mapping, err = buildMapping()
|
||||
if err != nil {
|
||||
|
@ -53,8 +54,8 @@ var createCmd = &cobra.Command{
|
|||
},
|
||||
}
|
||||
|
||||
func buildMapping() (*bleve.IndexMapping, error) {
|
||||
mapping := bleve.NewIndexMapping()
|
||||
func buildMapping() (mapping.IndexMapping, error) {
|
||||
mapping := mapping.NewIndexMapping()
|
||||
if mappingPath != "" {
|
||||
mappingBytes, err := ioutil.ReadFile(mappingPath)
|
||||
if err != nil {
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/blevesearch/bleve"
|
||||
"github.com/blevesearch/bleve/search/query"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -55,27 +56,27 @@ var queryCmd = &cobra.Command{
|
|||
},
|
||||
}
|
||||
|
||||
func buildQuery(args []string) bleve.Query {
|
||||
var query bleve.Query
|
||||
func buildQuery(args []string) query.Query {
|
||||
var q query.Query
|
||||
switch qtype {
|
||||
case "prefix":
|
||||
pquery := bleve.NewPrefixQuery(strings.Join(args[1:], " "))
|
||||
if qfield != "" {
|
||||
pquery.SetField(qfield)
|
||||
}
|
||||
query = pquery
|
||||
q = pquery
|
||||
case "term":
|
||||
pquery := bleve.NewTermQuery(strings.Join(args[1:], " "))
|
||||
if qfield != "" {
|
||||
pquery.SetField(qfield)
|
||||
}
|
||||
query = pquery
|
||||
q = pquery
|
||||
default:
|
||||
// build a search with the provided parameters
|
||||
queryString := strings.Join(args[1:], " ")
|
||||
query = bleve.NewQueryStringQuery(queryString)
|
||||
q = bleve.NewQueryStringQuery(queryString)
|
||||
}
|
||||
return query
|
||||
return q
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -15,7 +15,6 @@ import (
|
|||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/blevesearch/bleve/analysis/datetime_parsers/datetime_optional"
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/index/store/gtreap"
|
||||
"github.com/blevesearch/bleve/index/upside_down"
|
||||
|
@ -31,7 +30,6 @@ type configuration struct {
|
|||
DefaultKVStore string
|
||||
DefaultMemKVStore string
|
||||
DefaultIndexType string
|
||||
QueryDateTimeParser string
|
||||
SlowSearchLogThreshold time.Duration
|
||||
analysisQueue *index.AnalysisQueue
|
||||
}
|
||||
|
@ -68,9 +66,6 @@ func init() {
|
|||
// default index
|
||||
Config.DefaultIndexType = upside_down.Name
|
||||
|
||||
// default query date time parser
|
||||
Config.QueryDateTimeParser = datetime_optional.Name
|
||||
|
||||
bootDuration := time.Since(bootStart)
|
||||
bleveExpVar.Add("bootDuration", int64(bootDuration))
|
||||
indexStats = NewIndexStats()
|
||||
|
|
32
error.go
32
error.go
|
@ -15,11 +15,6 @@ const (
|
|||
ErrorIndexPathDoesNotExist
|
||||
ErrorIndexMetaMissing
|
||||
ErrorIndexMetaCorrupt
|
||||
ErrorDisjunctionFewerThanMinClauses
|
||||
ErrorBooleanQueryNeedsMustOrShouldOrNotMust
|
||||
ErrorNumericQueryNoBounds
|
||||
ErrorPhraseQueryNoTerms
|
||||
ErrorUnknownQueryType
|
||||
ErrorUnknownStorageType
|
||||
ErrorIndexClosed
|
||||
ErrorAliasMulti
|
||||
|
@ -38,20 +33,15 @@ func (e Error) Error() string {
|
|||
}
|
||||
|
||||
var errorMessages = map[Error]string{
|
||||
ErrorIndexPathExists: "cannot create new index, path already exists",
|
||||
ErrorIndexPathDoesNotExist: "cannot open index, path does not exist",
|
||||
ErrorIndexMetaMissing: "cannot open index, metadata missing",
|
||||
ErrorIndexMetaCorrupt: "cannot open index, metadata corrupt",
|
||||
ErrorDisjunctionFewerThanMinClauses: "disjunction query has fewer than the minimum number of clauses to satisfy",
|
||||
ErrorBooleanQueryNeedsMustOrShouldOrNotMust: "boolean query must contain at least one must or should or not must clause",
|
||||
ErrorNumericQueryNoBounds: "numeric range query must specify min or max",
|
||||
ErrorPhraseQueryNoTerms: "phrase query must contain at least one term",
|
||||
ErrorUnknownQueryType: "unknown query type",
|
||||
ErrorUnknownStorageType: "unknown storage type",
|
||||
ErrorIndexClosed: "index is closed",
|
||||
ErrorAliasMulti: "cannot perform single index operation on multiple index alias",
|
||||
ErrorAliasEmpty: "cannot perform operation on empty alias",
|
||||
ErrorUnknownIndexType: "unknown index type",
|
||||
ErrorEmptyID: "document ID cannot be empty",
|
||||
ErrorIndexReadInconsistency: "index read inconsistency detected",
|
||||
ErrorIndexPathExists: "cannot create new index, path already exists",
|
||||
ErrorIndexPathDoesNotExist: "cannot open index, path does not exist",
|
||||
ErrorIndexMetaMissing: "cannot open index, metadata missing",
|
||||
ErrorIndexMetaCorrupt: "cannot open index, metadata corrupt",
|
||||
ErrorUnknownStorageType: "unknown storage type",
|
||||
ErrorIndexClosed: "index is closed",
|
||||
ErrorAliasMulti: "cannot perform single index operation on multiple index alias",
|
||||
ErrorAliasEmpty: "cannot perform operation on empty alias",
|
||||
ErrorUnknownIndexType: "unknown index type",
|
||||
ErrorEmptyID: "document ID cannot be empty",
|
||||
ErrorIndexReadInconsistency: "index read inconsistency detected",
|
||||
}
|
||||
|
|
120
examples_test.go
120
examples_test.go
|
@ -15,11 +15,12 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/highlight/highlighters/ansi"
|
||||
)
|
||||
|
||||
var mapping *IndexMapping
|
||||
var indexMapping mapping.IndexMapping
|
||||
var example_index Index
|
||||
var err error
|
||||
|
||||
|
@ -43,8 +44,8 @@ func TestMain(m *testing.M) {
|
|||
}
|
||||
|
||||
func ExampleNew() {
|
||||
mapping = NewIndexMapping()
|
||||
example_index, err = New("path_to_index", mapping)
|
||||
indexMapping = NewIndexMapping()
|
||||
example_index, err = New("path_to_index", indexMapping)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -371,11 +372,11 @@ func ExampleNewSearchRequest() {
|
|||
}
|
||||
|
||||
func ExampleNewBooleanQuery() {
|
||||
must := make([]Query, 1)
|
||||
mustNot := make([]Query, 1)
|
||||
must[0] = NewMatchQuery("one")
|
||||
mustNot[0] = NewMatchQuery("great")
|
||||
query := NewBooleanQuery(must, nil, mustNot)
|
||||
must := NewMatchQuery("one")
|
||||
mustNot := NewMatchQuery("great")
|
||||
query := NewBooleanQuery()
|
||||
query.AddMust(must)
|
||||
query.AddMustNot(mustNot)
|
||||
searchRequest := NewSearchRequest(query)
|
||||
searchResults, err := example_index.Search(searchRequest)
|
||||
if err != nil {
|
||||
|
@ -387,40 +388,10 @@ func ExampleNewBooleanQuery() {
|
|||
// document id 1
|
||||
}
|
||||
|
||||
func ExampleNewBooleanQueryMinShould() {
|
||||
should := make([]Query, 2)
|
||||
should[0] = NewMatchQuery("great")
|
||||
should[1] = NewMatchQuery("one")
|
||||
query := NewBooleanQueryMinShould(nil, should, nil, float64(2))
|
||||
searchRequest := NewSearchRequest(query)
|
||||
searchResults, err := example_index.Search(searchRequest)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(searchResults.Hits[0].ID)
|
||||
// Output:
|
||||
// document id 2
|
||||
}
|
||||
|
||||
func ExampleNewConjunctionQuery() {
|
||||
conjuncts := make([]Query, 2)
|
||||
conjuncts[0] = NewMatchQuery("great")
|
||||
conjuncts[1] = NewMatchQuery("one")
|
||||
query := NewConjunctionQuery(conjuncts)
|
||||
searchRequest := NewSearchRequest(query)
|
||||
searchResults, err := example_index.Search(searchRequest)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(searchResults.Hits[0].ID)
|
||||
// Output:
|
||||
// document id 2
|
||||
}
|
||||
|
||||
func ExampleNewMatchQueryOperator() {
|
||||
query := NewMatchQueryOperator("great one", MatchQueryOperatorAnd)
|
||||
conjunct1 := NewMatchQuery("great")
|
||||
conjunct2 := NewMatchQuery("one")
|
||||
query := NewConjunctionQuery(conjunct1, conjunct2)
|
||||
searchRequest := NewSearchRequest(query)
|
||||
searchResults, err := example_index.Search(searchRequest)
|
||||
if err != nil {
|
||||
|
@ -433,10 +404,9 @@ func ExampleNewMatchQueryOperator() {
|
|||
}
|
||||
|
||||
func ExampleNewDisjunctionQuery() {
|
||||
disjuncts := make([]Query, 2)
|
||||
disjuncts[0] = NewMatchQuery("great")
|
||||
disjuncts[1] = NewMatchQuery("named")
|
||||
query := NewDisjunctionQuery(disjuncts)
|
||||
disjunct1 := NewMatchQuery("great")
|
||||
disjunct2 := NewMatchQuery("named")
|
||||
query := NewDisjunctionQuery(disjunct1, disjunct2)
|
||||
searchRequest := NewSearchRequest(query)
|
||||
searchResults, err := example_index.Search(searchRequest)
|
||||
if err != nil {
|
||||
|
@ -448,66 +418,6 @@ func ExampleNewDisjunctionQuery() {
|
|||
// 2
|
||||
}
|
||||
|
||||
func ExampleNewDisjunctionQueryMin() {
|
||||
disjuncts := make([]Query, 2)
|
||||
disjuncts[0] = NewMatchQuery("great")
|
||||
disjuncts[1] = NewMatchQuery("named")
|
||||
query := NewDisjunctionQueryMin(disjuncts, float64(2))
|
||||
searchRequest := NewSearchRequest(query)
|
||||
searchResults, err := example_index.Search(searchRequest)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(len(searchResults.Hits))
|
||||
// Output:
|
||||
// 0
|
||||
}
|
||||
|
||||
// Examples for Mapping related functions
|
||||
|
||||
func ExampleDocumentMapping_AddSubDocumentMapping() {
|
||||
// adds a document mapping for a property in a document
|
||||
// useful for mapping nested documents
|
||||
documentMapping := NewDocumentMapping()
|
||||
subDocumentMapping := NewDocumentMapping()
|
||||
documentMapping.AddSubDocumentMapping("Property", subDocumentMapping)
|
||||
|
||||
fmt.Println(len(documentMapping.Properties))
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleDocumentMapping_AddFieldMapping() {
|
||||
// you can only add field mapping to those properties which already have a document mapping
|
||||
documentMapping := NewDocumentMapping()
|
||||
subDocumentMapping := NewDocumentMapping()
|
||||
documentMapping.AddSubDocumentMapping("Property", subDocumentMapping)
|
||||
|
||||
fieldMapping := NewTextFieldMapping()
|
||||
fieldMapping.Analyzer = "en"
|
||||
subDocumentMapping.AddFieldMapping(fieldMapping)
|
||||
|
||||
fmt.Println(len(documentMapping.Properties["Property"].Fields))
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleDocumentMapping_AddFieldMappingsAt() {
|
||||
// you can only add field mapping to those properties which already have a document mapping
|
||||
documentMapping := NewDocumentMapping()
|
||||
subDocumentMapping := NewDocumentMapping()
|
||||
documentMapping.AddSubDocumentMapping("NestedProperty", subDocumentMapping)
|
||||
|
||||
fieldMapping := NewTextFieldMapping()
|
||||
fieldMapping.Analyzer = "en"
|
||||
documentMapping.AddFieldMappingsAt("NestedProperty", fieldMapping)
|
||||
|
||||
fmt.Println(len(documentMapping.Properties["NestedProperty"].Fields))
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleSearchRequest_SortBy() {
|
||||
// find docs containing "one", order by Age instead of score
|
||||
query := NewMatchQuery("one")
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/blevesearch/bleve"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
)
|
||||
|
||||
type GetIndexHandler struct {
|
||||
|
@ -42,9 +42,9 @@ func (h *GetIndexHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
rv := struct {
|
||||
Status string `json:"status"`
|
||||
Name string `json:"name"`
|
||||
Mapping *bleve.IndexMapping `json:"mapping"`
|
||||
Status string `json:"status"`
|
||||
Name string `json:"name"`
|
||||
Mapping mapping.IndexMapping `json:"mapping"`
|
||||
}{
|
||||
Status: "ok",
|
||||
Name: indexName,
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/blevesearch/bleve"
|
||||
"github.com/blevesearch/bleve/search/query"
|
||||
)
|
||||
|
||||
// SearchHandler can handle search requests sent over HTTP
|
||||
|
@ -66,10 +67,12 @@ func (h *SearchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
logger.Printf("parsed request %#v", searchRequest)
|
||||
|
||||
// validate the query
|
||||
err = searchRequest.Query.Validate()
|
||||
if err != nil {
|
||||
showError(w, req, fmt.Sprintf("error validating query: %v", err), 400)
|
||||
return
|
||||
if srqv, ok := searchRequest.Query.(query.ValidatableQuery); ok {
|
||||
err = srqv.Validate()
|
||||
if err != nil {
|
||||
showError(w, req, fmt.Sprintf("error validating query: %v", err), 400)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// execute the query
|
||||
|
|
22
index.go
22
index.go
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/blevesearch/bleve/document"
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/index/store"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
|
@ -35,7 +36,7 @@ func (b *Batch) Index(id string, data interface{}) error {
|
|||
return ErrorEmptyID
|
||||
}
|
||||
doc := document.NewDocument(id)
|
||||
err := b.index.Mapping().mapDocument(doc, data)
|
||||
err := b.index.Mapping().MapDocument(doc, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -92,11 +93,6 @@ func (b *Batch) Reset() {
|
|||
// assigns string paths to its fields or values then applies field mappings on
|
||||
// them.
|
||||
//
|
||||
// If the value is a []byte, the indexer attempts to convert it to something
|
||||
// else using the ByteArrayConverter registered as
|
||||
// IndexMapping.ByteArrayConverter. By default, it interprets the value as a
|
||||
// JSON payload and unmarshals it to map[string]interface{}.
|
||||
//
|
||||
// The DocumentMapping used to index a value is deduced by the following rules:
|
||||
// 1) If value implements Classifier interface, resolve the mapping from Type().
|
||||
// 2) If value has a string field or value at IndexMapping.TypeField.
|
||||
|
@ -178,7 +174,7 @@ type Index interface {
|
|||
|
||||
Close() error
|
||||
|
||||
Mapping() *IndexMapping
|
||||
Mapping() mapping.IndexMapping
|
||||
|
||||
Stats() *IndexStat
|
||||
StatsMap() map[string]interface{}
|
||||
|
@ -197,16 +193,10 @@ type Index interface {
|
|||
Advanced() (index.Index, store.KVStore, error)
|
||||
}
|
||||
|
||||
// A Classifier is an interface describing any object
|
||||
// which knows how to identify its own type.
|
||||
type Classifier interface {
|
||||
Type() string
|
||||
}
|
||||
|
||||
// New index at the specified path, must not exist.
|
||||
// The provided mapping will be used for all
|
||||
// Index/Search operations.
|
||||
func New(path string, mapping *IndexMapping) (Index, error) {
|
||||
func New(path string, mapping mapping.IndexMapping) (Index, error) {
|
||||
return newIndexUsing(path, mapping, Config.DefaultIndexType, Config.DefaultKVStore, nil)
|
||||
}
|
||||
|
||||
|
@ -215,7 +205,7 @@ func New(path string, mapping *IndexMapping) (Index, error) {
|
|||
// and will be lost once closed.
|
||||
// The provided mapping will be used for all
|
||||
// Index/Search operations.
|
||||
func NewMemOnly(mapping *IndexMapping) (Index, error) {
|
||||
func NewMemOnly(mapping mapping.IndexMapping) (Index, error) {
|
||||
return newIndexUsing("", mapping, Config.DefaultIndexType, Config.DefaultMemKVStore, nil)
|
||||
}
|
||||
|
||||
|
@ -227,7 +217,7 @@ func NewMemOnly(mapping *IndexMapping) (Index, error) {
|
|||
// The specified kvstore implementation will be used
|
||||
// and the provided kvconfig will be passed to its
|
||||
// constructor.
|
||||
func NewUsing(path string, mapping *IndexMapping, indexType string, kvstore string, kvconfig map[string]interface{}) (Index, error) {
|
||||
func NewUsing(path string, mapping mapping.IndexMapping, indexType string, kvstore string, kvconfig map[string]interface{}) (Index, error) {
|
||||
return newIndexUsing(path, mapping, indexType, kvstore, kvconfig)
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/blevesearch/bleve/document"
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/index/store"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
|
@ -259,7 +260,7 @@ func (i *indexAliasImpl) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (i *indexAliasImpl) Mapping() *IndexMapping {
|
||||
func (i *indexAliasImpl) Mapping() mapping.IndexMapping {
|
||||
i.mutex.RLock()
|
||||
defer i.mutex.RUnlock()
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/blevesearch/bleve/document"
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/index/store"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/numeric_util"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
@ -1269,7 +1270,7 @@ func (i *stubIndex) Close() error {
|
|||
return i.err
|
||||
}
|
||||
|
||||
func (i *stubIndex) Mapping() *IndexMapping {
|
||||
func (i *stubIndex) Mapping() mapping.IndexMapping {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/index/store"
|
||||
"github.com/blevesearch/bleve/index/upside_down"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/registry"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/collectors"
|
||||
|
@ -35,7 +36,7 @@ type indexImpl struct {
|
|||
name string
|
||||
meta *indexMeta
|
||||
i index.Index
|
||||
m *IndexMapping
|
||||
m mapping.IndexMapping
|
||||
mutex sync.RWMutex
|
||||
open bool
|
||||
stats *IndexStat
|
||||
|
@ -49,7 +50,7 @@ func indexStorePath(path string) string {
|
|||
return path + string(os.PathSeparator) + storePath
|
||||
}
|
||||
|
||||
func newIndexUsing(path string, mapping *IndexMapping, indexType string, kvstore string, kvconfig map[string]interface{}) (*indexImpl, error) {
|
||||
func newIndexUsing(path string, mapping mapping.IndexMapping, indexType string, kvstore string, kvconfig map[string]interface{}) (*indexImpl, error) {
|
||||
// first validate the mapping
|
||||
err := mapping.Validate()
|
||||
if err != nil {
|
||||
|
@ -183,7 +184,7 @@ func openIndexUsing(path string, runtimeConfig map[string]interface{}) (rv *inde
|
|||
return nil, err
|
||||
}
|
||||
|
||||
var im IndexMapping
|
||||
var im *mapping.IndexMappingImpl
|
||||
err = json.Unmarshal(mappingBytes, &im)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing mapping JSON: %v\nmapping contents:\n%s", err, string(mappingBytes))
|
||||
|
@ -202,7 +203,7 @@ func openIndexUsing(path string, runtimeConfig map[string]interface{}) (rv *inde
|
|||
return rv, err
|
||||
}
|
||||
|
||||
rv.m = &im
|
||||
rv.m = im
|
||||
indexStats.Register(rv)
|
||||
return rv, err
|
||||
}
|
||||
|
@ -219,7 +220,7 @@ func (i *indexImpl) Advanced() (index.Index, store.KVStore, error) {
|
|||
|
||||
// Mapping returns the IndexMapping in use by this
|
||||
// Index.
|
||||
func (i *indexImpl) Mapping() *IndexMapping {
|
||||
func (i *indexImpl) Mapping() mapping.IndexMapping {
|
||||
return i.m
|
||||
}
|
||||
|
||||
|
@ -239,7 +240,7 @@ func (i *indexImpl) Index(id string, data interface{}) (err error) {
|
|||
}
|
||||
|
||||
doc := document.NewDocument(id)
|
||||
err = i.m.mapDocument(doc, data)
|
||||
err = i.m.MapDocument(doc, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -387,7 +388,7 @@ func (i *indexImpl) SearchInContext(ctx context.Context, req *SearchRequest) (sr
|
|||
} else if facetRequest.DateTimeRanges != nil {
|
||||
// build date range facet
|
||||
facetBuilder := facets.NewDateTimeFacetBuilder(facetRequest.Field, facetRequest.Size)
|
||||
dateTimeParser := i.m.dateTimeParserNamed(i.m.DefaultDateTimeParser)
|
||||
dateTimeParser := i.m.DateTimeParserNamed("")
|
||||
for _, dr := range facetRequest.DateTimeRanges {
|
||||
dr.ParseDates(dateTimeParser)
|
||||
facetBuilder.AddRange(dr.Name, dr.Start, dr.End)
|
||||
|
|
|
@ -29,7 +29,9 @@ import (
|
|||
"github.com/blevesearch/bleve/analysis/analyzers/keyword_analyzer"
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/index/store/null"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/query"
|
||||
)
|
||||
|
||||
func TestCrud(t *testing.T) {
|
||||
|
@ -345,35 +347,15 @@ func TestClosedIndex(t *testing.T) {
|
|||
}
|
||||
|
||||
type slowQuery struct {
|
||||
actual Query
|
||||
actual query.Query
|
||||
delay time.Duration
|
||||
}
|
||||
|
||||
func (s *slowQuery) Boost() float64 {
|
||||
return s.actual.Boost()
|
||||
}
|
||||
|
||||
func (s *slowQuery) SetBoost(b float64) Query {
|
||||
return s.actual.SetBoost(b)
|
||||
}
|
||||
|
||||
func (s *slowQuery) Field() string {
|
||||
return s.actual.Field()
|
||||
}
|
||||
|
||||
func (s *slowQuery) SetField(f string) Query {
|
||||
return s.actual.SetField(f)
|
||||
}
|
||||
|
||||
func (s *slowQuery) Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error) {
|
||||
func (s *slowQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
time.Sleep(s.delay)
|
||||
return s.actual.Searcher(i, m, explain)
|
||||
}
|
||||
|
||||
func (s *slowQuery) Validate() error {
|
||||
return s.actual.Validate()
|
||||
}
|
||||
|
||||
func TestSlowSearch(t *testing.T) {
|
||||
defer func() {
|
||||
err := os.RemoveAll("testidx")
|
||||
|
@ -1130,7 +1112,8 @@ func TestTermVectorArrayPositions(t *testing.T) {
|
|||
}
|
||||
|
||||
// repeat search for this document in Messages field
|
||||
tq2 := NewTermQuery("third").SetField("Messages")
|
||||
tq2 := NewTermQuery("third")
|
||||
tq2.SetField("Messages")
|
||||
tsr = NewSearchRequest(tq2)
|
||||
results, err = index.Search(tsr)
|
||||
if err != nil {
|
||||
|
@ -1297,9 +1280,9 @@ func TestDateTimeFieldMappingIssue287(t *testing.T) {
|
|||
}
|
||||
|
||||
// search range across all docs
|
||||
start := now.Add(-4 * time.Hour).Format(time.RFC3339)
|
||||
end := now.Format(time.RFC3339)
|
||||
sreq := NewSearchRequest(NewDateRangeQuery(&start, &end))
|
||||
start := now.Add(-4 * time.Hour)
|
||||
end := now
|
||||
sreq := NewSearchRequest(NewDateRangeQuery(start, end))
|
||||
sres, err := index.Search(sreq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -1309,9 +1292,9 @@ func TestDateTimeFieldMappingIssue287(t *testing.T) {
|
|||
}
|
||||
|
||||
// search range includes only oldest
|
||||
start = now.Add(-4 * time.Hour).Format(time.RFC3339)
|
||||
end = now.Add(-121 * time.Minute).Format(time.RFC3339)
|
||||
sreq = NewSearchRequest(NewDateRangeQuery(&start, &end))
|
||||
start = now.Add(-4 * time.Hour)
|
||||
end = now.Add(-121 * time.Minute)
|
||||
sreq = NewSearchRequest(NewDateRangeQuery(start, end))
|
||||
sres, err = index.Search(sreq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -1324,9 +1307,9 @@ func TestDateTimeFieldMappingIssue287(t *testing.T) {
|
|||
}
|
||||
|
||||
// search range includes only newest
|
||||
start = now.Add(-61 * time.Minute).Format(time.RFC3339)
|
||||
end = now.Format(time.RFC3339)
|
||||
sreq = NewSearchRequest(NewDateRangeQuery(&start, &end))
|
||||
start = now.Add(-61 * time.Minute)
|
||||
end = now
|
||||
sreq = NewSearchRequest(NewDateRangeQuery(start, end))
|
||||
sres, err = index.Search(sreq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -1380,7 +1363,8 @@ func TestDocumentFieldArrayPositionsBug295(t *testing.T) {
|
|||
}
|
||||
|
||||
// search for it in the messages field
|
||||
tq := NewTermQuery("bleve").SetField("Messages")
|
||||
tq := NewTermQuery("bleve")
|
||||
tq.SetField("Messages")
|
||||
tsr := NewSearchRequest(tq)
|
||||
results, err := index.Search(tsr)
|
||||
if err != nil {
|
||||
|
@ -1454,7 +1438,9 @@ func TestBooleanFieldMappingIssue109(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sreq := NewSearchRequest(NewBoolFieldQuery(true).SetField("Bool"))
|
||||
q := NewBoolFieldQuery(true)
|
||||
q.SetField("Bool")
|
||||
sreq := NewSearchRequest(q)
|
||||
sres, err := index.Search(sreq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -1463,7 +1449,9 @@ func TestBooleanFieldMappingIssue109(t *testing.T) {
|
|||
t.Errorf("expected 1 results, got %d", sres.Total)
|
||||
}
|
||||
|
||||
sreq = NewSearchRequest(NewBoolFieldQuery(false).SetField("Bool"))
|
||||
q = NewBoolFieldQuery(false)
|
||||
q.SetField("Bool")
|
||||
sreq = NewSearchRequest(q)
|
||||
sres, err = index.Search(sreq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -1717,7 +1705,8 @@ func TestBug408(t *testing.T) {
|
|||
t.Fatalf("expected %d documents in index, got %d", numToTest, cnt)
|
||||
}
|
||||
|
||||
q := NewTermQuery(matchUserID).SetField("user_id")
|
||||
q := NewTermQuery(matchUserID)
|
||||
q.SetField("user_id")
|
||||
searchReq := NewSearchRequestOptions(q, math.MaxInt32, 0, false)
|
||||
results, err := index.Search(searchReq)
|
||||
if err != nil {
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
// except in compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
|
||||
import "github.com/blevesearch/bleve/mapping"
|
||||
|
||||
// NewIndexMapping creates a new IndexMapping that will use all the default indexing rules
|
||||
func NewIndexMapping() *mapping.IndexMappingImpl {
|
||||
return mapping.NewIndexMapping()
|
||||
}
|
||||
|
||||
// NewDocumentMapping returns a new document mapping
|
||||
// with all the default values.
|
||||
func NewDocumentMapping() *mapping.DocumentMapping {
|
||||
return mapping.NewDocumentMapping()
|
||||
}
|
||||
|
||||
// NewDocumentStaticMapping returns a new document
|
||||
// mapping that will not automatically index parts
|
||||
// of a document without an explicit mapping.
|
||||
func NewDocumentStaticMapping() *mapping.DocumentMapping {
|
||||
return mapping.NewDocumentStaticMapping()
|
||||
}
|
||||
|
||||
// NewDocumentDisabledMapping returns a new document
|
||||
// mapping that will not perform any indexing.
|
||||
func NewDocumentDisabledMapping() *mapping.DocumentMapping {
|
||||
return mapping.NewDocumentDisabledMapping()
|
||||
}
|
||||
|
||||
// NewTextFieldMapping returns a default field mapping for text
|
||||
func NewTextFieldMapping() *mapping.FieldMapping {
|
||||
return mapping.NewTextFieldMapping()
|
||||
}
|
||||
|
||||
// NewNumericFieldMapping returns a default field mapping for numbers
|
||||
func NewNumericFieldMapping() *mapping.FieldMapping {
|
||||
return mapping.NewNumericFieldMapping()
|
||||
}
|
||||
|
||||
// NewDateTimeFieldMapping returns a default field mapping for dates
|
||||
func NewDateTimeFieldMapping() *mapping.FieldMapping {
|
||||
return mapping.NewDateTimeFieldMapping()
|
||||
}
|
||||
|
||||
// NewBooleanFieldMapping returns a default field mapping for booleans
|
||||
func NewBooleanFieldMapping() *mapping.FieldMapping {
|
||||
return mapping.NewBooleanFieldMapping()
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
// except in compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package mapping
|
||||
|
||||
type customAnalysis struct {
|
||||
CharFilters map[string]map[string]interface{} `json:"char_filters,omitempty"`
|
||||
Tokenizers map[string]map[string]interface{} `json:"tokenizers,omitempty"`
|
||||
TokenMaps map[string]map[string]interface{} `json:"token_maps,omitempty"`
|
||||
TokenFilters map[string]map[string]interface{} `json:"token_filters,omitempty"`
|
||||
Analyzers map[string]map[string]interface{} `json:"analyzers,omitempty"`
|
||||
DateTimeParsers map[string]map[string]interface{} `json:"date_time_parsers,omitempty"`
|
||||
}
|
||||
|
||||
func (c *customAnalysis) registerAll(i *IndexMappingImpl) error {
|
||||
for name, config := range c.CharFilters {
|
||||
_, err := i.cache.DefineCharFilter(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.Tokenizers) > 0 {
|
||||
// put all the names in map tracking work to do
|
||||
todo := map[string]struct{}{}
|
||||
for name := range c.Tokenizers {
|
||||
todo[name] = struct{}{}
|
||||
}
|
||||
registered := 1
|
||||
errs := []error{}
|
||||
// as long as we keep making progress, keep going
|
||||
for len(todo) > 0 && registered > 0 {
|
||||
registered = 0
|
||||
errs = []error{}
|
||||
for name := range todo {
|
||||
config := c.Tokenizers[name]
|
||||
_, err := i.cache.DefineTokenizer(name, config)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
delete(todo, name)
|
||||
registered++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs[0]
|
||||
}
|
||||
}
|
||||
for name, config := range c.TokenMaps {
|
||||
_, err := i.cache.DefineTokenMap(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for name, config := range c.TokenFilters {
|
||||
_, err := i.cache.DefineTokenFilter(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for name, config := range c.Analyzers {
|
||||
_, err := i.cache.DefineAnalyzer(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for name, config := range c.DateTimeParsers {
|
||||
_, err := i.cache.DefineDateTimeParser(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newCustomAnalysis() *customAnalysis {
|
||||
rv := customAnalysis{
|
||||
CharFilters: make(map[string]map[string]interface{}),
|
||||
Tokenizers: make(map[string]map[string]interface{}),
|
||||
TokenMaps: make(map[string]map[string]interface{}),
|
||||
TokenFilters: make(map[string]map[string]interface{}),
|
||||
Analyzers: make(map[string]map[string]interface{}),
|
||||
DateTimeParsers: make(map[string]map[string]interface{}),
|
||||
}
|
||||
return &rv
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
@ -405,7 +405,7 @@ func (dm *DocumentMapping) processProperty(property interface{}, path []string,
|
|||
// automatic indexing behavior
|
||||
|
||||
// first see if it can be parsed by the default date parser
|
||||
dateTimeParser := context.im.dateTimeParserNamed(context.im.DefaultDateTimeParser)
|
||||
dateTimeParser := context.im.DateTimeParserNamed(context.im.DefaultDateTimeParser)
|
||||
if dateTimeParser != nil {
|
||||
parsedDateTime, err := dateTimeParser.ParseDateTime(propertyValueString)
|
||||
if err != nil {
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright (c) 2016 Couchbase, Inc.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
// except in compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package mapping
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Examples for Mapping related functions
|
||||
|
||||
func ExampleDocumentMapping_AddSubDocumentMapping() {
|
||||
// adds a document mapping for a property in a document
|
||||
// useful for mapping nested documents
|
||||
documentMapping := NewDocumentMapping()
|
||||
subDocumentMapping := NewDocumentMapping()
|
||||
documentMapping.AddSubDocumentMapping("Property", subDocumentMapping)
|
||||
|
||||
fmt.Println(len(documentMapping.Properties))
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleDocumentMapping_AddFieldMapping() {
|
||||
// you can only add field mapping to those properties which already have a document mapping
|
||||
documentMapping := NewDocumentMapping()
|
||||
subDocumentMapping := NewDocumentMapping()
|
||||
documentMapping.AddSubDocumentMapping("Property", subDocumentMapping)
|
||||
|
||||
fieldMapping := NewTextFieldMapping()
|
||||
fieldMapping.Analyzer = "en"
|
||||
subDocumentMapping.AddFieldMapping(fieldMapping)
|
||||
|
||||
fmt.Println(len(documentMapping.Properties["Property"].Fields))
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleDocumentMapping_AddFieldMappingsAt() {
|
||||
// you can only add field mapping to those properties which already have a document mapping
|
||||
documentMapping := NewDocumentMapping()
|
||||
subDocumentMapping := NewDocumentMapping()
|
||||
documentMapping.AddSubDocumentMapping("NestedProperty", subDocumentMapping)
|
||||
|
||||
fieldMapping := NewTextFieldMapping()
|
||||
fieldMapping.Analyzer = "en"
|
||||
documentMapping.AddFieldMappingsAt("NestedProperty", fieldMapping)
|
||||
|
||||
fmt.Println(len(documentMapping.Properties["NestedProperty"].Fields))
|
||||
// Output:
|
||||
// 1
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
@ -61,7 +61,7 @@ func NewTextFieldMapping() *FieldMapping {
|
|||
}
|
||||
}
|
||||
|
||||
func newTextFieldMappingDynamic(im *IndexMapping) *FieldMapping {
|
||||
func newTextFieldMappingDynamic(im *IndexMappingImpl) *FieldMapping {
|
||||
rv := NewTextFieldMapping()
|
||||
rv.Store = im.StoreDynamic
|
||||
rv.Index = im.IndexDynamic
|
||||
|
@ -78,7 +78,7 @@ func NewNumericFieldMapping() *FieldMapping {
|
|||
}
|
||||
}
|
||||
|
||||
func newNumericFieldMappingDynamic(im *IndexMapping) *FieldMapping {
|
||||
func newNumericFieldMappingDynamic(im *IndexMappingImpl) *FieldMapping {
|
||||
rv := NewNumericFieldMapping()
|
||||
rv.Store = im.StoreDynamic
|
||||
rv.Index = im.IndexDynamic
|
||||
|
@ -95,7 +95,7 @@ func NewDateTimeFieldMapping() *FieldMapping {
|
|||
}
|
||||
}
|
||||
|
||||
func newDateTimeFieldMappingDynamic(im *IndexMapping) *FieldMapping {
|
||||
func newDateTimeFieldMappingDynamic(im *IndexMappingImpl) *FieldMapping {
|
||||
rv := NewDateTimeFieldMapping()
|
||||
rv.Store = im.StoreDynamic
|
||||
rv.Index = im.IndexDynamic
|
||||
|
@ -112,7 +112,7 @@ func NewBooleanFieldMapping() *FieldMapping {
|
|||
}
|
||||
}
|
||||
|
||||
func newBooleanFieldMappingDynamic(im *IndexMapping) *FieldMapping {
|
||||
func newBooleanFieldMappingDynamic(im *IndexMappingImpl) *FieldMapping {
|
||||
rv := NewBooleanFieldMapping()
|
||||
rv.Store = im.StoreDynamic
|
||||
rv.Index = im.IndexDynamic
|
||||
|
@ -150,7 +150,7 @@ func (fm *FieldMapping) processString(propertyValueString string, pathString str
|
|||
if fm.DateFormat != "" {
|
||||
dateTimeFormat = fm.DateFormat
|
||||
}
|
||||
dateTimeParser := context.im.dateTimeParserNamed(dateTimeFormat)
|
||||
dateTimeParser := context.im.DateTimeParserNamed(dateTimeFormat)
|
||||
if dateTimeParser != nil {
|
||||
parsedDateTime, err := dateTimeParser.ParseDateTime(propertyValueString)
|
||||
if err == nil {
|
||||
|
@ -211,7 +211,7 @@ func (fm *FieldMapping) analyzerForField(path []string, context *walkContext) *a
|
|||
analyzerName = context.im.DefaultAnalyzer
|
||||
}
|
||||
}
|
||||
return context.im.analyzerNamed(analyzerName)
|
||||
return context.im.AnalyzerNamed(analyzerName)
|
||||
}
|
||||
|
||||
func getFieldName(pathString string, path []string, fieldMapping *FieldMapping) string {
|
|
@ -7,7 +7,7 @@
|
|||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
@ -28,98 +28,14 @@ const defaultField = "_all"
|
|||
const defaultAnalyzer = standard_analyzer.Name
|
||||
const defaultDateTimeParser = datetime_optional.Name
|
||||
|
||||
type customAnalysis struct {
|
||||
CharFilters map[string]map[string]interface{} `json:"char_filters,omitempty"`
|
||||
Tokenizers map[string]map[string]interface{} `json:"tokenizers,omitempty"`
|
||||
TokenMaps map[string]map[string]interface{} `json:"token_maps,omitempty"`
|
||||
TokenFilters map[string]map[string]interface{} `json:"token_filters,omitempty"`
|
||||
Analyzers map[string]map[string]interface{} `json:"analyzers,omitempty"`
|
||||
DateTimeParsers map[string]map[string]interface{} `json:"date_time_parsers,omitempty"`
|
||||
}
|
||||
|
||||
func (c *customAnalysis) registerAll(i *IndexMapping) error {
|
||||
for name, config := range c.CharFilters {
|
||||
_, err := i.cache.DefineCharFilter(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.Tokenizers) > 0 {
|
||||
// put all the names in map tracking work to do
|
||||
todo := map[string]struct{}{}
|
||||
for name := range c.Tokenizers {
|
||||
todo[name] = struct{}{}
|
||||
}
|
||||
registered := 1
|
||||
errs := []error{}
|
||||
// as long as we keep making progress, keep going
|
||||
for len(todo) > 0 && registered > 0 {
|
||||
registered = 0
|
||||
errs = []error{}
|
||||
for name := range todo {
|
||||
config := c.Tokenizers[name]
|
||||
_, err := i.cache.DefineTokenizer(name, config)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
delete(todo, name)
|
||||
registered++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs[0]
|
||||
}
|
||||
}
|
||||
for name, config := range c.TokenMaps {
|
||||
_, err := i.cache.DefineTokenMap(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for name, config := range c.TokenFilters {
|
||||
_, err := i.cache.DefineTokenFilter(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for name, config := range c.Analyzers {
|
||||
_, err := i.cache.DefineAnalyzer(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for name, config := range c.DateTimeParsers {
|
||||
_, err := i.cache.DefineDateTimeParser(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newCustomAnalysis() *customAnalysis {
|
||||
rv := customAnalysis{
|
||||
CharFilters: make(map[string]map[string]interface{}),
|
||||
Tokenizers: make(map[string]map[string]interface{}),
|
||||
TokenMaps: make(map[string]map[string]interface{}),
|
||||
TokenFilters: make(map[string]map[string]interface{}),
|
||||
Analyzers: make(map[string]map[string]interface{}),
|
||||
DateTimeParsers: make(map[string]map[string]interface{}),
|
||||
}
|
||||
return &rv
|
||||
}
|
||||
|
||||
// An IndexMapping controls how objects are placed
|
||||
// An IndexMappingImpl controls how objects are placed
|
||||
// into an index.
|
||||
// First the type of the object is determined.
|
||||
// Once the type is know, the appropriate
|
||||
// DocumentMapping is selected by the type.
|
||||
// If no mapping was determined for that type,
|
||||
// a DefaultMapping will be used.
|
||||
type IndexMapping struct {
|
||||
type IndexMappingImpl struct {
|
||||
TypeMapping map[string]*DocumentMapping `json:"types,omitempty"`
|
||||
DefaultMapping *DocumentMapping `json:"default_mapping"`
|
||||
TypeField string `json:"type_field"`
|
||||
|
@ -134,7 +50,7 @@ type IndexMapping struct {
|
|||
}
|
||||
|
||||
// AddCustomCharFilter defines a custom char filter for use in this mapping
|
||||
func (im *IndexMapping) AddCustomCharFilter(name string, config map[string]interface{}) error {
|
||||
func (im *IndexMappingImpl) AddCustomCharFilter(name string, config map[string]interface{}) error {
|
||||
_, err := im.cache.DefineCharFilter(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -144,7 +60,7 @@ func (im *IndexMapping) AddCustomCharFilter(name string, config map[string]inter
|
|||
}
|
||||
|
||||
// AddCustomTokenizer defines a custom tokenizer for use in this mapping
|
||||
func (im *IndexMapping) AddCustomTokenizer(name string, config map[string]interface{}) error {
|
||||
func (im *IndexMappingImpl) AddCustomTokenizer(name string, config map[string]interface{}) error {
|
||||
_, err := im.cache.DefineTokenizer(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -154,7 +70,7 @@ func (im *IndexMapping) AddCustomTokenizer(name string, config map[string]interf
|
|||
}
|
||||
|
||||
// AddCustomTokenMap defines a custom token map for use in this mapping
|
||||
func (im *IndexMapping) AddCustomTokenMap(name string, config map[string]interface{}) error {
|
||||
func (im *IndexMappingImpl) AddCustomTokenMap(name string, config map[string]interface{}) error {
|
||||
_, err := im.cache.DefineTokenMap(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -164,7 +80,7 @@ func (im *IndexMapping) AddCustomTokenMap(name string, config map[string]interfa
|
|||
}
|
||||
|
||||
// AddCustomTokenFilter defines a custom token filter for use in this mapping
|
||||
func (im *IndexMapping) AddCustomTokenFilter(name string, config map[string]interface{}) error {
|
||||
func (im *IndexMappingImpl) AddCustomTokenFilter(name string, config map[string]interface{}) error {
|
||||
_, err := im.cache.DefineTokenFilter(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -202,7 +118,7 @@ func (im *IndexMapping) AddCustomTokenFilter(name string, config map[string]inte
|
|||
// ...
|
||||
// },
|
||||
// })
|
||||
func (im *IndexMapping) AddCustomAnalyzer(name string, config map[string]interface{}) error {
|
||||
func (im *IndexMappingImpl) AddCustomAnalyzer(name string, config map[string]interface{}) error {
|
||||
_, err := im.cache.DefineAnalyzer(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -212,7 +128,7 @@ func (im *IndexMapping) AddCustomAnalyzer(name string, config map[string]interfa
|
|||
}
|
||||
|
||||
// AddCustomDateTimeParser defines a custom date time parser for use in this mapping
|
||||
func (im *IndexMapping) AddCustomDateTimeParser(name string, config map[string]interface{}) error {
|
||||
func (im *IndexMappingImpl) AddCustomDateTimeParser(name string, config map[string]interface{}) error {
|
||||
_, err := im.cache.DefineDateTimeParser(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -222,8 +138,8 @@ func (im *IndexMapping) AddCustomDateTimeParser(name string, config map[string]i
|
|||
}
|
||||
|
||||
// NewIndexMapping creates a new IndexMapping that will use all the default indexing rules
|
||||
func NewIndexMapping() *IndexMapping {
|
||||
return &IndexMapping{
|
||||
func NewIndexMapping() *IndexMappingImpl {
|
||||
return &IndexMappingImpl{
|
||||
TypeMapping: make(map[string]*DocumentMapping),
|
||||
DefaultMapping: NewDocumentMapping(),
|
||||
TypeField: defaultTypeField,
|
||||
|
@ -240,7 +156,7 @@ func NewIndexMapping() *IndexMapping {
|
|||
|
||||
// Validate will walk the entire structure ensuring the following
|
||||
// explicitly named and default analyzers can be built
|
||||
func (im *IndexMapping) Validate() error {
|
||||
func (im *IndexMappingImpl) Validate() error {
|
||||
_, err := im.cache.AnalyzerNamed(im.DefaultAnalyzer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -263,11 +179,11 @@ func (im *IndexMapping) Validate() error {
|
|||
}
|
||||
|
||||
// AddDocumentMapping sets a custom document mapping for the specified type
|
||||
func (im *IndexMapping) AddDocumentMapping(doctype string, dm *DocumentMapping) {
|
||||
func (im *IndexMappingImpl) AddDocumentMapping(doctype string, dm *DocumentMapping) {
|
||||
im.TypeMapping[doctype] = dm
|
||||
}
|
||||
|
||||
func (im *IndexMapping) mappingForType(docType string) *DocumentMapping {
|
||||
func (im *IndexMappingImpl) mappingForType(docType string) *DocumentMapping {
|
||||
docMapping := im.TypeMapping[docType]
|
||||
if docMapping == nil {
|
||||
docMapping = im.DefaultMapping
|
||||
|
@ -276,7 +192,7 @@ func (im *IndexMapping) mappingForType(docType string) *DocumentMapping {
|
|||
}
|
||||
|
||||
// UnmarshalJSON offers custom unmarshaling with optional strict validation
|
||||
func (im *IndexMapping) UnmarshalJSON(data []byte) error {
|
||||
func (im *IndexMappingImpl) UnmarshalJSON(data []byte) error {
|
||||
|
||||
var tmp map[string]json.RawMessage
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
|
@ -367,7 +283,7 @@ func (im *IndexMapping) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (im *IndexMapping) determineType(data interface{}) string {
|
||||
func (im *IndexMappingImpl) determineType(data interface{}) string {
|
||||
// first see if the object implements Classifier
|
||||
classifier, ok := data.(Classifier)
|
||||
if ok {
|
||||
|
@ -383,7 +299,7 @@ func (im *IndexMapping) determineType(data interface{}) string {
|
|||
return im.DefaultType
|
||||
}
|
||||
|
||||
func (im *IndexMapping) mapDocument(doc *document.Document, data interface{}) error {
|
||||
func (im *IndexMappingImpl) MapDocument(doc *document.Document, data interface{}) error {
|
||||
docType := im.determineType(data)
|
||||
docMapping := im.mappingForType(docType)
|
||||
walkContext := im.newWalkContext(doc, docMapping)
|
||||
|
@ -403,12 +319,12 @@ func (im *IndexMapping) mapDocument(doc *document.Document, data interface{}) er
|
|||
|
||||
type walkContext struct {
|
||||
doc *document.Document
|
||||
im *IndexMapping
|
||||
im *IndexMappingImpl
|
||||
dm *DocumentMapping
|
||||
excludedFromAll []string
|
||||
}
|
||||
|
||||
func (im *IndexMapping) newWalkContext(doc *document.Document, dm *DocumentMapping) *walkContext {
|
||||
func (im *IndexMappingImpl) newWalkContext(doc *document.Document, dm *DocumentMapping) *walkContext {
|
||||
return &walkContext{
|
||||
doc: doc,
|
||||
im: im,
|
||||
|
@ -422,7 +338,7 @@ func (im *IndexMapping) newWalkContext(doc *document.Document, dm *DocumentMappi
|
|||
// provided path, if one exists and it has an explicit analyzer
|
||||
// that is returned
|
||||
// nil should be an acceptable return value meaning we don't know
|
||||
func (im *IndexMapping) analyzerNameForPath(path string) string {
|
||||
func (im *IndexMappingImpl) AnalyzerNameForPath(path string) string {
|
||||
// first we look for explicit mapping on the field
|
||||
for _, docMapping := range im.TypeMapping {
|
||||
analyzerName := docMapping.analyzerNameForPath(path)
|
||||
|
@ -452,7 +368,7 @@ func (im *IndexMapping) analyzerNameForPath(path string) string {
|
|||
return im.DefaultAnalyzer
|
||||
}
|
||||
|
||||
func (im *IndexMapping) analyzerNamed(name string) *analysis.Analyzer {
|
||||
func (im *IndexMappingImpl) AnalyzerNamed(name string) *analysis.Analyzer {
|
||||
analyzer, err := im.cache.AnalyzerNamed(name)
|
||||
if err != nil {
|
||||
logger.Printf("error using analyzer named: %s", name)
|
||||
|
@ -461,7 +377,10 @@ func (im *IndexMapping) analyzerNamed(name string) *analysis.Analyzer {
|
|||
return analyzer
|
||||
}
|
||||
|
||||
func (im *IndexMapping) dateTimeParserNamed(name string) analysis.DateTimeParser {
|
||||
func (im *IndexMappingImpl) DateTimeParserNamed(name string) analysis.DateTimeParser {
|
||||
if name == "" {
|
||||
name = im.DefaultDateTimeParser
|
||||
}
|
||||
dateTimeParser, err := im.cache.DateTimeParserNamed(name)
|
||||
if err != nil {
|
||||
logger.Printf("error using datetime parser named: %s", name)
|
||||
|
@ -470,7 +389,7 @@ func (im *IndexMapping) dateTimeParserNamed(name string) analysis.DateTimeParser
|
|||
return dateTimeParser
|
||||
}
|
||||
|
||||
func (im *IndexMapping) datetimeParserNameForPath(path string) string {
|
||||
func (im *IndexMappingImpl) datetimeParserNameForPath(path string) string {
|
||||
|
||||
// first we look for explicit mapping on the field
|
||||
for _, docMapping := range im.TypeMapping {
|
||||
|
@ -487,7 +406,7 @@ func (im *IndexMapping) datetimeParserNameForPath(path string) string {
|
|||
return im.DefaultDateTimeParser
|
||||
}
|
||||
|
||||
func (im *IndexMapping) AnalyzeText(analyzerName string, text []byte) (analysis.TokenStream, error) {
|
||||
func (im *IndexMappingImpl) AnalyzeText(analyzerName string, text []byte) (analysis.TokenStream, error) {
|
||||
analyzer, err := im.cache.AnalyzerNamed(analyzerName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -496,6 +415,12 @@ func (im *IndexMapping) AnalyzeText(analyzerName string, text []byte) (analysis.
|
|||
}
|
||||
|
||||
// FieldAnalyzer returns the name of the analyzer used on a field.
|
||||
func (im *IndexMapping) FieldAnalyzer(field string) string {
|
||||
return im.analyzerNameForPath(field)
|
||||
func (im *IndexMappingImpl) FieldAnalyzer(field string) string {
|
||||
return im.AnalyzerNameForPath(field)
|
||||
}
|
||||
|
||||
// wrapper to satisfy new interface
|
||||
|
||||
func (im *IndexMappingImpl) DefaultSearchField() string {
|
||||
return im.DefaultField
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
// except in compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/blevesearch/bleve/analysis"
|
||||
"github.com/blevesearch/bleve/document"
|
||||
)
|
||||
|
||||
// A Classifier is an interface describing any object
|
||||
// which knows how to identify its own type.
|
||||
type Classifier interface {
|
||||
Type() string
|
||||
}
|
||||
|
||||
var logger = log.New(ioutil.Discard, "bleve mapping ", log.LstdFlags)
|
||||
|
||||
// SetLog sets the logger used for logging
|
||||
// by default log messages are sent to ioutil.Discard
|
||||
func SetLog(l *log.Logger) {
|
||||
logger = l
|
||||
}
|
||||
|
||||
type IndexMapping interface {
|
||||
MapDocument(doc *document.Document, data interface{}) error
|
||||
Validate() error
|
||||
|
||||
DateTimeParserNamed(name string) analysis.DateTimeParser
|
||||
|
||||
DefaultSearchField() string
|
||||
|
||||
AnalyzerNameForPath(path string) string
|
||||
AnalyzerNamed(name string) *analysis.Analyzer
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
@ -46,7 +46,7 @@ var mappingSource = []byte(`{
|
|||
"default_type": "_default"
|
||||
}`)
|
||||
|
||||
func buildMapping() *IndexMapping {
|
||||
func buildMapping() IndexMapping {
|
||||
nameFieldMapping := NewTextFieldMapping()
|
||||
nameFieldMapping.Name = "name"
|
||||
nameFieldMapping.Analyzer = "standard"
|
||||
|
@ -66,7 +66,7 @@ func buildMapping() *IndexMapping {
|
|||
func TestUnmarshalMappingJSON(t *testing.T) {
|
||||
mapping := buildMapping()
|
||||
|
||||
var indexMapping IndexMapping
|
||||
var indexMapping IndexMappingImpl
|
||||
err := json.Unmarshal(mappingSource, &indexMapping)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -88,7 +88,7 @@ func TestMappingStructWithJSONTags(t *testing.T) {
|
|||
}
|
||||
|
||||
doc := document.NewDocument("1")
|
||||
err := mapping.mapDocument(doc, x)
|
||||
err := mapping.MapDocument(doc, x)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ func TestMappingStructWithJSONTagsOneDisabled(t *testing.T) {
|
|||
}
|
||||
|
||||
doc := document.NewDocument("1")
|
||||
err := mapping.mapDocument(doc, x)
|
||||
err := mapping.MapDocument(doc, x)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -168,7 +168,7 @@ func TestMappingStructWithPointerToString(t *testing.T) {
|
|||
}
|
||||
|
||||
doc := document.NewDocument("1")
|
||||
err := mapping.mapDocument(doc, x)
|
||||
err := mapping.MapDocument(doc, x)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -200,7 +200,7 @@ func TestMappingJSONWithNull(t *testing.T) {
|
|||
}
|
||||
|
||||
doc := document.NewDocument("1")
|
||||
err = mapping.mapDocument(doc, jsondoc)
|
||||
err = mapping.MapDocument(doc, jsondoc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -243,17 +243,17 @@ func TestMappingForPath(t *testing.T) {
|
|||
mapping := NewIndexMapping()
|
||||
mapping.AddDocumentMapping("a", docMappingA)
|
||||
|
||||
analyzerName := mapping.analyzerNameForPath("name")
|
||||
analyzerName := mapping.AnalyzerNameForPath("name")
|
||||
if analyzerName != enFieldMapping.Analyzer {
|
||||
t.Errorf("expected '%s' got '%s'", enFieldMapping.Analyzer, analyzerName)
|
||||
}
|
||||
|
||||
analyzerName = mapping.analyzerNameForPath("nameCustom")
|
||||
analyzerName = mapping.AnalyzerNameForPath("nameCustom")
|
||||
if analyzerName != customMapping.Analyzer {
|
||||
t.Errorf("expected '%s' got '%s'", customMapping.Analyzer, analyzerName)
|
||||
}
|
||||
|
||||
analyzerName = mapping.analyzerNameForPath("child.desc")
|
||||
analyzerName = mapping.AnalyzerNameForPath("child.desc")
|
||||
if analyzerName != customFieldX.Analyzer {
|
||||
t.Errorf("expected '%s' got '%s'", customFieldX.Analyzer, analyzerName)
|
||||
}
|
||||
|
@ -345,7 +345,7 @@ func TestEnablingDisablingStoringDynamicFields(t *testing.T) {
|
|||
}
|
||||
doc := document.NewDocument("x")
|
||||
mapping := NewIndexMapping()
|
||||
err := mapping.mapDocument(doc, data)
|
||||
err := mapping.MapDocument(doc, data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -363,7 +363,7 @@ func TestEnablingDisablingStoringDynamicFields(t *testing.T) {
|
|||
|
||||
mapping = NewIndexMapping()
|
||||
doc = document.NewDocument("y")
|
||||
err = mapping.mapDocument(doc, data)
|
||||
err = mapping.MapDocument(doc, data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -377,7 +377,7 @@ func TestEnablingDisablingStoringDynamicFields(t *testing.T) {
|
|||
mapping = NewIndexMapping()
|
||||
mapping.StoreDynamic = true
|
||||
doc = document.NewDocument("y")
|
||||
err = mapping.mapDocument(doc, data)
|
||||
err = mapping.MapDocument(doc, data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -405,7 +405,7 @@ func TestMappingBool(t *testing.T) {
|
|||
}
|
||||
|
||||
doc := document.NewDocument("1")
|
||||
err := mapping.mapDocument(doc, x)
|
||||
err := mapping.MapDocument(doc, x)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -441,7 +441,7 @@ func TestDisableDefaultMapping(t *testing.T) {
|
|||
}
|
||||
|
||||
doc := document.NewDocument("x")
|
||||
err := indexMapping.mapDocument(doc, data)
|
||||
err := indexMapping.MapDocument(doc, data)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
@ -526,7 +526,7 @@ func TestInvalidIndexMappingStrict(t *testing.T) {
|
|||
mappingBytes := []byte(`{"typeField":"type","default_field":"all"}`)
|
||||
|
||||
// first unmarhsal it without strict
|
||||
var im IndexMapping
|
||||
var im IndexMappingImpl
|
||||
err := json.Unmarshal(mappingBytes, &im)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -590,7 +590,7 @@ func TestMappingBug353(t *testing.T) {
|
|||
mapping.DefaultMapping.AddSubDocumentMapping("Other", otherMapping)
|
||||
|
||||
doc := document.NewDocument("x")
|
||||
err = mapping.mapDocument(doc, data)
|
||||
err = mapping.MapDocument(doc, data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -636,7 +636,7 @@ func TestAnonymousStructFields(t *testing.T) {
|
|||
|
||||
doc := document.NewDocument("1")
|
||||
m := NewIndexMapping()
|
||||
err := m.mapDocument(doc, x)
|
||||
err := m.MapDocument(doc, x)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -676,7 +676,7 @@ func TestAnonymousStructFields(t *testing.T) {
|
|||
}
|
||||
|
||||
doc2 := document.NewDocument("2")
|
||||
err = m.mapDocument(doc2, y)
|
||||
err = m.MapDocument(doc2, y)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -712,7 +712,7 @@ func TestAnonymousStructFieldWithJSONStructTagEmptString(t *testing.T) {
|
|||
|
||||
doc := document.NewDocument("1")
|
||||
m := NewIndexMapping()
|
||||
err := m.mapDocument(doc, x)
|
||||
err := m.MapDocument(doc, x)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -749,7 +749,7 @@ func TestMappingPrimitives(t *testing.T) {
|
|||
m := NewIndexMapping()
|
||||
for _, test := range tests {
|
||||
doc := document.NewDocument("x")
|
||||
err := m.mapDocument(doc, test.data)
|
||||
err := m.MapDocument(doc, test.data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"reflect"
|
473
query.go
473
query.go
|
@ -10,327 +10,172 @@
|
|||
package bleve
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/query"
|
||||
)
|
||||
|
||||
// A Query represents a description of the type
|
||||
// and parameters for a query into the index.
|
||||
type Query interface {
|
||||
Boost() float64
|
||||
SetBoost(b float64) Query
|
||||
Field() string
|
||||
SetField(f string) Query
|
||||
Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error)
|
||||
Validate() error
|
||||
// NewBoolFieldQuery creates a new Query for boolean fields
|
||||
func NewBoolFieldQuery(val bool) *query.BoolFieldQuery {
|
||||
return query.NewBoolFieldQuery(val)
|
||||
}
|
||||
|
||||
// ParseQuery deserializes a JSON representation of
|
||||
// a Query object.
|
||||
func ParseQuery(input []byte) (Query, error) {
|
||||
var tmp map[string]interface{}
|
||||
err := json.Unmarshal(input, &tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, isMatchQuery := tmp["match"]
|
||||
_, hasFuzziness := tmp["fuzziness"]
|
||||
if hasFuzziness && !isMatchQuery {
|
||||
var rv fuzzyQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rv.Boost() == 0 {
|
||||
rv.SetBoost(1)
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, isTermQuery := tmp["term"]
|
||||
if isTermQuery {
|
||||
var rv termQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rv.Boost() == 0 {
|
||||
rv.SetBoost(1)
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
if isMatchQuery {
|
||||
var rv matchQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rv.Boost() == 0 {
|
||||
rv.SetBoost(1)
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, isMatchPhraseQuery := tmp["match_phrase"]
|
||||
if isMatchPhraseQuery {
|
||||
var rv matchPhraseQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rv.Boost() == 0 {
|
||||
rv.SetBoost(1)
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasMust := tmp["must"]
|
||||
_, hasShould := tmp["should"]
|
||||
_, hasMustNot := tmp["must_not"]
|
||||
if hasMust || hasShould || hasMustNot {
|
||||
var rv booleanQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rv.Boost() == 0 {
|
||||
rv.SetBoost(1)
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasTerms := tmp["terms"]
|
||||
if hasTerms {
|
||||
var rv phraseQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rv.Boost() == 0 {
|
||||
rv.SetBoost(1)
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasConjuncts := tmp["conjuncts"]
|
||||
if hasConjuncts {
|
||||
var rv conjunctionQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rv.Boost() == 0 {
|
||||
rv.SetBoost(1)
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasDisjuncts := tmp["disjuncts"]
|
||||
if hasDisjuncts {
|
||||
var rv disjunctionQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rv.Boost() == 0 {
|
||||
rv.SetBoost(1)
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
|
||||
_, hasSyntaxQuery := tmp["query"]
|
||||
if hasSyntaxQuery {
|
||||
var rv queryStringQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rv.Boost() == 0 {
|
||||
rv.SetBoost(1)
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasMin := tmp["min"]
|
||||
_, hasMax := tmp["max"]
|
||||
if hasMin || hasMax {
|
||||
var rv numericRangeQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rv.Boost() == 0 {
|
||||
rv.SetBoost(1)
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasStart := tmp["start"]
|
||||
_, hasEnd := tmp["end"]
|
||||
if hasStart || hasEnd {
|
||||
var rv dateRangeQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rv.Boost() == 0 {
|
||||
rv.SetBoost(1)
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasPrefix := tmp["prefix"]
|
||||
if hasPrefix {
|
||||
var rv prefixQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rv.Boost() == 0 {
|
||||
rv.SetBoost(1)
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasRegexp := tmp["regexp"]
|
||||
if hasRegexp {
|
||||
var rv regexpQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rv.Boost() == 0 {
|
||||
rv.SetBoost(1)
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasWildcard := tmp["wildcard"]
|
||||
if hasWildcard {
|
||||
var rv wildcardQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rv.Boost() == 0 {
|
||||
rv.SetBoost(1)
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasMatchAll := tmp["match_all"]
|
||||
if hasMatchAll {
|
||||
var rv matchAllQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rv.Boost() == 0 {
|
||||
rv.SetBoost(1)
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasMatchNone := tmp["match_none"]
|
||||
if hasMatchNone {
|
||||
var rv matchNoneQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rv.Boost() == 0 {
|
||||
rv.SetBoost(1)
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasDocIds := tmp["ids"]
|
||||
if hasDocIds {
|
||||
var rv docIDQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rv.Boost() == 0 {
|
||||
rv.SetBoost(1)
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
return nil, ErrorUnknownQueryType
|
||||
// NewBooleanQuery creates a compound Query composed
|
||||
// of several other Query objects.
|
||||
// These other query objects are added using the
|
||||
// AddMust() AddShould() and AddMustNot() methods.
|
||||
// Result documents must satisfy ALL of the
|
||||
// must Queries.
|
||||
// Result documents must satisfy NONE of the must not
|
||||
// Queries.
|
||||
// Result documents that ALSO satisfy any of the should
|
||||
// Queries will score higher.
|
||||
func NewBooleanQuery() *query.BooleanQuery {
|
||||
return query.NewBooleanQuery(nil, nil, nil)
|
||||
}
|
||||
|
||||
// expandQuery traverses the input query tree and returns a new tree where
|
||||
// query string queries have been expanded into base queries. Returned tree may
|
||||
// reference queries from the input tree or new queries.
|
||||
func expandQuery(m *IndexMapping, query Query) (Query, error) {
|
||||
var expand func(query Query) (Query, error)
|
||||
var expandSlice func(queries []Query) ([]Query, error)
|
||||
|
||||
expandSlice = func(queries []Query) ([]Query, error) {
|
||||
expanded := []Query{}
|
||||
for _, q := range queries {
|
||||
exp, err := expand(q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expanded = append(expanded, exp)
|
||||
}
|
||||
return expanded, nil
|
||||
}
|
||||
|
||||
expand = func(query Query) (Query, error) {
|
||||
switch query.(type) {
|
||||
case *queryStringQuery:
|
||||
q := query.(*queryStringQuery)
|
||||
parsed, err := parseQuerySyntax(q.Query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse '%s': %s", q.Query, err)
|
||||
}
|
||||
return expand(parsed)
|
||||
case *conjunctionQuery:
|
||||
q := *query.(*conjunctionQuery)
|
||||
children, err := expandSlice(q.Conjuncts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.Conjuncts = children
|
||||
return &q, nil
|
||||
case *disjunctionQuery:
|
||||
q := *query.(*disjunctionQuery)
|
||||
children, err := expandSlice(q.Disjuncts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.Disjuncts = children
|
||||
return &q, nil
|
||||
case *booleanQuery:
|
||||
q := *query.(*booleanQuery)
|
||||
var err error
|
||||
q.Must, err = expand(q.Must)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.Should, err = expand(q.Should)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.MustNot, err = expand(q.MustNot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &q, nil
|
||||
case *phraseQuery:
|
||||
q := *query.(*phraseQuery)
|
||||
children, err := expandSlice(q.termQueries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.termQueries = children
|
||||
return &q, nil
|
||||
default:
|
||||
return query, nil
|
||||
}
|
||||
}
|
||||
return expand(query)
|
||||
// NewConjunctionQuery creates a new compound Query.
|
||||
// Result documents must satisfy all of the queries.
|
||||
func NewConjunctionQuery(conjuncts ...query.Query) *query.ConjunctionQuery {
|
||||
return query.NewConjunctionQuery(conjuncts)
|
||||
}
|
||||
|
||||
// DumpQuery returns a string representation of the query tree, where query
|
||||
// string queries have been expanded into base queries. The output format is
|
||||
// meant for debugging purpose and may change in the future.
|
||||
func DumpQuery(m *IndexMapping, query Query) (string, error) {
|
||||
q, err := expandQuery(m, query)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
data, err := json.MarshalIndent(q, "", " ")
|
||||
return string(data), err
|
||||
// NewDateRangeQuery creates a new Query for ranges
|
||||
// of date values.
|
||||
// Date strings are parsed using the DateTimeParser configured in the
|
||||
// top-level config.QueryDateTimeParser
|
||||
// Either, but not both endpoints can be nil.
|
||||
func NewDateRangeQuery(start, end time.Time) *query.DateRangeQuery {
|
||||
return query.NewDateRangeQuery(start, end)
|
||||
}
|
||||
|
||||
// NewDateRangeInclusiveQuery creates a new Query for ranges
|
||||
// of date values.
|
||||
// Date strings are parsed using the DateTimeParser configured in the
|
||||
// top-level config.QueryDateTimeParser
|
||||
// Either, but not both endpoints can be nil.
|
||||
// startInclusive and endInclusive control inclusion of the endpoints.
|
||||
func NewDateRangeInclusiveQuery(start, end time.Time, startInclusive, endInclusive *bool) *query.DateRangeQuery {
|
||||
return query.NewDateRangeInclusiveQuery(start, end, startInclusive, endInclusive)
|
||||
}
|
||||
|
||||
// NewDisjunctionQuery creates a new compound Query.
|
||||
// Result documents satisfy at least one Query.
|
||||
func NewDisjunctionQuery(disjuncts ...query.Query) *query.DisjunctionQuery {
|
||||
return query.NewDisjunctionQuery(disjuncts)
|
||||
}
|
||||
|
||||
// NewDocIDQuery creates a new Query object returning indexed documents among
|
||||
// the specified set. Combine it with ConjunctionQuery to restrict the scope of
|
||||
// other queries output.
|
||||
func NewDocIDQuery(ids []string) *query.DocIDQuery {
|
||||
return query.NewDocIDQuery(ids)
|
||||
}
|
||||
|
||||
// NewFuzzyQuery creates a new Query which finds
|
||||
// documents containing terms within a specific
|
||||
// fuzziness of the specified term.
|
||||
// The default fuzziness is 2.
|
||||
//
|
||||
// The current implementation uses Levenshtein edit
|
||||
// distance as the fuzziness metric.
|
||||
func NewFuzzyQuery(term string) *query.FuzzyQuery {
|
||||
return query.NewFuzzyQuery(term)
|
||||
}
|
||||
|
||||
// NewMatchAllQuery creates a Query which will
|
||||
// match all documents in the index.
|
||||
func NewMatchAllQuery() *query.MatchAllQuery {
|
||||
return query.NewMatchAllQuery()
|
||||
}
|
||||
|
||||
// NewMatchNoneQuery creates a Query which will not
|
||||
// match any documents in the index.
|
||||
func NewMatchNoneQuery() *query.MatchNoneQuery {
|
||||
return query.NewMatchNoneQuery()
|
||||
}
|
||||
|
||||
// NewMatchPhraseQuery creates a new Query object
|
||||
// for matching phrases in the index.
|
||||
// An Analyzer is chosen based on the field.
|
||||
// Input text is analyzed using this analyzer.
|
||||
// Token terms resulting from this analysis are
|
||||
// used to build a search phrase. Result documents
|
||||
// must match this phrase. Queried field must have been indexed with
|
||||
// IncludeTermVectors set to true.
|
||||
func NewMatchPhraseQuery(matchPhrase string) *query.MatchPhraseQuery {
|
||||
return query.NewMatchPhraseQuery(matchPhrase)
|
||||
}
|
||||
|
||||
// NewMatchQuery creates a Query for matching text.
|
||||
// An Analyzer is chosen based on the field.
|
||||
// Input text is analyzed using this analyzer.
|
||||
// Token terms resulting from this analysis are
|
||||
// used to perform term searches. Result documents
|
||||
// must satisfy at least one of these term searches.
|
||||
func NewMatchQuery(match string) *query.MatchQuery {
|
||||
return query.NewMatchQuery(match)
|
||||
}
|
||||
|
||||
// NewNumericRangeQuery creates a new Query for ranges
|
||||
// of numeric values.
|
||||
// Either, but not both endpoints can be nil.
|
||||
// The minimum value is inclusive.
|
||||
// The maximum value is exclusive.
|
||||
func NewNumericRangeQuery(min, max *float64) *query.NumericRangeQuery {
|
||||
return query.NewNumericRangeQuery(min, max)
|
||||
}
|
||||
|
||||
// NewNumericRangeInclusiveQuery creates a new Query for ranges
|
||||
// of numeric values.
|
||||
// Either, but not both endpoints can be nil.
|
||||
// Control endpoint inclusion with inclusiveMin, inclusiveMax.
|
||||
func NewNumericRangeInclusiveQuery(min, max *float64, minInclusive, maxInclusive *bool) *query.NumericRangeQuery {
|
||||
return query.NewNumericRangeInclusiveQuery(min, max, minInclusive, maxInclusive)
|
||||
}
|
||||
|
||||
// NewPhraseQuery creates a new Query for finding
|
||||
// exact term phrases in the index.
|
||||
// The provided terms must exist in the correct
|
||||
// order, at the correct index offsets, in the
|
||||
// specified field. Queried field must have been indexed with
|
||||
// IncludeTermVectors set to true.
|
||||
func NewPhraseQuery(terms []string, field string) *query.PhraseQuery {
|
||||
return query.NewPhraseQuery(terms, field)
|
||||
}
|
||||
|
||||
// NewPrefixQuery creates a new Query which finds
|
||||
// documents containing terms that start with the
|
||||
// specified prefix.
|
||||
func NewPrefixQuery(prefix string) *query.PrefixQuery {
|
||||
return query.NewPrefixQuery(prefix)
|
||||
}
|
||||
|
||||
// NewRegexpQuery creates a new Query which finds
|
||||
// documents containing terms that match the
|
||||
// specified regular expression.
|
||||
func NewRegexpQuery(regexp string) *query.RegexpQuery {
|
||||
return query.NewRegexpQuery(regexp)
|
||||
}
|
||||
|
||||
// NewQueryStringQuery creates a new Query used for
|
||||
// finding documents that satisfy a query string. The
|
||||
// query string is a small query language for humans.
|
||||
func NewQueryStringQuery(q string) *query.QueryStringQuery {
|
||||
return query.NewQueryStringQuery(q)
|
||||
}
|
||||
|
||||
// NewTermQuery creates a new Query for finding an
|
||||
// exact term match in the index.
|
||||
func NewTermQuery(term string) *query.TermQuery {
|
||||
return query.NewTermQuery(term)
|
||||
}
|
||||
|
||||
// NewWildcardQuery creates a new Query which finds
|
||||
// documents containing terms that match the
|
||||
// specified wildcard. In the wildcard pattern '*'
|
||||
// will match any sequence of 0 or more characters,
|
||||
// and '?' will match any single character.
|
||||
func NewWildcardQuery(wildcard string) *query.WildcardQuery {
|
||||
return query.NewWildcardQuery(wildcard)
|
||||
}
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
// except in compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searchers"
|
||||
)
|
||||
|
||||
type boolFieldQuery struct {
|
||||
Bool bool `json:"bool"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewBoolFieldQuery creates a new Query for boolean fields
|
||||
func NewBoolFieldQuery(val bool) *boolFieldQuery {
|
||||
return &boolFieldQuery{
|
||||
Bool: val,
|
||||
BoostVal: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *boolFieldQuery) Boost() float64 {
|
||||
return q.BoostVal
|
||||
}
|
||||
|
||||
func (q *boolFieldQuery) SetBoost(b float64) Query {
|
||||
q.BoostVal = b
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *boolFieldQuery) Field() string {
|
||||
return q.FieldVal
|
||||
}
|
||||
|
||||
func (q *boolFieldQuery) SetField(f string) Query {
|
||||
q.FieldVal = f
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *boolFieldQuery) Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.FieldVal
|
||||
if q.FieldVal == "" {
|
||||
field = m.DefaultField
|
||||
}
|
||||
term := "F"
|
||||
if q.Bool {
|
||||
term = "T"
|
||||
}
|
||||
return searchers.NewTermSearcher(i, term, field, q.BoostVal, explain)
|
||||
}
|
||||
|
||||
func (q *boolFieldQuery) Validate() error {
|
||||
return nil
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
// except in compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/numeric_util"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searchers"
|
||||
)
|
||||
|
||||
type dateRangeQuery struct {
|
||||
Start *string `json:"start,omitempty"`
|
||||
End *string `json:"end,omitempty"`
|
||||
InclusiveStart *bool `json:"inclusive_start,omitempty"`
|
||||
InclusiveEnd *bool `json:"inclusive_end,omitempty"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewDateRangeQuery creates a new Query for ranges
|
||||
// of date values.
|
||||
// Date strings are parsed using the DateTimeParser configured in the
|
||||
// top-level config.QueryDateTimeParser
|
||||
// Either, but not both endpoints can be nil.
|
||||
func NewDateRangeQuery(start, end *string) *dateRangeQuery {
|
||||
return NewDateRangeInclusiveQuery(start, end, nil, nil)
|
||||
}
|
||||
|
||||
// NewDateRangeInclusiveQuery creates a new Query for ranges
|
||||
// of date values.
|
||||
// Date strings are parsed using the DateTimeParser configured in the
|
||||
// top-level config.QueryDateTimeParser
|
||||
// Either, but not both endpoints can be nil.
|
||||
// startInclusive and endInclusive control inclusion of the endpoints.
|
||||
func NewDateRangeInclusiveQuery(start, end *string, startInclusive, endInclusive *bool) *dateRangeQuery {
|
||||
return &dateRangeQuery{
|
||||
Start: start,
|
||||
End: end,
|
||||
InclusiveStart: startInclusive,
|
||||
InclusiveEnd: endInclusive,
|
||||
BoostVal: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *dateRangeQuery) Boost() float64 {
|
||||
return q.BoostVal
|
||||
}
|
||||
|
||||
func (q *dateRangeQuery) SetBoost(b float64) Query {
|
||||
q.BoostVal = b
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *dateRangeQuery) Field() string {
|
||||
return q.FieldVal
|
||||
}
|
||||
|
||||
func (q *dateRangeQuery) SetField(f string) Query {
|
||||
q.FieldVal = f
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *dateRangeQuery) Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error) {
|
||||
|
||||
min, max, err := q.parseEndpoints()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
field := q.FieldVal
|
||||
if q.FieldVal == "" {
|
||||
field = m.DefaultField
|
||||
}
|
||||
|
||||
return searchers.NewNumericRangeSearcher(i, min, max, q.InclusiveStart, q.InclusiveEnd, field, q.BoostVal, explain)
|
||||
}
|
||||
|
||||
func (q *dateRangeQuery) parseEndpoints() (*float64, *float64, error) {
|
||||
dateTimeParser, err := Config.Cache.DateTimeParserNamed(Config.QueryDateTimeParser)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// now parse the endpoints
|
||||
min := math.Inf(-1)
|
||||
max := math.Inf(1)
|
||||
if q.Start != nil && *q.Start != "" {
|
||||
startTime, err := dateTimeParser.ParseDateTime(*q.Start)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
min = numeric_util.Int64ToFloat64(startTime.UnixNano())
|
||||
}
|
||||
if q.End != nil && *q.End != "" {
|
||||
endTime, err := dateTimeParser.ParseDateTime(*q.End)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
max = numeric_util.Int64ToFloat64(endTime.UnixNano())
|
||||
}
|
||||
|
||||
return &min, &max, nil
|
||||
}
|
||||
|
||||
func (q *dateRangeQuery) Validate() error {
|
||||
if q.Start == nil && q.Start == q.End {
|
||||
return fmt.Errorf("must specify start or end")
|
||||
}
|
||||
_, _, err := q.parseEndpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
// except in compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searchers"
|
||||
)
|
||||
|
||||
type disjunctionQuery struct {
|
||||
Disjuncts []Query `json:"disjuncts"`
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
MinVal float64 `json:"min"`
|
||||
}
|
||||
|
||||
// NewDisjunctionQuery creates a new compound Query.
|
||||
// Result documents satisfy at least one Query.
|
||||
func NewDisjunctionQuery(disjuncts []Query) *disjunctionQuery {
|
||||
return &disjunctionQuery{
|
||||
Disjuncts: disjuncts,
|
||||
BoostVal: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDisjunctionQueryMin creates a new compound Query.
|
||||
// Result documents satisfy at least min Queries.
|
||||
func NewDisjunctionQueryMin(disjuncts []Query, min float64) *disjunctionQuery {
|
||||
return &disjunctionQuery{
|
||||
Disjuncts: disjuncts,
|
||||
BoostVal: 1.0,
|
||||
MinVal: min,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *disjunctionQuery) Boost() float64 {
|
||||
return q.BoostVal
|
||||
}
|
||||
|
||||
func (q *disjunctionQuery) SetBoost(b float64) Query {
|
||||
q.BoostVal = b
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *disjunctionQuery) AddQuery(aq Query) Query {
|
||||
q.Disjuncts = append(q.Disjuncts, aq)
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *disjunctionQuery) Min() float64 {
|
||||
return q.MinVal
|
||||
}
|
||||
|
||||
func (q *disjunctionQuery) SetMin(m float64) Query {
|
||||
q.MinVal = m
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *disjunctionQuery) Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error) {
|
||||
ss := make([]search.Searcher, len(q.Disjuncts))
|
||||
for in, disjunct := range q.Disjuncts {
|
||||
var err error
|
||||
ss[in], err = disjunct.Searcher(i, m, explain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return searchers.NewDisjunctionSearcher(i, ss, q.MinVal, explain)
|
||||
}
|
||||
|
||||
func (q *disjunctionQuery) Validate() error {
|
||||
if int(q.MinVal) > len(q.Disjuncts) {
|
||||
return ErrorDisjunctionFewerThanMinClauses
|
||||
}
|
||||
for _, q := range q.Disjuncts {
|
||||
err := q.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *disjunctionQuery) UnmarshalJSON(data []byte) error {
|
||||
tmp := struct {
|
||||
Disjuncts []json.RawMessage `json:"disjuncts"`
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
MinVal float64 `json:"min"`
|
||||
}{}
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Disjuncts = make([]Query, len(tmp.Disjuncts))
|
||||
for i, term := range tmp.Disjuncts {
|
||||
query, err := ParseQuery(term)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Disjuncts[i] = query
|
||||
}
|
||||
q.BoostVal = tmp.BoostVal
|
||||
if q.BoostVal == 0 {
|
||||
q.BoostVal = 1
|
||||
}
|
||||
q.MinVal = tmp.MinVal
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *disjunctionQuery) Field() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (q *disjunctionQuery) SetField(f string) Query {
|
||||
return q
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
// except in compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searchers"
|
||||
)
|
||||
|
||||
type fuzzyQuery struct {
|
||||
Term string `json:"term"`
|
||||
PrefixVal int `json:"prefix_length"`
|
||||
FuzzinessVal int `json:"fuzziness"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewFuzzyQuery creates a new Query which finds
|
||||
// documents containing terms within a specific
|
||||
// fuzziness of the specified term.
|
||||
// The default fuzziness is 2.
|
||||
//
|
||||
// The current implementation uses Levenshtein edit
|
||||
// distance as the fuzziness metric.
|
||||
func NewFuzzyQuery(term string) *fuzzyQuery {
|
||||
return &fuzzyQuery{
|
||||
Term: term,
|
||||
PrefixVal: 0,
|
||||
FuzzinessVal: 2,
|
||||
BoostVal: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *fuzzyQuery) Boost() float64 {
|
||||
return q.BoostVal
|
||||
}
|
||||
|
||||
func (q *fuzzyQuery) SetBoost(b float64) Query {
|
||||
q.BoostVal = b
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *fuzzyQuery) Field() string {
|
||||
return q.FieldVal
|
||||
}
|
||||
|
||||
func (q *fuzzyQuery) SetField(f string) Query {
|
||||
q.FieldVal = f
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *fuzzyQuery) Fuzziness() int {
|
||||
return q.FuzzinessVal
|
||||
}
|
||||
|
||||
func (q *fuzzyQuery) SetFuzziness(f int) Query {
|
||||
q.FuzzinessVal = f
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *fuzzyQuery) Prefix() int {
|
||||
return q.PrefixVal
|
||||
}
|
||||
|
||||
func (q *fuzzyQuery) SetPrefix(p int) Query {
|
||||
q.PrefixVal = p
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *fuzzyQuery) Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.FieldVal
|
||||
if q.FieldVal == "" {
|
||||
field = m.DefaultField
|
||||
}
|
||||
return searchers.NewFuzzySearcher(i, q.Term, q.PrefixVal, q.FuzzinessVal, field, q.BoostVal, explain)
|
||||
}
|
||||
|
||||
func (q *fuzzyQuery) Validate() error {
|
||||
return nil
|
||||
}
|
211
query_match.go
211
query_match.go
|
@ -1,211 +0,0 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
// except in compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type matchQuery struct {
|
||||
Match string `json:"match"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
Analyzer string `json:"analyzer,omitempty"`
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
PrefixVal int `json:"prefix_length"`
|
||||
FuzzinessVal int `json:"fuzziness"`
|
||||
OperatorVal MatchQueryOperator `json:"operator,omitempty"`
|
||||
}
|
||||
|
||||
type MatchQueryOperator int
|
||||
|
||||
const (
|
||||
// Document must satisfy AT LEAST ONE of term searches.
|
||||
MatchQueryOperatorOr = 0
|
||||
// Document must satisfy ALL of term searches.
|
||||
MatchQueryOperatorAnd = 1
|
||||
)
|
||||
|
||||
func (o MatchQueryOperator) MarshalJSON() ([]byte, error) {
|
||||
switch o {
|
||||
case MatchQueryOperatorOr:
|
||||
return json.Marshal("or")
|
||||
case MatchQueryOperatorAnd:
|
||||
return json.Marshal("and")
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot marshal match operator %d to JSON", o)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *MatchQueryOperator) UnmarshalJSON(data []byte) error {
|
||||
var operatorString string
|
||||
err := json.Unmarshal(data, &operatorString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch operatorString {
|
||||
case "or":
|
||||
*o = MatchQueryOperatorOr
|
||||
return nil
|
||||
case "and":
|
||||
*o = MatchQueryOperatorAnd
|
||||
return nil
|
||||
default:
|
||||
return matchQueryOperatorUnmarshalError(operatorString)
|
||||
}
|
||||
}
|
||||
|
||||
type matchQueryOperatorUnmarshalError string
|
||||
|
||||
func (e matchQueryOperatorUnmarshalError) Error() string {
|
||||
return fmt.Sprintf("cannot unmarshal match operator '%s' from JSON", e)
|
||||
}
|
||||
|
||||
// NewMatchQuery creates a Query for matching text.
|
||||
// An Analyzer is chosen based on the field.
|
||||
// Input text is analyzed using this analyzer.
|
||||
// Token terms resulting from this analysis are
|
||||
// used to perform term searches. Result documents
|
||||
// must satisfy at least one of these term searches.
|
||||
func NewMatchQuery(match string) *matchQuery {
|
||||
return &matchQuery{
|
||||
Match: match,
|
||||
BoostVal: 1.0,
|
||||
OperatorVal: MatchQueryOperatorOr,
|
||||
}
|
||||
}
|
||||
|
||||
// NewMatchQuery creates a Query for matching text.
|
||||
// An Analyzer is chosen based on the field.
|
||||
// Input text is analyzed using this analyzer.
|
||||
// Token terms resulting from this analysis are
|
||||
// used to perform term searches. Result documents
|
||||
// must satisfy term searches according to given operator.
|
||||
func NewMatchQueryOperator(match string, operator MatchQueryOperator) *matchQuery {
|
||||
return &matchQuery{
|
||||
Match: match,
|
||||
BoostVal: 1.0,
|
||||
OperatorVal: operator,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *matchQuery) Boost() float64 {
|
||||
return q.BoostVal
|
||||
}
|
||||
|
||||
func (q *matchQuery) SetBoost(b float64) Query {
|
||||
q.BoostVal = b
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *matchQuery) Field() string {
|
||||
return q.FieldVal
|
||||
}
|
||||
|
||||
func (q *matchQuery) SetField(f string) Query {
|
||||
q.FieldVal = f
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *matchQuery) Fuzziness() int {
|
||||
return q.FuzzinessVal
|
||||
}
|
||||
|
||||
func (q *matchQuery) SetFuzziness(f int) Query {
|
||||
q.FuzzinessVal = f
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *matchQuery) Prefix() int {
|
||||
return q.PrefixVal
|
||||
}
|
||||
|
||||
func (q *matchQuery) SetPrefix(p int) Query {
|
||||
q.PrefixVal = p
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *matchQuery) Operator() MatchQueryOperator {
|
||||
return q.OperatorVal
|
||||
}
|
||||
|
||||
func (q *matchQuery) SetOperator(operator MatchQueryOperator) Query {
|
||||
q.OperatorVal = operator
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *matchQuery) Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error) {
|
||||
|
||||
field := q.FieldVal
|
||||
if q.FieldVal == "" {
|
||||
field = m.DefaultField
|
||||
}
|
||||
|
||||
analyzerName := ""
|
||||
if q.Analyzer != "" {
|
||||
analyzerName = q.Analyzer
|
||||
} else {
|
||||
analyzerName = m.analyzerNameForPath(field)
|
||||
}
|
||||
analyzer := m.analyzerNamed(analyzerName)
|
||||
|
||||
if analyzer == nil {
|
||||
return nil, fmt.Errorf("no analyzer named '%s' registered", q.Analyzer)
|
||||
}
|
||||
|
||||
tokens := analyzer.Analyze([]byte(q.Match))
|
||||
if len(tokens) > 0 {
|
||||
|
||||
tqs := make([]Query, len(tokens))
|
||||
if q.FuzzinessVal != 0 {
|
||||
for i, token := range tokens {
|
||||
query := NewFuzzyQuery(string(token.Term))
|
||||
query.SetFuzziness(q.FuzzinessVal)
|
||||
query.SetPrefix(q.PrefixVal)
|
||||
query.SetField(field)
|
||||
query.SetBoost(q.BoostVal)
|
||||
tqs[i] = query
|
||||
}
|
||||
} else {
|
||||
for i, token := range tokens {
|
||||
tqs[i] = NewTermQuery(string(token.Term)).
|
||||
SetField(field).
|
||||
SetBoost(q.BoostVal)
|
||||
}
|
||||
}
|
||||
|
||||
switch q.OperatorVal {
|
||||
case MatchQueryOperatorOr:
|
||||
shouldQuery := NewDisjunctionQueryMin(tqs, 1).
|
||||
SetBoost(q.BoostVal)
|
||||
|
||||
return shouldQuery.Searcher(i, m, explain)
|
||||
|
||||
case MatchQueryOperatorAnd:
|
||||
mustQuery := NewConjunctionQuery(tqs).
|
||||
SetBoost(q.BoostVal)
|
||||
|
||||
return mustQuery.Searcher(i, m, explain)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled operator %d", q.OperatorVal)
|
||||
}
|
||||
}
|
||||
noneQuery := NewMatchNoneQuery()
|
||||
return noneQuery.Searcher(i, m, explain)
|
||||
}
|
||||
|
||||
func (q *matchQuery) Validate() error {
|
||||
return nil
|
||||
}
|
319
query_test.go
319
query_test.go
|
@ -1,319 +0,0 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
// except in compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var minNum = 5.1
|
||||
var maxNum = 7.1
|
||||
var startDate = "2011-01-01"
|
||||
var endDate = "2012-01-01"
|
||||
|
||||
func TestParseQuery(t *testing.T) {
|
||||
tests := []struct {
|
||||
input []byte
|
||||
output Query
|
||||
err error
|
||||
}{
|
||||
{
|
||||
input: []byte(`{"term":"water","field":"desc"}`),
|
||||
output: NewTermQuery("water").SetField("desc"),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"match":"beer","field":"desc"}`),
|
||||
output: NewMatchQuery("beer").SetField("desc"),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"match":"beer","field":"desc","operator":"or"}`),
|
||||
output: NewMatchQuery("beer").SetField("desc"),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"match":"beer","field":"desc","operator":"and"}`),
|
||||
output: NewMatchQueryOperator("beer", MatchQueryOperatorAnd).SetField("desc"),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"match":"beer","field":"desc","operator":"or"}`),
|
||||
output: NewMatchQueryOperator("beer", MatchQueryOperatorOr).SetField("desc"),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"match":"beer","field":"desc","operator":"does not exist"}`),
|
||||
output: nil,
|
||||
err: matchQueryOperatorUnmarshalError("does not exist"),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"match_phrase":"light beer","field":"desc"}`),
|
||||
output: NewMatchPhraseQuery("light beer").SetField("desc"),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"must":{"conjuncts": [{"match":"beer","field":"desc"}]},"should":{"disjuncts": [{"match":"water","field":"desc"}],"min":1.0},"must_not":{"disjuncts": [{"match":"devon","field":"desc"}]}}`),
|
||||
output: NewBooleanQueryMinShould(
|
||||
[]Query{NewMatchQuery("beer").SetField("desc")},
|
||||
[]Query{NewMatchQuery("water").SetField("desc")},
|
||||
[]Query{NewMatchQuery("devon").SetField("desc")},
|
||||
1.0),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"terms":["watered","down"],"field":"desc"}`),
|
||||
output: NewPhraseQuery([]string{"watered", "down"}, "desc"),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"query":"+beer \"light beer\" -devon"}`),
|
||||
output: NewQueryStringQuery(`+beer "light beer" -devon`),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"min":5.1,"max":7.1,"field":"desc"}`),
|
||||
output: NewNumericRangeQuery(&minNum, &maxNum).SetField("desc"),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"start":"` + startDate + `","end":"` + endDate + `","field":"desc"}`),
|
||||
output: NewDateRangeQuery(&startDate, &endDate).SetField("desc"),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"prefix":"budwei","field":"desc"}`),
|
||||
output: NewPrefixQuery("budwei").SetField("desc"),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"match_all":{}}`),
|
||||
output: NewMatchAllQuery(),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"match_none":{}}`),
|
||||
output: NewMatchNoneQuery(),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"ids":["a","b","c"]}`),
|
||||
output: NewDocIDQuery([]string{"a", "b", "c"}),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"madeitup":"queryhere"}`),
|
||||
output: nil,
|
||||
err: ErrorUnknownQueryType,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
actual, err := ParseQuery(test.input)
|
||||
if err != nil && test.err == nil {
|
||||
t.Errorf("error %v for %d", err, i)
|
||||
} else if test.err != nil {
|
||||
if !reflect.DeepEqual(err, test.err) {
|
||||
t.Errorf("expected error: %#v, got: %#v", test.err, err)
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(test.output, actual) {
|
||||
t.Errorf("expected: %#v, got: %#v", test.output, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetGetField(t *testing.T) {
|
||||
tests := []struct {
|
||||
query Query
|
||||
field string
|
||||
}{
|
||||
{
|
||||
query: NewTermQuery("water").SetField("desc"),
|
||||
field: "desc",
|
||||
},
|
||||
{
|
||||
query: NewMatchQuery("beer").SetField("desc"),
|
||||
field: "desc",
|
||||
},
|
||||
{
|
||||
query: NewMatchPhraseQuery("light beer").SetField("desc"),
|
||||
field: "desc",
|
||||
},
|
||||
{
|
||||
query: NewNumericRangeQuery(&minNum, &maxNum).SetField("desc"),
|
||||
field: "desc",
|
||||
},
|
||||
{
|
||||
query: NewDateRangeQuery(&startDate, &endDate).SetField("desc"),
|
||||
field: "desc",
|
||||
},
|
||||
{
|
||||
query: NewPrefixQuery("budwei").SetField("desc"),
|
||||
field: "desc",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
query := test.query
|
||||
if query.Field() != test.field {
|
||||
t.Errorf("expected field '%s', got '%s'", test.field, query.Field())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryValidate(t *testing.T) {
|
||||
tests := []struct {
|
||||
query Query
|
||||
err error
|
||||
}{
|
||||
{
|
||||
query: NewTermQuery("water").SetField("desc"),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
query: NewMatchQuery("beer").SetField("desc"),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
query: NewMatchPhraseQuery("light beer").SetField("desc"),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
query: NewNumericRangeQuery(&minNum, &maxNum).SetField("desc"),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
query: NewNumericRangeQuery(nil, nil).SetField("desc"),
|
||||
err: ErrorNumericQueryNoBounds,
|
||||
},
|
||||
{
|
||||
query: NewDateRangeQuery(&startDate, &endDate).SetField("desc"),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
query: NewPrefixQuery("budwei").SetField("desc"),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
query: NewQueryStringQuery(`+beer "light beer" -devon`),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
query: NewPhraseQuery([]string{"watered", "down"}, "desc"),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
query: NewPhraseQuery([]string{}, "field"),
|
||||
err: ErrorPhraseQueryNoTerms,
|
||||
},
|
||||
{
|
||||
query: NewMatchNoneQuery().SetBoost(25),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
query: NewMatchAllQuery().SetBoost(25),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
query: NewBooleanQuery(
|
||||
[]Query{NewMatchQuery("beer").SetField("desc")},
|
||||
[]Query{NewMatchQuery("water").SetField("desc")},
|
||||
[]Query{NewMatchQuery("devon").SetField("desc")}),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
query: NewBooleanQuery(
|
||||
nil,
|
||||
nil,
|
||||
[]Query{NewMatchQuery("devon").SetField("desc")}),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
query: NewBooleanQuery(
|
||||
[]Query{},
|
||||
[]Query{},
|
||||
[]Query{NewMatchQuery("devon").SetField("desc")}),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
query: NewBooleanQuery(
|
||||
nil,
|
||||
nil,
|
||||
nil),
|
||||
err: ErrorBooleanQueryNeedsMustOrShouldOrNotMust,
|
||||
},
|
||||
{
|
||||
query: NewBooleanQuery(
|
||||
[]Query{},
|
||||
[]Query{},
|
||||
[]Query{}),
|
||||
err: ErrorBooleanQueryNeedsMustOrShouldOrNotMust,
|
||||
},
|
||||
{
|
||||
query: NewBooleanQueryMinShould(
|
||||
[]Query{NewMatchQuery("beer").SetField("desc")},
|
||||
[]Query{NewMatchQuery("water").SetField("desc")},
|
||||
[]Query{NewMatchQuery("devon").SetField("desc")},
|
||||
2.0),
|
||||
err: ErrorDisjunctionFewerThanMinClauses,
|
||||
},
|
||||
{
|
||||
query: NewDocIDQuery(nil).SetBoost(25),
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
actual := test.query.Validate()
|
||||
if !reflect.DeepEqual(actual, test.err) {
|
||||
t.Errorf("expected error: %#v got %#v", test.err, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDumpQuery(t *testing.T) {
|
||||
mapping := &IndexMapping{}
|
||||
q := NewQueryStringQuery("+water -light beer")
|
||||
s, err := DumpQuery(mapping, q)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s = strings.TrimSpace(s)
|
||||
wanted := strings.TrimSpace(`{
|
||||
"must": {
|
||||
"conjuncts": [
|
||||
{
|
||||
"match": "water",
|
||||
"boost": 1,
|
||||
"prefix_length": 0,
|
||||
"fuzziness": 0
|
||||
}
|
||||
],
|
||||
"boost": 1
|
||||
},
|
||||
"should": {
|
||||
"disjuncts": [
|
||||
{
|
||||
"match": "beer",
|
||||
"boost": 1,
|
||||
"prefix_length": 0,
|
||||
"fuzziness": 0
|
||||
}
|
||||
],
|
||||
"boost": 1,
|
||||
"min": 0
|
||||
},
|
||||
"must_not": {
|
||||
"disjuncts": [
|
||||
{
|
||||
"match": "light",
|
||||
"boost": 1,
|
||||
"prefix_length": 0,
|
||||
"fuzziness": 0
|
||||
}
|
||||
],
|
||||
"boost": 1,
|
||||
"min": 0
|
||||
},
|
||||
"boost": 1
|
||||
}`)
|
||||
if wanted != s {
|
||||
t.Fatalf("query:\n%s\ndiffers from expected:\n%s", s, wanted)
|
||||
}
|
||||
}
|
17
search.go
17
search.go
|
@ -16,6 +16,7 @@ import (
|
|||
|
||||
"github.com/blevesearch/bleve/analysis"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/query"
|
||||
)
|
||||
|
||||
type numericRange struct {
|
||||
|
@ -195,7 +196,7 @@ func (h *HighlightRequest) AddField(field string) {
|
|||
//
|
||||
// A special field named "*" can be used to return all fields.
|
||||
type SearchRequest struct {
|
||||
Query Query `json:"query"`
|
||||
Query query.Query `json:"query"`
|
||||
Size int `json:"size"`
|
||||
From int `json:"from"`
|
||||
Highlight *HighlightRequest `json:"highlight"`
|
||||
|
@ -206,9 +207,11 @@ type SearchRequest struct {
|
|||
}
|
||||
|
||||
func (sr *SearchRequest) Validate() error {
|
||||
err := sr.Query.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
if srq, ok := sr.Query.(query.ValidatableQuery); ok {
|
||||
err := srq.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return sr.Facets.Validate()
|
||||
|
@ -274,7 +277,7 @@ func (r *SearchRequest) UnmarshalJSON(input []byte) error {
|
|||
r.Highlight = temp.Highlight
|
||||
r.Fields = temp.Fields
|
||||
r.Facets = temp.Facets
|
||||
r.Query, err = ParseQuery(temp.Q)
|
||||
r.Query, err = query.ParseQuery(temp.Q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -293,7 +296,7 @@ func (r *SearchRequest) UnmarshalJSON(input []byte) error {
|
|||
// NewSearchRequest creates a new SearchRequest
|
||||
// for the Query, using default values for all
|
||||
// other search parameters.
|
||||
func NewSearchRequest(q Query) *SearchRequest {
|
||||
func NewSearchRequest(q query.Query) *SearchRequest {
|
||||
return NewSearchRequestOptions(q, 10, 0, false)
|
||||
}
|
||||
|
||||
|
@ -301,7 +304,7 @@ func NewSearchRequest(q Query) *SearchRequest {
|
|||
// for the Query, with the requested size, from
|
||||
// and explanation search parameters.
|
||||
// By default results are ordered by score, descending.
|
||||
func NewSearchRequestOptions(q Query, size, from int, explain bool) *SearchRequest {
|
||||
func NewSearchRequestOptions(q query.Query, size, from int, explain bool) *SearchRequest {
|
||||
return &SearchRequest{
|
||||
Query: q,
|
||||
Size: size,
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
// except in compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searchers"
|
||||
)
|
||||
|
||||
type BoolFieldQuery struct {
|
||||
Bool bool `json:"bool"`
|
||||
Field string `json:"field,omitempty"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewBoolFieldQuery creates a new Query for boolean fields
|
||||
func NewBoolFieldQuery(val bool) *BoolFieldQuery {
|
||||
return &BoolFieldQuery{
|
||||
Bool: val,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *BoolFieldQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.Boost = &boost
|
||||
}
|
||||
|
||||
func (q *BoolFieldQuery) SetField(f string) {
|
||||
q.Field = f
|
||||
}
|
||||
|
||||
func (q *BoolFieldQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.Field
|
||||
if q.Field == "" {
|
||||
field = m.DefaultSearchField()
|
||||
}
|
||||
term := "F"
|
||||
if q.Bool {
|
||||
term = "T"
|
||||
}
|
||||
return searchers.NewTermSearcher(i, term, field, q.Boost.Value(), explain)
|
||||
}
|
|
@ -7,22 +7,23 @@
|
|||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
package query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searchers"
|
||||
)
|
||||
|
||||
type booleanQuery struct {
|
||||
Must Query `json:"must,omitempty"`
|
||||
Should Query `json:"should,omitempty"`
|
||||
MustNot Query `json:"must_not,omitempty"`
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
type BooleanQuery struct {
|
||||
Must Query `json:"must,omitempty"`
|
||||
Should Query `json:"should,omitempty"`
|
||||
MustNot Query `json:"must_not,omitempty"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewBooleanQuery creates a compound Query composed
|
||||
|
@ -33,24 +34,14 @@ type booleanQuery struct {
|
|||
// Queries.
|
||||
// Result documents that ALSO satisfy any of the should
|
||||
// Queries will score higher.
|
||||
func NewBooleanQuery(must []Query, should []Query, mustNot []Query) *booleanQuery {
|
||||
return NewBooleanQueryMinShould(must, should, mustNot, 0.0)
|
||||
}
|
||||
func NewBooleanQuery(must []Query, should []Query, mustNot []Query) *BooleanQuery {
|
||||
|
||||
// NewBooleanQueryMinShould is the same as
|
||||
// NewBooleanQuery, only it offers control of the
|
||||
// minimum number of should queries that must be
|
||||
// satisfied.
|
||||
func NewBooleanQueryMinShould(must []Query, should []Query, mustNot []Query, minShould float64) *booleanQuery {
|
||||
|
||||
rv := booleanQuery{
|
||||
BoostVal: 1.0,
|
||||
}
|
||||
rv := BooleanQuery{}
|
||||
if len(must) > 0 {
|
||||
rv.Must = NewConjunctionQuery(must)
|
||||
}
|
||||
if len(should) > 0 {
|
||||
rv.Should = NewDisjunctionQueryMin(should, minShould)
|
||||
rv.Should = NewDisjunctionQuery(should)
|
||||
}
|
||||
if len(mustNot) > 0 {
|
||||
rv.MustNot = NewDisjunctionQuery(mustNot)
|
||||
|
@ -61,41 +52,43 @@ func NewBooleanQueryMinShould(must []Query, should []Query, mustNot []Query, min
|
|||
|
||||
// SetMinShould requires that at least minShould of the
|
||||
// should Queries must be satisfied.
|
||||
func (q *booleanQuery) SetMinShould(minShould float64) {
|
||||
q.Should.(*disjunctionQuery).SetMin(minShould)
|
||||
func (q *BooleanQuery) SetMinShould(minShould float64) {
|
||||
q.Should.(*DisjunctionQuery).SetMin(minShould)
|
||||
}
|
||||
|
||||
func (q *booleanQuery) AddMust(m Query) {
|
||||
func (q *BooleanQuery) AddMust(m ...Query) {
|
||||
if q.Must == nil {
|
||||
q.Must = NewConjunctionQuery([]Query{})
|
||||
}
|
||||
q.Must.(*conjunctionQuery).AddQuery(m)
|
||||
for _, mq := range m {
|
||||
q.Must.(*ConjunctionQuery).AddQuery(mq)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *booleanQuery) AddShould(m Query) {
|
||||
func (q *BooleanQuery) AddShould(m ...Query) {
|
||||
if q.Should == nil {
|
||||
q.Should = NewDisjunctionQuery([]Query{})
|
||||
}
|
||||
q.Should.(*disjunctionQuery).AddQuery(m)
|
||||
for _, mq := range m {
|
||||
q.Should.(*DisjunctionQuery).AddQuery(mq)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *booleanQuery) AddMustNot(m Query) {
|
||||
func (q *BooleanQuery) AddMustNot(m ...Query) {
|
||||
if q.MustNot == nil {
|
||||
q.MustNot = NewDisjunctionQuery([]Query{})
|
||||
}
|
||||
q.MustNot.(*disjunctionQuery).AddQuery(m)
|
||||
for _, mq := range m {
|
||||
q.MustNot.(*DisjunctionQuery).AddQuery(mq)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *booleanQuery) Boost() float64 {
|
||||
return q.BoostVal
|
||||
func (q *BooleanQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.Boost = &boost
|
||||
}
|
||||
|
||||
func (q *booleanQuery) SetBoost(b float64) Query {
|
||||
q.BoostVal = b
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *booleanQuery) Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error) {
|
||||
func (q *BooleanQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
var err error
|
||||
var mustNotSearcher search.Searcher
|
||||
if q.MustNot != nil {
|
||||
|
@ -131,37 +124,37 @@ func (q *booleanQuery) Searcher(i index.IndexReader, m *IndexMapping, explain bo
|
|||
return searchers.NewBooleanSearcher(i, mustSearcher, shouldSearcher, mustNotSearcher, explain)
|
||||
}
|
||||
|
||||
func (q *booleanQuery) Validate() error {
|
||||
if q.Must != nil {
|
||||
err := q.Must.Validate()
|
||||
func (q *BooleanQuery) Validate() error {
|
||||
if qm, ok := q.Must.(ValidatableQuery); ok {
|
||||
err := qm.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if q.Should != nil {
|
||||
err := q.Should.Validate()
|
||||
if qs, ok := q.Should.(ValidatableQuery); ok {
|
||||
err := qs.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if q.MustNot != nil {
|
||||
err := q.MustNot.Validate()
|
||||
if qmn, ok := q.MustNot.(ValidatableQuery); ok {
|
||||
err := qmn.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if q.Must == nil && q.Should == nil && q.MustNot == nil {
|
||||
return ErrorBooleanQueryNeedsMustOrShouldOrNotMust
|
||||
return fmt.Errorf("boolean query must contain at least one must or should or not must clause")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *booleanQuery) UnmarshalJSON(data []byte) error {
|
||||
func (q *BooleanQuery) UnmarshalJSON(data []byte) error {
|
||||
tmp := struct {
|
||||
Must json.RawMessage `json:"must,omitempty"`
|
||||
Should json.RawMessage `json:"should,omitempty"`
|
||||
MustNot json.RawMessage `json:"must_not,omitempty"`
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
Must json.RawMessage `json:"must,omitempty"`
|
||||
Should json.RawMessage `json:"should,omitempty"`
|
||||
MustNot json.RawMessage `json:"must_not,omitempty"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
}{}
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
|
@ -173,7 +166,7 @@ func (q *booleanQuery) UnmarshalJSON(data []byte) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, isConjunctionQuery := q.Must.(*conjunctionQuery)
|
||||
_, isConjunctionQuery := q.Must.(*ConjunctionQuery)
|
||||
if !isConjunctionQuery {
|
||||
return fmt.Errorf("must clause must be conjunction")
|
||||
}
|
||||
|
@ -184,7 +177,7 @@ func (q *booleanQuery) UnmarshalJSON(data []byte) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, isDisjunctionQuery := q.Should.(*disjunctionQuery)
|
||||
_, isDisjunctionQuery := q.Should.(*DisjunctionQuery)
|
||||
if !isDisjunctionQuery {
|
||||
return fmt.Errorf("should clause must be disjunction")
|
||||
}
|
||||
|
@ -195,23 +188,13 @@ func (q *booleanQuery) UnmarshalJSON(data []byte) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, isDisjunctionQuery := q.MustNot.(*disjunctionQuery)
|
||||
_, isDisjunctionQuery := q.MustNot.(*DisjunctionQuery)
|
||||
if !isDisjunctionQuery {
|
||||
return fmt.Errorf("must not clause must be disjunction")
|
||||
}
|
||||
}
|
||||
|
||||
q.BoostVal = tmp.BoostVal
|
||||
if q.BoostVal == 0 {
|
||||
q.BoostVal = 1
|
||||
}
|
||||
q.Boost = tmp.Boost
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *booleanQuery) Field() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (q *booleanQuery) SetField(f string) Query {
|
||||
return q
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
// except in compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package query
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Boost float64
|
||||
|
||||
func (b *Boost) Value() float64 {
|
||||
if b == nil {
|
||||
return 1.0
|
||||
}
|
||||
return float64(*b)
|
||||
}
|
||||
|
||||
func (b *Boost) GoString() string {
|
||||
if b == nil {
|
||||
return "boost unspecified"
|
||||
}
|
||||
return fmt.Sprintf("%f", *b)
|
||||
}
|
|
@ -7,45 +7,42 @@
|
|||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
package query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searchers"
|
||||
)
|
||||
|
||||
type conjunctionQuery struct {
|
||||
type ConjunctionQuery struct {
|
||||
Conjuncts []Query `json:"conjuncts"`
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewConjunctionQuery creates a new compound Query.
|
||||
// Result documents must satisfy all of the queries.
|
||||
func NewConjunctionQuery(conjuncts []Query) *conjunctionQuery {
|
||||
return &conjunctionQuery{
|
||||
func NewConjunctionQuery(conjuncts []Query) *ConjunctionQuery {
|
||||
return &ConjunctionQuery{
|
||||
Conjuncts: conjuncts,
|
||||
BoostVal: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *conjunctionQuery) Boost() float64 {
|
||||
return q.BoostVal
|
||||
func (q *ConjunctionQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.Boost = &boost
|
||||
}
|
||||
|
||||
func (q *conjunctionQuery) SetBoost(b float64) Query {
|
||||
q.BoostVal = b
|
||||
return q
|
||||
func (q *ConjunctionQuery) AddQuery(aq ...Query) {
|
||||
for _, aaq := range aq {
|
||||
q.Conjuncts = append(q.Conjuncts, aaq)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *conjunctionQuery) AddQuery(aq Query) *conjunctionQuery {
|
||||
q.Conjuncts = append(q.Conjuncts, aq)
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *conjunctionQuery) Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error) {
|
||||
func (q *ConjunctionQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
ss := make([]search.Searcher, len(q.Conjuncts))
|
||||
for in, conjunct := range q.Conjuncts {
|
||||
var err error
|
||||
|
@ -57,20 +54,22 @@ func (q *conjunctionQuery) Searcher(i index.IndexReader, m *IndexMapping, explai
|
|||
return searchers.NewConjunctionSearcher(i, ss, explain)
|
||||
}
|
||||
|
||||
func (q *conjunctionQuery) Validate() error {
|
||||
func (q *ConjunctionQuery) Validate() error {
|
||||
for _, q := range q.Conjuncts {
|
||||
err := q.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
if q, ok := q.(ValidatableQuery); ok {
|
||||
err := q.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *conjunctionQuery) UnmarshalJSON(data []byte) error {
|
||||
func (q *ConjunctionQuery) UnmarshalJSON(data []byte) error {
|
||||
tmp := struct {
|
||||
Conjuncts []json.RawMessage `json:"conjuncts"`
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
}{}
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
|
@ -84,17 +83,6 @@ func (q *conjunctionQuery) UnmarshalJSON(data []byte) error {
|
|||
}
|
||||
q.Conjuncts[i] = query
|
||||
}
|
||||
q.BoostVal = tmp.BoostVal
|
||||
if q.BoostVal == 0 {
|
||||
q.BoostVal = 1
|
||||
}
|
||||
q.Boost = tmp.Boost
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *conjunctionQuery) Field() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (q *conjunctionQuery) SetField(f string) Query {
|
||||
return q
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
// except in compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/blevesearch/bleve/analysis/datetime_parsers/datetime_optional"
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/numeric_util"
|
||||
"github.com/blevesearch/bleve/registry"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searchers"
|
||||
)
|
||||
|
||||
// QueryDateTimeParser controls the default query date time parser
|
||||
var QueryDateTimeParser = datetime_optional.Name
|
||||
|
||||
// QueryDateTimeFormat controls the format when Marshaling to JSON
|
||||
var QueryDateTimeFormat = time.RFC3339
|
||||
|
||||
var cache = registry.NewCache()
|
||||
|
||||
type BleveQueryTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func QueryTimeFromString(t string) (time.Time, error) {
|
||||
dateTimeParser, err := cache.DateTimeParserNamed(QueryDateTimeParser)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
rv, err := dateTimeParser.ParseDateTime(t)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (t *BleveQueryTime) MarshalJSON() ([]byte, error) {
|
||||
tt := time.Time(t.Time)
|
||||
return []byte(tt.Format(QueryDateTimeFormat)), nil
|
||||
}
|
||||
|
||||
func (t *BleveQueryTime) UnmarshalJSON(data []byte) error {
|
||||
var timeString string
|
||||
err := json.Unmarshal(data, &timeString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dateTimeParser, err := cache.DateTimeParserNamed(QueryDateTimeParser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Time, err = dateTimeParser.ParseDateTime(timeString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DateRangeQuery struct {
|
||||
Start BleveQueryTime `json:"start,omitempty"`
|
||||
End BleveQueryTime `json:"end,omitempty"`
|
||||
InclusiveStart *bool `json:"inclusive_start,omitempty"`
|
||||
InclusiveEnd *bool `json:"inclusive_end,omitempty"`
|
||||
Field string `json:"field,omitempty"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewDateRangeQuery creates a new Query for ranges
|
||||
// of date values.
|
||||
// Date strings are parsed using the DateTimeParser configured in the
|
||||
// top-level config.QueryDateTimeParser
|
||||
// Either, but not both endpoints can be nil.
|
||||
func NewDateRangeQuery(start, end time.Time) *DateRangeQuery {
|
||||
return NewDateRangeInclusiveQuery(start, end, nil, nil)
|
||||
}
|
||||
|
||||
// NewDateRangeInclusiveQuery creates a new Query for ranges
|
||||
// of date values.
|
||||
// Date strings are parsed using the DateTimeParser configured in the
|
||||
// top-level config.QueryDateTimeParser
|
||||
// Either, but not both endpoints can be nil.
|
||||
// startInclusive and endInclusive control inclusion of the endpoints.
|
||||
func NewDateRangeInclusiveQuery(start, end time.Time, startInclusive, endInclusive *bool) *DateRangeQuery {
|
||||
return &DateRangeQuery{
|
||||
Start: BleveQueryTime{start},
|
||||
End: BleveQueryTime{end},
|
||||
InclusiveStart: startInclusive,
|
||||
InclusiveEnd: endInclusive,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *DateRangeQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.Boost = &boost
|
||||
}
|
||||
|
||||
func (q *DateRangeQuery) SetField(f string) {
|
||||
q.Field = f
|
||||
}
|
||||
|
||||
func (q *DateRangeQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
min, max, err := q.parseEndpoints()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
field := q.Field
|
||||
if q.Field == "" {
|
||||
field = m.DefaultSearchField()
|
||||
}
|
||||
|
||||
return searchers.NewNumericRangeSearcher(i, min, max, q.InclusiveStart, q.InclusiveEnd, field, q.Boost.Value(), explain)
|
||||
}
|
||||
|
||||
func (q *DateRangeQuery) parseEndpoints() (*float64, *float64, error) {
|
||||
min := math.Inf(-1)
|
||||
max := math.Inf(1)
|
||||
if !q.Start.IsZero() {
|
||||
min = numeric_util.Int64ToFloat64(q.Start.UnixNano())
|
||||
}
|
||||
if !q.End.IsZero() {
|
||||
max = numeric_util.Int64ToFloat64(q.End.UnixNano())
|
||||
}
|
||||
|
||||
return &min, &max, nil
|
||||
}
|
||||
|
||||
func (q *DateRangeQuery) Validate() error {
|
||||
if q.Start.IsZero() && q.End.IsZero() {
|
||||
return fmt.Errorf("must specify start or end")
|
||||
}
|
||||
_, _, err := q.parseEndpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
// except in compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searchers"
|
||||
)
|
||||
|
||||
type DisjunctionQuery struct {
|
||||
Disjuncts []Query `json:"disjuncts"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
Min float64 `json:"min"`
|
||||
}
|
||||
|
||||
// NewDisjunctionQuery creates a new compound Query.
|
||||
// Result documents satisfy at least one Query.
|
||||
func NewDisjunctionQuery(disjuncts []Query) *DisjunctionQuery {
|
||||
return &DisjunctionQuery{
|
||||
Disjuncts: disjuncts,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *DisjunctionQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.Boost = &boost
|
||||
}
|
||||
|
||||
func (q *DisjunctionQuery) AddQuery(aq ...Query) {
|
||||
for _, aaq := range aq {
|
||||
q.Disjuncts = append(q.Disjuncts, aaq)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *DisjunctionQuery) SetMin(m float64) {
|
||||
q.Min = m
|
||||
}
|
||||
|
||||
func (q *DisjunctionQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
ss := make([]search.Searcher, len(q.Disjuncts))
|
||||
for in, disjunct := range q.Disjuncts {
|
||||
var err error
|
||||
ss[in], err = disjunct.Searcher(i, m, explain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return searchers.NewDisjunctionSearcher(i, ss, q.Min, explain)
|
||||
}
|
||||
|
||||
func (q *DisjunctionQuery) Validate() error {
|
||||
if int(q.Min) > len(q.Disjuncts) {
|
||||
return fmt.Errorf("disjunction query has fewer than the minimum number of clauses to satisfy")
|
||||
}
|
||||
for _, q := range q.Disjuncts {
|
||||
if q, ok := q.(ValidatableQuery); ok {
|
||||
err := q.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *DisjunctionQuery) UnmarshalJSON(data []byte) error {
|
||||
tmp := struct {
|
||||
Disjuncts []json.RawMessage `json:"disjuncts"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
Min float64 `json:"min"`
|
||||
}{}
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Disjuncts = make([]Query, len(tmp.Disjuncts))
|
||||
for i, term := range tmp.Disjuncts {
|
||||
query, err := ParseQuery(term)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Disjuncts[i] = query
|
||||
}
|
||||
q.Boost = tmp.Boost
|
||||
q.Min = tmp.Min
|
||||
return nil
|
||||
}
|
|
@ -7,50 +7,34 @@
|
|||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
package query
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searchers"
|
||||
)
|
||||
|
||||
type docIDQuery struct {
|
||||
IDs []string `json:"ids"`
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
type DocIDQuery struct {
|
||||
IDs []string `json:"ids"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewDocIDQuery creates a new Query object returning indexed documents among
|
||||
// the specified set. Combine it with ConjunctionQuery to restrict the scope of
|
||||
// other queries output.
|
||||
func NewDocIDQuery(ids []string) *docIDQuery {
|
||||
return &docIDQuery{
|
||||
IDs: ids,
|
||||
BoostVal: 1.0,
|
||||
func NewDocIDQuery(ids []string) *DocIDQuery {
|
||||
return &DocIDQuery{
|
||||
IDs: ids,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *docIDQuery) Boost() float64 {
|
||||
return q.BoostVal
|
||||
func (q *DocIDQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.Boost = &boost
|
||||
}
|
||||
|
||||
func (q *docIDQuery) SetBoost(b float64) Query {
|
||||
q.BoostVal = b
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *docIDQuery) Field() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (q *docIDQuery) SetField(f string) Query {
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *docIDQuery) Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error) {
|
||||
return searchers.NewDocIDSearcher(i, q.IDs, q.BoostVal, explain)
|
||||
}
|
||||
|
||||
func (q *docIDQuery) Validate() error {
|
||||
return nil
|
||||
func (q *DocIDQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
return searchers.NewDocIDSearcher(i, q.IDs, q.Boost.Value(), explain)
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
// except in compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searchers"
|
||||
)
|
||||
|
||||
type FuzzyQuery struct {
|
||||
Term string `json:"term"`
|
||||
Prefix int `json:"prefix_length"`
|
||||
Fuzziness int `json:"fuzziness"`
|
||||
Field string `json:"field,omitempty"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewFuzzyQuery creates a new Query which finds
|
||||
// documents containing terms within a specific
|
||||
// fuzziness of the specified term.
|
||||
// The default fuzziness is 2.
|
||||
//
|
||||
// The current implementation uses Levenshtein edit
|
||||
// distance as the fuzziness metric.
|
||||
func NewFuzzyQuery(term string) *FuzzyQuery {
|
||||
return &FuzzyQuery{
|
||||
Term: term,
|
||||
Fuzziness: 2,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *FuzzyQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.Boost = &boost
|
||||
}
|
||||
|
||||
func (q *FuzzyQuery) SetField(f string) {
|
||||
q.Field = f
|
||||
}
|
||||
|
||||
func (q *FuzzyQuery) SetFuzziness(f int) {
|
||||
q.Fuzziness = f
|
||||
}
|
||||
|
||||
func (q *FuzzyQuery) SetPrefix(p int) {
|
||||
q.Prefix = p
|
||||
}
|
||||
|
||||
func (q *FuzzyQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.Field
|
||||
if q.Field == "" {
|
||||
field = m.DefaultSearchField()
|
||||
}
|
||||
return searchers.NewFuzzySearcher(i, q.Term, q.Prefix, q.Fuzziness, field, q.Boost.Value(), explain)
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
// except in compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type MatchQuery struct {
|
||||
Match string `json:"match"`
|
||||
Field string `json:"field,omitempty"`
|
||||
Analyzer string `json:"analyzer,omitempty"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
Prefix int `json:"prefix_length"`
|
||||
Fuzziness int `json:"fuzziness"`
|
||||
Operator MatchQueryOperator `json:"operator,omitempty"`
|
||||
}
|
||||
|
||||
type MatchQueryOperator int
|
||||
|
||||
const (
|
||||
// Document must satisfy AT LEAST ONE of term searches.
|
||||
MatchQueryOperatorOr = 0
|
||||
// Document must satisfy ALL of term searches.
|
||||
MatchQueryOperatorAnd = 1
|
||||
)
|
||||
|
||||
func (o MatchQueryOperator) MarshalJSON() ([]byte, error) {
|
||||
switch o {
|
||||
case MatchQueryOperatorOr:
|
||||
return json.Marshal("or")
|
||||
case MatchQueryOperatorAnd:
|
||||
return json.Marshal("and")
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot marshal match operator %d to JSON", o)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *MatchQueryOperator) UnmarshalJSON(data []byte) error {
|
||||
var operatorString string
|
||||
err := json.Unmarshal(data, &operatorString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch operatorString {
|
||||
case "or":
|
||||
*o = MatchQueryOperatorOr
|
||||
return nil
|
||||
case "and":
|
||||
*o = MatchQueryOperatorAnd
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("cannot unmarshal match operator '%v' from JSON", o)
|
||||
}
|
||||
}
|
||||
|
||||
// NewMatchQuery creates a Query for matching text.
|
||||
// An Analyzer is chosen based on the field.
|
||||
// Input text is analyzed using this analyzer.
|
||||
// Token terms resulting from this analysis are
|
||||
// used to perform term searches. Result documents
|
||||
// must satisfy at least one of these term searches.
|
||||
func NewMatchQuery(match string) *MatchQuery {
|
||||
return &MatchQuery{
|
||||
Match: match,
|
||||
Operator: MatchQueryOperatorOr,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *MatchQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.Boost = &boost
|
||||
}
|
||||
|
||||
func (q *MatchQuery) SetField(f string) {
|
||||
q.Field = f
|
||||
}
|
||||
|
||||
func (q *MatchQuery) SetFuzziness(f int) {
|
||||
q.Fuzziness = f
|
||||
}
|
||||
|
||||
func (q *MatchQuery) SetPrefix(p int) {
|
||||
q.Prefix = p
|
||||
}
|
||||
|
||||
func (q *MatchQuery) SetOperator(operator MatchQueryOperator) {
|
||||
q.Operator = operator
|
||||
}
|
||||
|
||||
func (q *MatchQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
|
||||
field := q.Field
|
||||
if q.Field == "" {
|
||||
field = m.DefaultSearchField()
|
||||
}
|
||||
|
||||
analyzerName := ""
|
||||
if q.Analyzer != "" {
|
||||
analyzerName = q.Analyzer
|
||||
} else {
|
||||
analyzerName = m.AnalyzerNameForPath(field)
|
||||
}
|
||||
analyzer := m.AnalyzerNamed(analyzerName)
|
||||
|
||||
if analyzer == nil {
|
||||
return nil, fmt.Errorf("no analyzer named '%s' registered", q.Analyzer)
|
||||
}
|
||||
|
||||
tokens := analyzer.Analyze([]byte(q.Match))
|
||||
if len(tokens) > 0 {
|
||||
|
||||
tqs := make([]Query, len(tokens))
|
||||
if q.Fuzziness != 0 {
|
||||
for i, token := range tokens {
|
||||
query := NewFuzzyQuery(string(token.Term))
|
||||
query.SetFuzziness(q.Fuzziness)
|
||||
query.SetPrefix(q.Prefix)
|
||||
query.SetField(field)
|
||||
query.SetBoost(q.Boost.Value())
|
||||
tqs[i] = query
|
||||
}
|
||||
} else {
|
||||
for i, token := range tokens {
|
||||
tq := NewTermQuery(string(token.Term))
|
||||
tq.SetField(field)
|
||||
tq.SetBoost(q.Boost.Value())
|
||||
tqs[i] = tq
|
||||
}
|
||||
}
|
||||
|
||||
switch q.Operator {
|
||||
case MatchQueryOperatorOr:
|
||||
shouldQuery := NewDisjunctionQuery(tqs)
|
||||
shouldQuery.SetMin(1)
|
||||
shouldQuery.SetBoost(q.Boost.Value())
|
||||
return shouldQuery.Searcher(i, m, explain)
|
||||
|
||||
case MatchQueryOperatorAnd:
|
||||
mustQuery := NewConjunctionQuery(tqs)
|
||||
mustQuery.SetBoost(q.Boost.Value())
|
||||
return mustQuery.Searcher(i, m, explain)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled operator %d", q.Operator)
|
||||
}
|
||||
}
|
||||
noneQuery := NewMatchNoneQuery()
|
||||
return noneQuery.Searcher(i, m, explain)
|
||||
}
|
|
@ -7,56 +7,39 @@
|
|||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
package query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searchers"
|
||||
)
|
||||
|
||||
type matchAllQuery struct {
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
type MatchAllQuery struct {
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewMatchAllQuery creates a Query which will
|
||||
// match all documents in the index.
|
||||
func NewMatchAllQuery() *matchAllQuery {
|
||||
return &matchAllQuery{
|
||||
BoostVal: 1.0,
|
||||
}
|
||||
func NewMatchAllQuery() *MatchAllQuery {
|
||||
return &MatchAllQuery{}
|
||||
}
|
||||
|
||||
func (q *matchAllQuery) Boost() float64 {
|
||||
return q.BoostVal
|
||||
func (q *MatchAllQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.Boost = &boost
|
||||
}
|
||||
|
||||
func (q *matchAllQuery) SetBoost(b float64) Query {
|
||||
q.BoostVal = b
|
||||
return q
|
||||
func (q *MatchAllQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
return searchers.NewMatchAllSearcher(i, q.Boost.Value(), explain)
|
||||
}
|
||||
|
||||
func (q *matchAllQuery) Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error) {
|
||||
return searchers.NewMatchAllSearcher(i, q.BoostVal, explain)
|
||||
}
|
||||
|
||||
func (q *matchAllQuery) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *matchAllQuery) Field() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (q *matchAllQuery) SetField(f string) Query {
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *matchAllQuery) MarshalJSON() ([]byte, error) {
|
||||
func (q *MatchAllQuery) MarshalJSON() ([]byte, error) {
|
||||
tmp := map[string]interface{}{
|
||||
"boost": q.BoostVal,
|
||||
"boost": q.Boost,
|
||||
"match_all": map[string]interface{}{},
|
||||
}
|
||||
return json.Marshal(tmp)
|
|
@ -7,56 +7,39 @@
|
|||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
package query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searchers"
|
||||
)
|
||||
|
||||
type matchNoneQuery struct {
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
type MatchNoneQuery struct {
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewMatchNoneQuery creates a Query which will not
|
||||
// match any documents in the index.
|
||||
func NewMatchNoneQuery() *matchNoneQuery {
|
||||
return &matchNoneQuery{
|
||||
BoostVal: 1.0,
|
||||
}
|
||||
func NewMatchNoneQuery() *MatchNoneQuery {
|
||||
return &MatchNoneQuery{}
|
||||
}
|
||||
|
||||
func (q *matchNoneQuery) Boost() float64 {
|
||||
return q.BoostVal
|
||||
func (q *MatchNoneQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.Boost = &boost
|
||||
}
|
||||
|
||||
func (q *matchNoneQuery) SetBoost(b float64) Query {
|
||||
q.BoostVal = b
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *matchNoneQuery) Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error) {
|
||||
func (q *MatchNoneQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
return searchers.NewMatchNoneSearcher(i)
|
||||
}
|
||||
|
||||
func (q *matchNoneQuery) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *matchNoneQuery) Field() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (q *matchNoneQuery) SetField(f string) Query {
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *matchNoneQuery) MarshalJSON() ([]byte, error) {
|
||||
func (q *MatchNoneQuery) MarshalJSON() ([]byte, error) {
|
||||
tmp := map[string]interface{}{
|
||||
"boost": q.BoostVal,
|
||||
"boost": q.Boost,
|
||||
"match_none": map[string]interface{}{},
|
||||
}
|
||||
return json.Marshal(tmp)
|
|
@ -7,21 +7,22 @@
|
|||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/analysis"
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type matchPhraseQuery struct {
|
||||
MatchPhrase string `json:"match_phrase"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
Analyzer string `json:"analyzer,omitempty"`
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
type MatchPhraseQuery struct {
|
||||
MatchPhrase string `json:"match_phrase"`
|
||||
Field string `json:"field,omitempty"`
|
||||
Analyzer string `json:"analyzer,omitempty"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewMatchPhraseQuery creates a new Query object
|
||||
|
@ -32,44 +33,34 @@ type matchPhraseQuery struct {
|
|||
// used to build a search phrase. Result documents
|
||||
// must match this phrase. Queried field must have been indexed with
|
||||
// IncludeTermVectors set to true.
|
||||
func NewMatchPhraseQuery(matchPhrase string) *matchPhraseQuery {
|
||||
return &matchPhraseQuery{
|
||||
func NewMatchPhraseQuery(matchPhrase string) *MatchPhraseQuery {
|
||||
return &MatchPhraseQuery{
|
||||
MatchPhrase: matchPhrase,
|
||||
BoostVal: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *matchPhraseQuery) Boost() float64 {
|
||||
return q.BoostVal
|
||||
func (q *MatchPhraseQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.Boost = &boost
|
||||
}
|
||||
|
||||
func (q *matchPhraseQuery) SetBoost(b float64) Query {
|
||||
q.BoostVal = b
|
||||
return q
|
||||
func (q *MatchPhraseQuery) SetField(f string) {
|
||||
q.Field = f
|
||||
}
|
||||
|
||||
func (q *matchPhraseQuery) Field() string {
|
||||
return q.FieldVal
|
||||
}
|
||||
|
||||
func (q *matchPhraseQuery) SetField(f string) Query {
|
||||
q.FieldVal = f
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *matchPhraseQuery) Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.FieldVal
|
||||
if q.FieldVal == "" {
|
||||
field = m.DefaultField
|
||||
func (q *MatchPhraseQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.Field
|
||||
if q.Field == "" {
|
||||
field = m.DefaultSearchField()
|
||||
}
|
||||
|
||||
analyzerName := ""
|
||||
if q.Analyzer != "" {
|
||||
analyzerName = q.Analyzer
|
||||
} else {
|
||||
analyzerName = m.analyzerNameForPath(field)
|
||||
analyzerName = m.AnalyzerNameForPath(field)
|
||||
}
|
||||
analyzer := m.analyzerNamed(analyzerName)
|
||||
analyzer := m.AnalyzerNamed(analyzerName)
|
||||
if analyzer == nil {
|
||||
return nil, fmt.Errorf("no analyzer named '%s' registered", q.Analyzer)
|
||||
}
|
||||
|
@ -77,7 +68,8 @@ func (q *matchPhraseQuery) Searcher(i index.IndexReader, m *IndexMapping, explai
|
|||
tokens := analyzer.Analyze([]byte(q.MatchPhrase))
|
||||
if len(tokens) > 0 {
|
||||
phrase := tokenStreamToPhrase(tokens)
|
||||
phraseQuery := NewPhraseQuery(phrase, field).SetBoost(q.BoostVal)
|
||||
phraseQuery := NewPhraseQuery(phrase, field)
|
||||
phraseQuery.SetBoost(q.Boost.Value())
|
||||
return phraseQuery.Searcher(i, m, explain)
|
||||
}
|
||||
noneQuery := NewMatchNoneQuery()
|
||||
|
@ -109,7 +101,3 @@ func tokenStreamToPhrase(tokens analysis.TokenStream) []string {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *matchPhraseQuery) Validate() error {
|
||||
return nil
|
||||
}
|
|
@ -7,21 +7,24 @@
|
|||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searchers"
|
||||
)
|
||||
|
||||
type numericRangeQuery struct {
|
||||
type NumericRangeQuery struct {
|
||||
Min *float64 `json:"min,omitempty"`
|
||||
Max *float64 `json:"max,omitempty"`
|
||||
InclusiveMin *bool `json:"inclusive_min,omitempty"`
|
||||
InclusiveMax *bool `json:"inclusive_max,omitempty"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
Field string `json:"field,omitempty"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewNumericRangeQuery creates a new Query for ranges
|
||||
|
@ -29,7 +32,7 @@ type numericRangeQuery struct {
|
|||
// Either, but not both endpoints can be nil.
|
||||
// The minimum value is inclusive.
|
||||
// The maximum value is exclusive.
|
||||
func NewNumericRangeQuery(min, max *float64) *numericRangeQuery {
|
||||
func NewNumericRangeQuery(min, max *float64) *NumericRangeQuery {
|
||||
return NewNumericRangeInclusiveQuery(min, max, nil, nil)
|
||||
}
|
||||
|
||||
|
@ -37,45 +40,35 @@ func NewNumericRangeQuery(min, max *float64) *numericRangeQuery {
|
|||
// of numeric values.
|
||||
// Either, but not both endpoints can be nil.
|
||||
// Control endpoint inclusion with inclusiveMin, inclusiveMax.
|
||||
func NewNumericRangeInclusiveQuery(min, max *float64, minInclusive, maxInclusive *bool) *numericRangeQuery {
|
||||
return &numericRangeQuery{
|
||||
func NewNumericRangeInclusiveQuery(min, max *float64, minInclusive, maxInclusive *bool) *NumericRangeQuery {
|
||||
return &NumericRangeQuery{
|
||||
Min: min,
|
||||
Max: max,
|
||||
InclusiveMin: minInclusive,
|
||||
InclusiveMax: maxInclusive,
|
||||
BoostVal: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *numericRangeQuery) Boost() float64 {
|
||||
return q.BoostVal
|
||||
func (q *NumericRangeQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.Boost = &boost
|
||||
}
|
||||
|
||||
func (q *numericRangeQuery) SetBoost(b float64) Query {
|
||||
q.BoostVal = b
|
||||
return q
|
||||
func (q *NumericRangeQuery) SetField(f string) {
|
||||
q.Field = f
|
||||
}
|
||||
|
||||
func (q *numericRangeQuery) Field() string {
|
||||
return q.FieldVal
|
||||
}
|
||||
|
||||
func (q *numericRangeQuery) SetField(f string) Query {
|
||||
q.FieldVal = f
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *numericRangeQuery) Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.FieldVal
|
||||
if q.FieldVal == "" {
|
||||
field = m.DefaultField
|
||||
func (q *NumericRangeQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.Field
|
||||
if q.Field == "" {
|
||||
field = m.DefaultSearchField()
|
||||
}
|
||||
return searchers.NewNumericRangeSearcher(i, q.Min, q.Max, q.InclusiveMin, q.InclusiveMax, field, q.BoostVal, explain)
|
||||
return searchers.NewNumericRangeSearcher(i, q.Min, q.Max, q.InclusiveMin, q.InclusiveMax, field, q.Boost.Value(), explain)
|
||||
}
|
||||
|
||||
func (q *numericRangeQuery) Validate() error {
|
||||
func (q *NumericRangeQuery) Validate() error {
|
||||
if q.Min == nil && q.Min == q.Max {
|
||||
return ErrorNumericQueryNoBounds
|
||||
return fmt.Errorf("numeric range query must specify min or max")
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -7,20 +7,22 @@
|
|||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
package query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searchers"
|
||||
)
|
||||
|
||||
type phraseQuery struct {
|
||||
type PhraseQuery struct {
|
||||
Terms []string `json:"terms"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
Field string `json:"field,omitempty"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
termQueries []Query
|
||||
}
|
||||
|
||||
|
@ -30,31 +32,28 @@ type phraseQuery struct {
|
|||
// order, at the correct index offsets, in the
|
||||
// specified field. Queried field must have been indexed with
|
||||
// IncludeTermVectors set to true.
|
||||
func NewPhraseQuery(terms []string, field string) *phraseQuery {
|
||||
func NewPhraseQuery(terms []string, field string) *PhraseQuery {
|
||||
termQueries := make([]Query, 0)
|
||||
for _, term := range terms {
|
||||
if term != "" {
|
||||
termQueries = append(termQueries, NewTermQuery(term).SetField(field))
|
||||
tq := NewTermQuery(term)
|
||||
tq.SetField(field)
|
||||
termQueries = append(termQueries, tq)
|
||||
}
|
||||
}
|
||||
return &phraseQuery{
|
||||
return &PhraseQuery{
|
||||
Terms: terms,
|
||||
FieldVal: field,
|
||||
BoostVal: 1.0,
|
||||
Field: field,
|
||||
termQueries: termQueries,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *phraseQuery) Boost() float64 {
|
||||
return q.BoostVal
|
||||
func (q *PhraseQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.Boost = &boost
|
||||
}
|
||||
|
||||
func (q *phraseQuery) SetBoost(b float64) Query {
|
||||
q.BoostVal = b
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *phraseQuery) Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error) {
|
||||
func (q *PhraseQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
|
||||
conjunctionQuery := NewConjunctionQuery(q.termQueries)
|
||||
conjunctionSearcher, err := conjunctionQuery.Searcher(i, m, explain)
|
||||
|
@ -64,37 +63,26 @@ func (q *phraseQuery) Searcher(i index.IndexReader, m *IndexMapping, explain boo
|
|||
return searchers.NewPhraseSearcher(i, conjunctionSearcher.(*searchers.ConjunctionSearcher), q.Terms)
|
||||
}
|
||||
|
||||
func (q *phraseQuery) Validate() error {
|
||||
func (q *PhraseQuery) Validate() error {
|
||||
if len(q.termQueries) < 1 {
|
||||
return ErrorPhraseQueryNoTerms
|
||||
return fmt.Errorf("phrase query must contain at least one term")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *phraseQuery) UnmarshalJSON(data []byte) error {
|
||||
type _phraseQuery phraseQuery
|
||||
func (q *PhraseQuery) UnmarshalJSON(data []byte) error {
|
||||
type _phraseQuery PhraseQuery
|
||||
tmp := _phraseQuery{}
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Terms = tmp.Terms
|
||||
q.FieldVal = tmp.FieldVal
|
||||
q.BoostVal = tmp.BoostVal
|
||||
if q.BoostVal == 0 {
|
||||
q.BoostVal = 1
|
||||
}
|
||||
q.Field = tmp.Field
|
||||
q.Boost = tmp.Boost
|
||||
q.termQueries = make([]Query, len(q.Terms))
|
||||
for i, term := range q.Terms {
|
||||
q.termQueries[i] = &termQuery{Term: term, FieldVal: q.FieldVal, BoostVal: q.BoostVal}
|
||||
q.termQueries[i] = &TermQuery{Term: term, Field: q.Field, Boost: q.Boost}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *phraseQuery) Field() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (q *phraseQuery) SetField(f string) Query {
|
||||
return q
|
||||
}
|
|
@ -7,56 +7,43 @@
|
|||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
package query
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searchers"
|
||||
)
|
||||
|
||||
type prefixQuery struct {
|
||||
Prefix string `json:"prefix"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
type PrefixQuery struct {
|
||||
Prefix string `json:"prefix"`
|
||||
Field string `json:"field,omitempty"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewPrefixQuery creates a new Query which finds
|
||||
// documents containing terms that start with the
|
||||
// specified prefix.
|
||||
func NewPrefixQuery(prefix string) *prefixQuery {
|
||||
return &prefixQuery{
|
||||
Prefix: prefix,
|
||||
BoostVal: 1.0,
|
||||
func NewPrefixQuery(prefix string) *PrefixQuery {
|
||||
return &PrefixQuery{
|
||||
Prefix: prefix,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *prefixQuery) Boost() float64 {
|
||||
return q.BoostVal
|
||||
func (q *PrefixQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.Boost = &boost
|
||||
}
|
||||
|
||||
func (q *prefixQuery) SetBoost(b float64) Query {
|
||||
q.BoostVal = b
|
||||
return q
|
||||
func (q *PrefixQuery) SetField(f string) {
|
||||
q.Field = f
|
||||
}
|
||||
|
||||
func (q *prefixQuery) Field() string {
|
||||
return q.FieldVal
|
||||
}
|
||||
|
||||
func (q *prefixQuery) SetField(f string) Query {
|
||||
q.FieldVal = f
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *prefixQuery) Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.FieldVal
|
||||
if q.FieldVal == "" {
|
||||
field = m.DefaultField
|
||||
func (q *PrefixQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.Field
|
||||
if q.Field == "" {
|
||||
field = m.DefaultSearchField()
|
||||
}
|
||||
return searchers.NewTermPrefixSearcher(i, q.Prefix, field, q.BoostVal, explain)
|
||||
}
|
||||
|
||||
func (q *prefixQuery) Validate() error {
|
||||
return nil
|
||||
return searchers.NewTermPrefixSearcher(i, q.Prefix, field, q.Boost.Value(), explain)
|
||||
}
|
|
@ -0,0 +1,312 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
// except in compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
var logger = log.New(ioutil.Discard, "bleve mapping ", log.LstdFlags)
|
||||
|
||||
// SetLog sets the logger used for logging
|
||||
// by default log messages are sent to ioutil.Discard
|
||||
func SetLog(l *log.Logger) {
|
||||
logger = l
|
||||
}
|
||||
|
||||
// A Query represents a description of the type
|
||||
// and parameters for a query into the index.
|
||||
type Query interface {
|
||||
Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error)
|
||||
}
|
||||
|
||||
// A BoostableQuery represents a Query which can be boosted
|
||||
// relative to other queries.
|
||||
type BoostableQuery interface {
|
||||
Query
|
||||
SetBoost(b float64)
|
||||
}
|
||||
|
||||
// A FieldableQuery represents a Query which can be restricted
|
||||
// to a single field.
|
||||
type FieldableQuery interface {
|
||||
Query
|
||||
SetField(f string)
|
||||
}
|
||||
|
||||
// A ValidatableQuery represents a Query which can be validated
|
||||
// prior to execution.
|
||||
type ValidatableQuery interface {
|
||||
Query
|
||||
Validate() error
|
||||
}
|
||||
|
||||
// ParseQuery deserializes a JSON representation of
|
||||
// a Query object.
|
||||
func ParseQuery(input []byte) (Query, error) {
|
||||
var tmp map[string]interface{}
|
||||
err := json.Unmarshal(input, &tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, isMatchQuery := tmp["match"]
|
||||
_, hasFuzziness := tmp["fuzziness"]
|
||||
if hasFuzziness && !isMatchQuery {
|
||||
var rv FuzzyQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, isTermQuery := tmp["term"]
|
||||
if isTermQuery {
|
||||
var rv TermQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
if isMatchQuery {
|
||||
var rv MatchQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, isMatchPhraseQuery := tmp["match_phrase"]
|
||||
if isMatchPhraseQuery {
|
||||
var rv MatchPhraseQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasMust := tmp["must"]
|
||||
_, hasShould := tmp["should"]
|
||||
_, hasMustNot := tmp["must_not"]
|
||||
if hasMust || hasShould || hasMustNot {
|
||||
var rv BooleanQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasTerms := tmp["terms"]
|
||||
if hasTerms {
|
||||
var rv PhraseQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasConjuncts := tmp["conjuncts"]
|
||||
if hasConjuncts {
|
||||
var rv ConjunctionQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasDisjuncts := tmp["disjuncts"]
|
||||
if hasDisjuncts {
|
||||
var rv DisjunctionQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
|
||||
_, hasSyntaxQuery := tmp["query"]
|
||||
if hasSyntaxQuery {
|
||||
var rv QueryStringQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasMin := tmp["min"]
|
||||
_, hasMax := tmp["max"]
|
||||
if hasMin || hasMax {
|
||||
var rv NumericRangeQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasStart := tmp["start"]
|
||||
_, hasEnd := tmp["end"]
|
||||
if hasStart || hasEnd {
|
||||
var rv DateRangeQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasPrefix := tmp["prefix"]
|
||||
if hasPrefix {
|
||||
var rv PrefixQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasRegexp := tmp["regexp"]
|
||||
if hasRegexp {
|
||||
var rv RegexpQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasWildcard := tmp["wildcard"]
|
||||
if hasWildcard {
|
||||
var rv WildcardQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasMatchAll := tmp["match_all"]
|
||||
if hasMatchAll {
|
||||
var rv MatchAllQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasMatchNone := tmp["match_none"]
|
||||
if hasMatchNone {
|
||||
var rv MatchNoneQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
_, hasDocIds := tmp["ids"]
|
||||
if hasDocIds {
|
||||
var rv DocIDQuery
|
||||
err := json.Unmarshal(input, &rv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rv, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown query type")
|
||||
}
|
||||
|
||||
// expandQuery traverses the input query tree and returns a new tree where
|
||||
// query string queries have been expanded into base queries. Returned tree may
|
||||
// reference queries from the input tree or new queries.
|
||||
func expandQuery(m mapping.IndexMapping, query Query) (Query, error) {
|
||||
var expand func(query Query) (Query, error)
|
||||
var expandSlice func(queries []Query) ([]Query, error)
|
||||
|
||||
expandSlice = func(queries []Query) ([]Query, error) {
|
||||
expanded := []Query{}
|
||||
for _, q := range queries {
|
||||
exp, err := expand(q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expanded = append(expanded, exp)
|
||||
}
|
||||
return expanded, nil
|
||||
}
|
||||
|
||||
expand = func(query Query) (Query, error) {
|
||||
switch query.(type) {
|
||||
case *QueryStringQuery:
|
||||
q := query.(*QueryStringQuery)
|
||||
parsed, err := parseQuerySyntax(q.Query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse '%s': %s", q.Query, err)
|
||||
}
|
||||
return expand(parsed)
|
||||
case *ConjunctionQuery:
|
||||
q := *query.(*ConjunctionQuery)
|
||||
children, err := expandSlice(q.Conjuncts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.Conjuncts = children
|
||||
return &q, nil
|
||||
case *DisjunctionQuery:
|
||||
q := *query.(*DisjunctionQuery)
|
||||
children, err := expandSlice(q.Disjuncts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.Disjuncts = children
|
||||
return &q, nil
|
||||
case *BooleanQuery:
|
||||
q := *query.(*BooleanQuery)
|
||||
var err error
|
||||
q.Must, err = expand(q.Must)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.Should, err = expand(q.Should)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.MustNot, err = expand(q.MustNot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &q, nil
|
||||
case *PhraseQuery:
|
||||
q := *query.(*PhraseQuery)
|
||||
children, err := expandSlice(q.termQueries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.termQueries = children
|
||||
return &q, nil
|
||||
default:
|
||||
return query, nil
|
||||
}
|
||||
}
|
||||
return expand(query)
|
||||
}
|
||||
|
||||
// DumpQuery returns a string representation of the query tree, where query
|
||||
// string queries have been expanded into base queries. The output format is
|
||||
// meant for debugging purpose and may change in the future.
|
||||
func DumpQuery(m mapping.IndexMapping, query Query) (string, error) {
|
||||
q, err := expandQuery(m, query)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
data, err := json.MarshalIndent(q, "", " ")
|
||||
return string(data), err
|
||||
}
|
|
@ -7,38 +7,34 @@
|
|||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
package query
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
)
|
||||
|
||||
type queryStringQuery struct {
|
||||
Query string `json:"query"`
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
type QueryStringQuery struct {
|
||||
Query string `json:"query"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewQueryStringQuery creates a new Query used for
|
||||
// finding documents that satisfy a query string. The
|
||||
// query string is a small query language for humans.
|
||||
func NewQueryStringQuery(query string) *queryStringQuery {
|
||||
return &queryStringQuery{
|
||||
Query: query,
|
||||
BoostVal: 1.0,
|
||||
func NewQueryStringQuery(query string) *QueryStringQuery {
|
||||
return &QueryStringQuery{
|
||||
Query: query,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *queryStringQuery) Boost() float64 {
|
||||
return q.BoostVal
|
||||
func (q *QueryStringQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.Boost = &boost
|
||||
}
|
||||
|
||||
func (q *queryStringQuery) SetBoost(b float64) Query {
|
||||
q.BoostVal = b
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *queryStringQuery) Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error) {
|
||||
func (q *QueryStringQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
newQuery, err := parseQuerySyntax(q.Query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -46,18 +42,13 @@ func (q *queryStringQuery) Searcher(i index.IndexReader, m *IndexMapping, explai
|
|||
return newQuery.Searcher(i, m, explain)
|
||||
}
|
||||
|
||||
func (q *queryStringQuery) Validate() error {
|
||||
func (q *QueryStringQuery) Validate() error {
|
||||
newQuery, err := parseQuerySyntax(q.Query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return newQuery.Validate()
|
||||
}
|
||||
|
||||
func (q *queryStringQuery) Field() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (q *queryStringQuery) SetField(f string) Query {
|
||||
return q
|
||||
if newQuery, ok := newQuery.(ValidatableQuery); ok {
|
||||
return newQuery.Validate()
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
%{
|
||||
package bleve
|
||||
package query
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func logDebugGrammar(format string, v ...interface{}) {
|
||||
|
@ -17,7 +18,8 @@ func logDebugGrammar(format string, v ...interface{}) {
|
|||
s string
|
||||
n int
|
||||
f float64
|
||||
q Query}
|
||||
q Query
|
||||
pf *float64}
|
||||
|
||||
%token tSTRING tPHRASE tPLUS tMINUS tCOLON tBOOST tNUMBER tSTRING tGREATER tLESS
|
||||
tEQUAL tTILDE
|
||||
|
@ -28,7 +30,7 @@ tEQUAL tTILDE
|
|||
%type <s> tTILDE
|
||||
%type <s> tBOOST
|
||||
%type <q> searchBase
|
||||
%type <f> searchSuffix
|
||||
%type <pf> searchSuffix
|
||||
%type <n> searchPrefix
|
||||
|
||||
%%
|
||||
|
@ -50,7 +52,11 @@ searchPart {
|
|||
searchPart:
|
||||
searchPrefix searchBase searchSuffix {
|
||||
query := $2
|
||||
query.SetBoost($3)
|
||||
if $3 != nil {
|
||||
if query, ok := query.(BoostableQuery); ok {
|
||||
query.SetBoost(*$3)
|
||||
}
|
||||
}
|
||||
switch($1) {
|
||||
case queryShould:
|
||||
yylex.(*lexerWrapper).query.AddShould(query)
|
||||
|
@ -81,7 +87,7 @@ searchBase:
|
|||
tSTRING {
|
||||
str := $1
|
||||
logDebugGrammar("STRING - %s", str)
|
||||
var q Query
|
||||
var q FieldableQuery
|
||||
if strings.HasPrefix(str, "/") && strings.HasSuffix(str, "/") {
|
||||
q = NewRegexpQuery(str[1:len(str)-1])
|
||||
} else if strings.ContainsAny(str, "*?"){
|
||||
|
@ -136,7 +142,7 @@ tSTRING tCOLON tSTRING {
|
|||
field := $1
|
||||
str := $3
|
||||
logDebugGrammar("FIELD - %s STRING - %s", field, str)
|
||||
var q Query
|
||||
var q FieldableQuery
|
||||
if strings.HasPrefix(str, "/") && strings.HasSuffix(str, "/") {
|
||||
q = NewRegexpQuery(str[1:len(str)-1])
|
||||
} else if strings.ContainsAny(str, "*?"){
|
||||
|
@ -152,7 +158,8 @@ tSTRING tCOLON tNUMBER {
|
|||
field := $1
|
||||
str := $3
|
||||
logDebugGrammar("FIELD - %s STRING - %s", field, str)
|
||||
q := NewMatchQuery(str).SetField(field)
|
||||
q := NewMatchQuery(str)
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
|
@ -160,7 +167,8 @@ tSTRING tCOLON tPHRASE {
|
|||
field := $1
|
||||
phrase := $3
|
||||
logDebugGrammar("FIELD - %s PHRASE - %s", field, phrase)
|
||||
q := NewMatchPhraseQuery(phrase).SetField(field)
|
||||
q := NewMatchPhraseQuery(phrase)
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
|
@ -169,7 +177,8 @@ tSTRING tCOLON tGREATER tNUMBER {
|
|||
min, _ := strconv.ParseFloat($4, 64)
|
||||
minInclusive := false
|
||||
logDebugGrammar("FIELD - GREATER THAN %f", min)
|
||||
q := NewNumericRangeInclusiveQuery(&min, nil, &minInclusive, nil).SetField(field)
|
||||
q := NewNumericRangeInclusiveQuery(&min, nil, &minInclusive, nil)
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
|
@ -178,7 +187,8 @@ tSTRING tCOLON tGREATER tEQUAL tNUMBER {
|
|||
min, _ := strconv.ParseFloat($5, 64)
|
||||
minInclusive := true
|
||||
logDebugGrammar("FIELD - GREATER THAN OR EQUAL %f", min)
|
||||
q := NewNumericRangeInclusiveQuery(&min, nil, &minInclusive, nil).SetField(field)
|
||||
q := NewNumericRangeInclusiveQuery(&min, nil, &minInclusive, nil)
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
|
@ -187,7 +197,8 @@ tSTRING tCOLON tLESS tNUMBER {
|
|||
max, _ := strconv.ParseFloat($4, 64)
|
||||
maxInclusive := false
|
||||
logDebugGrammar("FIELD - LESS THAN %f", max)
|
||||
q := NewNumericRangeInclusiveQuery(nil, &max, nil, &maxInclusive).SetField(field)
|
||||
q := NewNumericRangeInclusiveQuery(nil, &max, nil, &maxInclusive)
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
|
@ -196,7 +207,8 @@ tSTRING tCOLON tLESS tEQUAL tNUMBER {
|
|||
max, _ := strconv.ParseFloat($5, 64)
|
||||
maxInclusive := true
|
||||
logDebugGrammar("FIELD - LESS THAN OR EQUAL %f", max)
|
||||
q := NewNumericRangeInclusiveQuery(nil, &max, nil, &maxInclusive).SetField(field)
|
||||
q := NewNumericRangeInclusiveQuery(nil, &max, nil, &maxInclusive)
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
|
@ -206,7 +218,12 @@ tSTRING tCOLON tGREATER tPHRASE {
|
|||
phrase := $4
|
||||
|
||||
logDebugGrammar("FIELD - GREATER THAN DATE %s", phrase)
|
||||
q := NewDateRangeInclusiveQuery(&phrase, nil, &minInclusive, nil).SetField(field)
|
||||
minTime, err := QueryTimeFromString(phrase)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid time: %v", err))
|
||||
}
|
||||
q := NewDateRangeInclusiveQuery(minTime, time.Time{}, &minInclusive, nil)
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
|
@ -216,7 +233,12 @@ tSTRING tCOLON tGREATER tEQUAL tPHRASE {
|
|||
phrase := $5
|
||||
|
||||
logDebugGrammar("FIELD - GREATER THAN OR EQUAL DATE %s", phrase)
|
||||
q := NewDateRangeInclusiveQuery(&phrase, nil, &minInclusive, nil).SetField(field)
|
||||
minTime, err := QueryTimeFromString(phrase)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid time: %v", err))
|
||||
}
|
||||
q := NewDateRangeInclusiveQuery(minTime, time.Time{}, &minInclusive, nil)
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
|
@ -226,7 +248,12 @@ tSTRING tCOLON tLESS tPHRASE {
|
|||
phrase := $4
|
||||
|
||||
logDebugGrammar("FIELD - LESS THAN DATE %s", phrase)
|
||||
q := NewDateRangeInclusiveQuery(nil, &phrase, nil, &maxInclusive).SetField(field)
|
||||
maxTime, err := QueryTimeFromString(phrase)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid time: %v", err))
|
||||
}
|
||||
q := NewDateRangeInclusiveQuery(time.Time{}, maxTime, nil, &maxInclusive)
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
}
|
||||
|
|
||||
|
@ -236,20 +263,27 @@ tSTRING tCOLON tLESS tEQUAL tPHRASE {
|
|||
phrase := $5
|
||||
|
||||
logDebugGrammar("FIELD - LESS THAN OR EQUAL DATE %s", phrase)
|
||||
q := NewDateRangeInclusiveQuery(nil, &phrase, nil, &maxInclusive).SetField(field)
|
||||
maxTime, err := QueryTimeFromString(phrase)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid time: %v", err))
|
||||
}
|
||||
q := NewDateRangeInclusiveQuery(time.Time{}, maxTime, nil, &maxInclusive)
|
||||
q.SetField(field)
|
||||
$$ = q
|
||||
};
|
||||
|
||||
searchSuffix:
|
||||
/* empty */ {
|
||||
$$ = 1.0
|
||||
$$ = nil
|
||||
}
|
||||
|
|
||||
tBOOST {
|
||||
$$ = nil
|
||||
boost, err := strconv.ParseFloat($1, 64)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid boost value: %v", err))
|
||||
} else {
|
||||
$$ = &boost
|
||||
}
|
||||
$$ = boost
|
||||
logDebugGrammar("BOOST %f", boost)
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
package bleve
|
||||
package query
|
||||
|
||||
import __yyfmt__ "fmt"
|
||||
|
||||
|
@ -7,6 +7,7 @@ import (
|
|||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func logDebugGrammar(format string, v ...interface{}) {
|
||||
|
@ -15,13 +16,14 @@ func logDebugGrammar(format string, v ...interface{}) {
|
|||
}
|
||||
}
|
||||
|
||||
//line query_string.y:16
|
||||
//line query_string.y:17
|
||||
type yySymType struct {
|
||||
yys int
|
||||
s string
|
||||
n int
|
||||
f float64
|
||||
q Query
|
||||
pf *float64
|
||||
}
|
||||
|
||||
const tSTRING = 57346
|
||||
|
@ -472,28 +474,32 @@ yydefault:
|
|||
|
||||
case 1:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line query_string.y:37
|
||||
//line query_string.y:39
|
||||
{
|
||||
logDebugGrammar("INPUT")
|
||||
}
|
||||
case 2:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
//line query_string.y:42
|
||||
//line query_string.y:44
|
||||
{
|
||||
logDebugGrammar("SEARCH PARTS")
|
||||
}
|
||||
case 3:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line query_string.y:46
|
||||
//line query_string.y:48
|
||||
{
|
||||
logDebugGrammar("SEARCH PART")
|
||||
}
|
||||
case 4:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line query_string.y:51
|
||||
//line query_string.y:53
|
||||
{
|
||||
query := yyDollar[2].q
|
||||
query.SetBoost(yyDollar[3].f)
|
||||
if yyDollar[3].pf != nil {
|
||||
if query, ok := query.(BoostableQuery); ok {
|
||||
query.SetBoost(*yyDollar[3].pf)
|
||||
}
|
||||
}
|
||||
switch yyDollar[1].n {
|
||||
case queryShould:
|
||||
yylex.(*lexerWrapper).query.AddShould(query)
|
||||
|
@ -505,31 +511,31 @@ yydefault:
|
|||
}
|
||||
case 5:
|
||||
yyDollar = yyS[yypt-0 : yypt+1]
|
||||
//line query_string.y:66
|
||||
//line query_string.y:72
|
||||
{
|
||||
yyVAL.n = queryShould
|
||||
}
|
||||
case 6:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line query_string.y:70
|
||||
//line query_string.y:76
|
||||
{
|
||||
logDebugGrammar("PLUS")
|
||||
yyVAL.n = queryMust
|
||||
}
|
||||
case 7:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line query_string.y:75
|
||||
//line query_string.y:81
|
||||
{
|
||||
logDebugGrammar("MINUS")
|
||||
yyVAL.n = queryMustNot
|
||||
}
|
||||
case 8:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line query_string.y:81
|
||||
//line query_string.y:87
|
||||
{
|
||||
str := yyDollar[1].s
|
||||
logDebugGrammar("STRING - %s", str)
|
||||
var q Query
|
||||
var q FieldableQuery
|
||||
if strings.HasPrefix(str, "/") && strings.HasSuffix(str, "/") {
|
||||
q = NewRegexpQuery(str[1 : len(str)-1])
|
||||
} else if strings.ContainsAny(str, "*?") {
|
||||
|
@ -541,7 +547,7 @@ yydefault:
|
|||
}
|
||||
case 9:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
//line query_string.y:95
|
||||
//line query_string.y:101
|
||||
{
|
||||
str := yyDollar[1].s
|
||||
fuzziness, err := strconv.ParseFloat(yyDollar[2].s, 64)
|
||||
|
@ -555,7 +561,7 @@ yydefault:
|
|||
}
|
||||
case 10:
|
||||
yyDollar = yyS[yypt-4 : yypt+1]
|
||||
//line query_string.y:107
|
||||
//line query_string.y:113
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
str := yyDollar[3].s
|
||||
|
@ -571,7 +577,7 @@ yydefault:
|
|||
}
|
||||
case 11:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line query_string.y:121
|
||||
//line query_string.y:127
|
||||
{
|
||||
str := yyDollar[1].s
|
||||
logDebugGrammar("STRING - %s", str)
|
||||
|
@ -580,7 +586,7 @@ yydefault:
|
|||
}
|
||||
case 12:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line query_string.y:128
|
||||
//line query_string.y:134
|
||||
{
|
||||
phrase := yyDollar[1].s
|
||||
logDebugGrammar("PHRASE - %s", phrase)
|
||||
|
@ -589,12 +595,12 @@ yydefault:
|
|||
}
|
||||
case 13:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line query_string.y:135
|
||||
//line query_string.y:141
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
str := yyDollar[3].s
|
||||
logDebugGrammar("FIELD - %s STRING - %s", field, str)
|
||||
var q Query
|
||||
var q FieldableQuery
|
||||
if strings.HasPrefix(str, "/") && strings.HasSuffix(str, "/") {
|
||||
q = NewRegexpQuery(str[1 : len(str)-1])
|
||||
} else if strings.ContainsAny(str, "*?") {
|
||||
|
@ -607,131 +613,159 @@ yydefault:
|
|||
}
|
||||
case 14:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line query_string.y:151
|
||||
//line query_string.y:157
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
str := yyDollar[3].s
|
||||
logDebugGrammar("FIELD - %s STRING - %s", field, str)
|
||||
q := NewMatchQuery(str).SetField(field)
|
||||
q := NewMatchQuery(str)
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 15:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
//line query_string.y:159
|
||||
//line query_string.y:166
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
phrase := yyDollar[3].s
|
||||
logDebugGrammar("FIELD - %s PHRASE - %s", field, phrase)
|
||||
q := NewMatchPhraseQuery(phrase).SetField(field)
|
||||
q := NewMatchPhraseQuery(phrase)
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 16:
|
||||
yyDollar = yyS[yypt-4 : yypt+1]
|
||||
//line query_string.y:167
|
||||
//line query_string.y:175
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
min, _ := strconv.ParseFloat(yyDollar[4].s, 64)
|
||||
minInclusive := false
|
||||
logDebugGrammar("FIELD - GREATER THAN %f", min)
|
||||
q := NewNumericRangeInclusiveQuery(&min, nil, &minInclusive, nil).SetField(field)
|
||||
q := NewNumericRangeInclusiveQuery(&min, nil, &minInclusive, nil)
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 17:
|
||||
yyDollar = yyS[yypt-5 : yypt+1]
|
||||
//line query_string.y:176
|
||||
//line query_string.y:185
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
min, _ := strconv.ParseFloat(yyDollar[5].s, 64)
|
||||
minInclusive := true
|
||||
logDebugGrammar("FIELD - GREATER THAN OR EQUAL %f", min)
|
||||
q := NewNumericRangeInclusiveQuery(&min, nil, &minInclusive, nil).SetField(field)
|
||||
q := NewNumericRangeInclusiveQuery(&min, nil, &minInclusive, nil)
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 18:
|
||||
yyDollar = yyS[yypt-4 : yypt+1]
|
||||
//line query_string.y:185
|
||||
//line query_string.y:195
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
max, _ := strconv.ParseFloat(yyDollar[4].s, 64)
|
||||
maxInclusive := false
|
||||
logDebugGrammar("FIELD - LESS THAN %f", max)
|
||||
q := NewNumericRangeInclusiveQuery(nil, &max, nil, &maxInclusive).SetField(field)
|
||||
q := NewNumericRangeInclusiveQuery(nil, &max, nil, &maxInclusive)
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 19:
|
||||
yyDollar = yyS[yypt-5 : yypt+1]
|
||||
//line query_string.y:194
|
||||
//line query_string.y:205
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
max, _ := strconv.ParseFloat(yyDollar[5].s, 64)
|
||||
maxInclusive := true
|
||||
logDebugGrammar("FIELD - LESS THAN OR EQUAL %f", max)
|
||||
q := NewNumericRangeInclusiveQuery(nil, &max, nil, &maxInclusive).SetField(field)
|
||||
q := NewNumericRangeInclusiveQuery(nil, &max, nil, &maxInclusive)
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 20:
|
||||
yyDollar = yyS[yypt-4 : yypt+1]
|
||||
//line query_string.y:203
|
||||
//line query_string.y:215
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
minInclusive := false
|
||||
phrase := yyDollar[4].s
|
||||
|
||||
logDebugGrammar("FIELD - GREATER THAN DATE %s", phrase)
|
||||
q := NewDateRangeInclusiveQuery(&phrase, nil, &minInclusive, nil).SetField(field)
|
||||
minTime, err := QueryTimeFromString(phrase)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid time: %v", err))
|
||||
}
|
||||
q := NewDateRangeInclusiveQuery(minTime, time.Time{}, &minInclusive, nil)
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 21:
|
||||
yyDollar = yyS[yypt-5 : yypt+1]
|
||||
//line query_string.y:213
|
||||
//line query_string.y:230
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
minInclusive := true
|
||||
phrase := yyDollar[5].s
|
||||
|
||||
logDebugGrammar("FIELD - GREATER THAN OR EQUAL DATE %s", phrase)
|
||||
q := NewDateRangeInclusiveQuery(&phrase, nil, &minInclusive, nil).SetField(field)
|
||||
minTime, err := QueryTimeFromString(phrase)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid time: %v", err))
|
||||
}
|
||||
q := NewDateRangeInclusiveQuery(minTime, time.Time{}, &minInclusive, nil)
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 22:
|
||||
yyDollar = yyS[yypt-4 : yypt+1]
|
||||
//line query_string.y:223
|
||||
//line query_string.y:245
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
maxInclusive := false
|
||||
phrase := yyDollar[4].s
|
||||
|
||||
logDebugGrammar("FIELD - LESS THAN DATE %s", phrase)
|
||||
q := NewDateRangeInclusiveQuery(nil, &phrase, nil, &maxInclusive).SetField(field)
|
||||
maxTime, err := QueryTimeFromString(phrase)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid time: %v", err))
|
||||
}
|
||||
q := NewDateRangeInclusiveQuery(time.Time{}, maxTime, nil, &maxInclusive)
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 23:
|
||||
yyDollar = yyS[yypt-5 : yypt+1]
|
||||
//line query_string.y:233
|
||||
//line query_string.y:260
|
||||
{
|
||||
field := yyDollar[1].s
|
||||
maxInclusive := true
|
||||
phrase := yyDollar[5].s
|
||||
|
||||
logDebugGrammar("FIELD - LESS THAN OR EQUAL DATE %s", phrase)
|
||||
q := NewDateRangeInclusiveQuery(nil, &phrase, nil, &maxInclusive).SetField(field)
|
||||
maxTime, err := QueryTimeFromString(phrase)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid time: %v", err))
|
||||
}
|
||||
q := NewDateRangeInclusiveQuery(time.Time{}, maxTime, nil, &maxInclusive)
|
||||
q.SetField(field)
|
||||
yyVAL.q = q
|
||||
}
|
||||
case 24:
|
||||
yyDollar = yyS[yypt-0 : yypt+1]
|
||||
//line query_string.y:244
|
||||
//line query_string.y:276
|
||||
{
|
||||
yyVAL.f = 1.0
|
||||
yyVAL.pf = nil
|
||||
}
|
||||
case 25:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
//line query_string.y:248
|
||||
//line query_string.y:280
|
||||
{
|
||||
yyVAL.pf = nil
|
||||
boost, err := strconv.ParseFloat(yyDollar[1].s, 64)
|
||||
if err != nil {
|
||||
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid boost value: %v", err))
|
||||
} else {
|
||||
yyVAL.pf = &boost
|
||||
}
|
||||
yyVAL.f = boost
|
||||
logDebugGrammar("BOOST %f", boost)
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
package query
|
||||
|
||||
import (
|
||||
"bufio"
|
|
@ -15,7 +15,7 @@
|
|||
// using -i.tmp works on both, at the expense of having to remove
|
||||
// the unsightly .tmp files
|
||||
|
||||
package bleve
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -55,7 +55,7 @@ const (
|
|||
type lexerWrapper struct {
|
||||
lex yyLexer
|
||||
errs []string
|
||||
query *booleanQuery
|
||||
query *BooleanQuery
|
||||
}
|
||||
|
||||
func newLexerWrapper(lex yyLexer) *lexerWrapper {
|
|
@ -7,27 +7,33 @@
|
|||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
package query
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
)
|
||||
|
||||
func TestQuerySyntaxParserValid(t *testing.T) {
|
||||
fivePointOh := 5.0
|
||||
theTruth := true
|
||||
theFalsehood := false
|
||||
theDate := "2006-01-02T15:04:05Z07:00"
|
||||
theDate, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tests := []struct {
|
||||
input string
|
||||
result Query
|
||||
mapping *IndexMapping
|
||||
mapping mapping.IndexMapping
|
||||
}{
|
||||
{
|
||||
input: "test",
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
|
@ -37,7 +43,7 @@ func TestQuerySyntaxParserValid(t *testing.T) {
|
|||
},
|
||||
{
|
||||
input: `"test phrase 1"`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
|
@ -47,157 +53,225 @@ func TestQuerySyntaxParserValid(t *testing.T) {
|
|||
},
|
||||
{
|
||||
input: "field:test",
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewMatchQuery("test").SetField("field"),
|
||||
func() Query {
|
||||
q := NewMatchQuery("test")
|
||||
q.SetField("field")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
// - is allowed inside a term, just not the start
|
||||
{
|
||||
input: "field:t-est",
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewMatchQuery("t-est").SetField("field"),
|
||||
func() Query {
|
||||
q := NewMatchQuery("t-est")
|
||||
q.SetField("field")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
// + is allowed inside a term, just not the start
|
||||
{
|
||||
input: "field:t+est",
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewMatchQuery("t+est").SetField("field"),
|
||||
func() Query {
|
||||
q := NewMatchQuery("t+est")
|
||||
q.SetField("field")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
// > is allowed inside a term, just not the start
|
||||
{
|
||||
input: "field:t>est",
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewMatchQuery("t>est").SetField("field"),
|
||||
func() Query {
|
||||
q := NewMatchQuery("t>est")
|
||||
q.SetField("field")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
// < is allowed inside a term, just not the start
|
||||
{
|
||||
input: "field:t<est",
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewMatchQuery("t<est").SetField("field"),
|
||||
func() Query {
|
||||
q := NewMatchQuery("t<est")
|
||||
q.SetField("field")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
// = is allowed inside a term, just not the start
|
||||
{
|
||||
input: "field:t=est",
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewMatchQuery("t=est").SetField("field"),
|
||||
func() Query {
|
||||
q := NewMatchQuery("t=est")
|
||||
q.SetField("field")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: "+field1:test1",
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
[]Query{
|
||||
NewMatchQuery("test1").SetField("field1"),
|
||||
func() Query {
|
||||
q := NewMatchQuery("test1")
|
||||
q.SetField("field1")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil,
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: "-field2:test2",
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
nil,
|
||||
[]Query{
|
||||
NewMatchQuery("test2").SetField("field2"),
|
||||
func() Query {
|
||||
q := NewMatchQuery("test2")
|
||||
q.SetField("field2")
|
||||
return q
|
||||
}(),
|
||||
}),
|
||||
},
|
||||
{
|
||||
input: `field3:"test phrase 2"`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewMatchPhraseQuery("test phrase 2").SetField("field3"),
|
||||
func() Query {
|
||||
q := NewMatchPhraseQuery("test phrase 2")
|
||||
q.SetField("field3")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: `+field4:"test phrase 1"`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
[]Query{
|
||||
NewMatchPhraseQuery("test phrase 1").SetField("field4"),
|
||||
func() Query {
|
||||
q := NewMatchPhraseQuery("test phrase 1")
|
||||
q.SetField("field4")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil,
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: `-field5:"test phrase 2"`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
nil,
|
||||
[]Query{
|
||||
NewMatchPhraseQuery("test phrase 2").SetField("field5"),
|
||||
func() Query {
|
||||
q := NewMatchPhraseQuery("test phrase 2")
|
||||
q.SetField("field5")
|
||||
return q
|
||||
}(),
|
||||
}),
|
||||
},
|
||||
{
|
||||
input: `+field6:test3 -field7:test4 field8:test5`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
[]Query{
|
||||
NewMatchQuery("test3").SetField("field6"),
|
||||
func() Query {
|
||||
q := NewMatchQuery("test3")
|
||||
q.SetField("field6")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
[]Query{
|
||||
NewMatchQuery("test5").SetField("field8"),
|
||||
func() Query {
|
||||
q := NewMatchQuery("test5")
|
||||
q.SetField("field8")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
[]Query{
|
||||
NewMatchQuery("test4").SetField("field7"),
|
||||
func() Query {
|
||||
q := NewMatchQuery("test4")
|
||||
q.SetField("field7")
|
||||
return q
|
||||
}(),
|
||||
}),
|
||||
},
|
||||
{
|
||||
input: "test^3",
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewMatchQuery("test").SetBoost(3.0),
|
||||
func() Query {
|
||||
q := NewMatchQuery("test")
|
||||
q.SetBoost(3.0)
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: "test^3 other^6",
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewMatchQuery("test").SetBoost(3.0),
|
||||
NewMatchQuery("other").SetBoost(6.0),
|
||||
func() Query {
|
||||
q := NewMatchQuery("test")
|
||||
q.SetBoost(3.0)
|
||||
return q
|
||||
}(),
|
||||
func() Query {
|
||||
q := NewMatchQuery("other")
|
||||
q.SetBoost(6.0)
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: "33",
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
|
@ -207,17 +281,21 @@ func TestQuerySyntaxParserValid(t *testing.T) {
|
|||
},
|
||||
{
|
||||
input: "field:33",
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewMatchQuery("33").SetField("field"),
|
||||
func() Query {
|
||||
q := NewMatchQuery("33")
|
||||
q.SetField("field")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: "cat-dog",
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
|
@ -227,148 +305,206 @@ func TestQuerySyntaxParserValid(t *testing.T) {
|
|||
},
|
||||
{
|
||||
input: "watex~",
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewMatchQuery("watex").SetFuzziness(1),
|
||||
func() Query {
|
||||
q := NewMatchQuery("watex")
|
||||
q.SetFuzziness(1)
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: "watex~2",
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewMatchQuery("watex").SetFuzziness(2),
|
||||
func() Query {
|
||||
q := NewMatchQuery("watex")
|
||||
q.SetFuzziness(2)
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: "watex~ 2",
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewMatchQuery("watex").SetFuzziness(1),
|
||||
func() Query {
|
||||
q := NewMatchQuery("watex")
|
||||
q.SetFuzziness(1)
|
||||
return q
|
||||
}(),
|
||||
NewMatchQuery("2"),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: "field:watex~",
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewMatchQuery("watex").SetFuzziness(1).SetField("field"),
|
||||
func() Query {
|
||||
q := NewMatchQuery("watex")
|
||||
q.SetFuzziness(1)
|
||||
q.SetField("field")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: "field:watex~2",
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewMatchQuery("watex").SetFuzziness(2).SetField("field"),
|
||||
func() Query {
|
||||
q := NewMatchQuery("watex")
|
||||
q.SetFuzziness(2)
|
||||
q.SetField("field")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: `field:555c3bb06f7a127cda000005`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewMatchQuery("555c3bb06f7a127cda000005").SetField("field"),
|
||||
func() Query {
|
||||
q := NewMatchQuery("555c3bb06f7a127cda000005")
|
||||
q.SetField("field")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: `field:>5`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewNumericRangeInclusiveQuery(&fivePointOh, nil, &theFalsehood, nil).SetField("field"),
|
||||
func() Query {
|
||||
q := NewNumericRangeInclusiveQuery(&fivePointOh, nil, &theFalsehood, nil)
|
||||
q.SetField("field")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: `field:>=5`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewNumericRangeInclusiveQuery(&fivePointOh, nil, &theTruth, nil).SetField("field"),
|
||||
func() Query {
|
||||
q := NewNumericRangeInclusiveQuery(&fivePointOh, nil, &theTruth, nil)
|
||||
q.SetField("field")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: `field:<5`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewNumericRangeInclusiveQuery(nil, &fivePointOh, nil, &theFalsehood).SetField("field"),
|
||||
func() Query {
|
||||
q := NewNumericRangeInclusiveQuery(nil, &fivePointOh, nil, &theFalsehood)
|
||||
q.SetField("field")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: `field:<=5`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewNumericRangeInclusiveQuery(nil, &fivePointOh, nil, &theTruth).SetField("field"),
|
||||
func() Query {
|
||||
q := NewNumericRangeInclusiveQuery(nil, &fivePointOh, nil, &theTruth)
|
||||
q.SetField("field")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: `field:>"2006-01-02T15:04:05Z07:00"`,
|
||||
mapping: NewIndexMapping(),
|
||||
input: `field:>"2006-01-02T15:04:05Z"`,
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewDateRangeInclusiveQuery(&theDate, nil, &theFalsehood, nil).SetField("field"),
|
||||
func() Query {
|
||||
q := NewDateRangeInclusiveQuery(theDate, time.Time{}, &theFalsehood, nil)
|
||||
q.SetField("field")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: `field:>="2006-01-02T15:04:05Z07:00"`,
|
||||
mapping: NewIndexMapping(),
|
||||
input: `field:>="2006-01-02T15:04:05Z"`,
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewDateRangeInclusiveQuery(&theDate, nil, &theTruth, nil).SetField("field"),
|
||||
func() Query {
|
||||
q := NewDateRangeInclusiveQuery(theDate, time.Time{}, &theTruth, nil)
|
||||
q.SetField("field")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: `field:<"2006-01-02T15:04:05Z07:00"`,
|
||||
mapping: NewIndexMapping(),
|
||||
input: `field:<"2006-01-02T15:04:05Z"`,
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewDateRangeInclusiveQuery(nil, &theDate, nil, &theFalsehood).SetField("field"),
|
||||
func() Query {
|
||||
q := NewDateRangeInclusiveQuery(time.Time{}, theDate, nil, &theFalsehood)
|
||||
q.SetField("field")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: `field:<="2006-01-02T15:04:05Z07:00"`,
|
||||
mapping: NewIndexMapping(),
|
||||
input: `field:<="2006-01-02T15:04:05Z"`,
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewDateRangeInclusiveQuery(nil, &theDate, nil, &theTruth).SetField("field"),
|
||||
func() Query {
|
||||
q := NewDateRangeInclusiveQuery(time.Time{}, theDate, nil, &theTruth)
|
||||
q.SetField("field")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: `/mar.*ty/`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
|
@ -378,17 +514,21 @@ func TestQuerySyntaxParserValid(t *testing.T) {
|
|||
},
|
||||
{
|
||||
input: `name:/mar.*ty/`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewRegexpQuery("mar.*ty").SetField("name"),
|
||||
func() Query {
|
||||
q := NewRegexpQuery("mar.*ty")
|
||||
q.SetField("name")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
{
|
||||
input: `mart*`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
|
@ -398,11 +538,15 @@ func TestQuerySyntaxParserValid(t *testing.T) {
|
|||
},
|
||||
{
|
||||
input: `name:mart*`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewWildcardQuery("mart*").SetField("name"),
|
||||
func() Query {
|
||||
q := NewWildcardQuery("mart*")
|
||||
q.SetField("name")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
|
@ -412,7 +556,7 @@ func TestQuerySyntaxParserValid(t *testing.T) {
|
|||
// escape : as field delimeter
|
||||
{
|
||||
input: `name\:marty`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
|
@ -423,18 +567,22 @@ func TestQuerySyntaxParserValid(t *testing.T) {
|
|||
// first colon delimiter, second escaped
|
||||
{
|
||||
input: `name:marty\:couchbase`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewMatchQuery("marty:couchbase").SetField("name"),
|
||||
func() Query {
|
||||
q := NewMatchQuery("marty:couchbase")
|
||||
q.SetField("name")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
// escape space, single arguemnt to match query
|
||||
{
|
||||
input: `marty\ couchbase`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
|
@ -445,7 +593,7 @@ func TestQuerySyntaxParserValid(t *testing.T) {
|
|||
// escape leading plus, not a must clause
|
||||
{
|
||||
input: `\+marty`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
|
@ -456,7 +604,7 @@ func TestQuerySyntaxParserValid(t *testing.T) {
|
|||
// escape leading minus, not a must not clause
|
||||
{
|
||||
input: `\-marty`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
|
@ -467,7 +615,7 @@ func TestQuerySyntaxParserValid(t *testing.T) {
|
|||
// escape quote inside of phrase
|
||||
{
|
||||
input: `"what does \"quote\" mean"`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
|
@ -478,7 +626,7 @@ func TestQuerySyntaxParserValid(t *testing.T) {
|
|||
// escaping an unsupported character retains backslash
|
||||
{
|
||||
input: `can\ i\ escap\e`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
|
@ -489,7 +637,7 @@ func TestQuerySyntaxParserValid(t *testing.T) {
|
|||
// leading spaces
|
||||
{
|
||||
input: ` what`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
|
@ -500,11 +648,15 @@ func TestQuerySyntaxParserValid(t *testing.T) {
|
|||
// no boost value defaults to 1
|
||||
{
|
||||
input: `term^`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
NewMatchQuery(`term`),
|
||||
func() Query {
|
||||
q := NewMatchQuery(`term`)
|
||||
q.SetBoost(1.0)
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
nil),
|
||||
},
|
||||
|
@ -512,7 +664,7 @@ func TestQuerySyntaxParserValid(t *testing.T) {
|
|||
// but contains escape and ends up as string
|
||||
{
|
||||
input: `3.0\:`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
|
@ -522,7 +674,7 @@ func TestQuerySyntaxParserValid(t *testing.T) {
|
|||
},
|
||||
{
|
||||
input: `3.0\a`,
|
||||
mapping: NewIndexMapping(),
|
||||
mapping: mapping.NewIndexMapping(),
|
||||
result: NewBooleanQuery(
|
||||
nil,
|
||||
[]Query{
|
||||
|
@ -545,7 +697,7 @@ func TestQuerySyntaxParserValid(t *testing.T) {
|
|||
}
|
||||
if !reflect.DeepEqual(q, test.result) {
|
||||
t.Errorf("Expected %#v, got %#v: for %s", test.result, q, test.input)
|
||||
t.Errorf("Expected %#v, got %#v: for %s", test.result.(*booleanQuery).Should.(*disjunctionQuery).Disjuncts[0], q.(*booleanQuery).Should.(*disjunctionQuery).Disjuncts[0], test.input)
|
||||
t.Errorf("Expected %#v, got %#v: for %s", test.result.(*BooleanQuery).Should.(*DisjunctionQuery).Disjuncts[0], q.(*BooleanQuery).Should.(*DisjunctionQuery).Disjuncts[0], test.input)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,404 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
// except in compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
)
|
||||
|
||||
var minNum = 5.1
|
||||
var maxNum = 7.1
|
||||
var startDateStr = "2011-01-01T00:00:00Z"
|
||||
var endDateStr = "2012-01-01T00:00:00Z"
|
||||
var startDate time.Time
|
||||
var endDate time.Time
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
startDate, err = time.Parse(time.RFC3339, startDateStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
endDate, err = time.Parse(time.RFC3339, endDateStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseQuery(t *testing.T) {
|
||||
tests := []struct {
|
||||
input []byte
|
||||
output Query
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
input: []byte(`{"term":"water","field":"desc"}`),
|
||||
output: func() Query {
|
||||
q := NewTermQuery("water")
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"match":"beer","field":"desc"}`),
|
||||
output: func() Query {
|
||||
q := NewMatchQuery("beer")
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"match":"beer","field":"desc","operator":"or"}`),
|
||||
output: func() Query {
|
||||
q := NewMatchQuery("beer")
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"match":"beer","field":"desc","operator":"and"}`),
|
||||
output: func() Query {
|
||||
q := NewMatchQuery("beer")
|
||||
q.SetOperator(MatchQueryOperatorAnd)
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"match":"beer","field":"desc","operator":"or"}`),
|
||||
output: func() Query {
|
||||
q := NewMatchQuery("beer")
|
||||
q.SetOperator(MatchQueryOperatorOr)
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"match":"beer","field":"desc","operator":"does not exist"}`),
|
||||
output: nil,
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
input: []byte(`{"match_phrase":"light beer","field":"desc"}`),
|
||||
output: func() Query {
|
||||
q := NewMatchPhraseQuery("light beer")
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"must":{"conjuncts": [{"match":"beer","field":"desc"}]},"should":{"disjuncts": [{"match":"water","field":"desc"}],"min":1.0},"must_not":{"disjuncts": [{"match":"devon","field":"desc"}]}}`),
|
||||
output: func() Query {
|
||||
q := NewBooleanQuery(
|
||||
[]Query{func() Query {
|
||||
q := NewMatchQuery("beer")
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}()},
|
||||
[]Query{func() Query {
|
||||
q := NewMatchQuery("water")
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}()},
|
||||
[]Query{func() Query {
|
||||
q := NewMatchQuery("devon")
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}()})
|
||||
q.SetMinShould(1)
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"terms":["watered","down"],"field":"desc"}`),
|
||||
output: NewPhraseQuery([]string{"watered", "down"}, "desc"),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"query":"+beer \"light beer\" -devon"}`),
|
||||
output: NewQueryStringQuery(`+beer "light beer" -devon`),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"min":5.1,"max":7.1,"field":"desc"}`),
|
||||
output: func() Query {
|
||||
q := NewNumericRangeQuery(&minNum, &maxNum)
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"start":"` + startDateStr + `","end":"` + endDateStr + `","field":"desc"}`),
|
||||
output: func() Query {
|
||||
q := NewDateRangeQuery(startDate, endDate)
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"prefix":"budwei","field":"desc"}`),
|
||||
output: func() Query {
|
||||
q := NewPrefixQuery("budwei")
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"match_all":{}}`),
|
||||
output: NewMatchAllQuery(),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"match_none":{}}`),
|
||||
output: NewMatchNoneQuery(),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"ids":["a","b","c"]}`),
|
||||
output: NewDocIDQuery([]string{"a", "b", "c"}),
|
||||
},
|
||||
{
|
||||
input: []byte(`{"madeitup":"queryhere"}`),
|
||||
output: nil,
|
||||
err: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
actual, err := ParseQuery(test.input)
|
||||
if err != nil && test.err == false {
|
||||
t.Errorf("error %v for %d", err, i)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(test.output, actual) {
|
||||
t.Errorf("expected: %#v, got: %#v for %s", test.output, actual, string(test.input))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryValidate(t *testing.T) {
|
||||
tests := []struct {
|
||||
query Query
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
query: func() Query {
|
||||
q := NewTermQuery("water")
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
{
|
||||
query: func() Query {
|
||||
q := NewMatchQuery("beer")
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
{
|
||||
query: func() Query {
|
||||
q := NewMatchPhraseQuery("light beer")
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
{
|
||||
query: func() Query {
|
||||
q := NewNumericRangeQuery(&minNum, &maxNum)
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
{
|
||||
query: func() Query {
|
||||
q := NewNumericRangeQuery(nil, nil)
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}(),
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
query: func() Query {
|
||||
q := NewDateRangeQuery(startDate, endDate)
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
{
|
||||
query: func() Query {
|
||||
q := NewPrefixQuery("budwei")
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
{
|
||||
query: NewQueryStringQuery(`+beer "light beer" -devon`),
|
||||
},
|
||||
{
|
||||
query: NewPhraseQuery([]string{"watered", "down"}, "desc"),
|
||||
},
|
||||
{
|
||||
query: NewPhraseQuery([]string{}, "field"),
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
query: func() Query {
|
||||
q := NewMatchNoneQuery()
|
||||
q.SetBoost(25)
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
{
|
||||
query: func() Query {
|
||||
q := NewMatchAllQuery()
|
||||
q.SetBoost(25)
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
{
|
||||
query: NewBooleanQuery(
|
||||
[]Query{func() Query {
|
||||
q := NewMatchQuery("beer")
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}()},
|
||||
[]Query{func() Query {
|
||||
q := NewMatchQuery("water")
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}()},
|
||||
[]Query{func() Query {
|
||||
q := NewMatchQuery("devon")
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}()}),
|
||||
},
|
||||
{
|
||||
query: NewBooleanQuery(
|
||||
nil,
|
||||
nil,
|
||||
[]Query{func() Query {
|
||||
q := NewMatchQuery("devon")
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}()}),
|
||||
},
|
||||
{
|
||||
query: NewBooleanQuery(
|
||||
[]Query{},
|
||||
[]Query{},
|
||||
[]Query{func() Query {
|
||||
q := NewMatchQuery("devon")
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}()}),
|
||||
},
|
||||
{
|
||||
query: NewBooleanQuery(
|
||||
nil,
|
||||
nil,
|
||||
nil),
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
query: NewBooleanQuery(
|
||||
[]Query{},
|
||||
[]Query{},
|
||||
[]Query{}),
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
query: func() Query {
|
||||
q := NewBooleanQuery(
|
||||
[]Query{func() Query {
|
||||
q := NewMatchQuery("beer")
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}()},
|
||||
[]Query{func() Query {
|
||||
q := NewMatchQuery("water")
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}()},
|
||||
[]Query{func() Query {
|
||||
q := NewMatchQuery("devon")
|
||||
q.SetField("desc")
|
||||
return q
|
||||
}()})
|
||||
q.SetMinShould(2)
|
||||
return q
|
||||
}(),
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
query: func() Query {
|
||||
q := NewDocIDQuery(nil)
|
||||
q.SetBoost(25)
|
||||
return q
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
if vq, ok := test.query.(ValidatableQuery); ok {
|
||||
actual := vq.Validate()
|
||||
if actual != nil && !test.err {
|
||||
t.Errorf("expected no error: %#v got %#v", test.err, actual)
|
||||
} else if actual == nil && test.err {
|
||||
t.Errorf("expected error: %#v got %#v", test.err, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDumpQuery(t *testing.T) {
|
||||
mapping := mapping.NewIndexMapping()
|
||||
q := NewQueryStringQuery("+water -light beer")
|
||||
s, err := DumpQuery(mapping, q)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s = strings.TrimSpace(s)
|
||||
wanted := strings.TrimSpace(`{
|
||||
"must": {
|
||||
"conjuncts": [
|
||||
{
|
||||
"match": "water",
|
||||
"prefix_length": 0,
|
||||
"fuzziness": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"should": {
|
||||
"disjuncts": [
|
||||
{
|
||||
"match": "beer",
|
||||
"prefix_length": 0,
|
||||
"fuzziness": 0
|
||||
}
|
||||
],
|
||||
"min": 0
|
||||
},
|
||||
"must_not": {
|
||||
"disjuncts": [
|
||||
{
|
||||
"match": "light",
|
||||
"prefix_length": 0,
|
||||
"fuzziness": 0
|
||||
}
|
||||
],
|
||||
"min": 0
|
||||
}
|
||||
}`)
|
||||
if wanted != s {
|
||||
t.Fatalf("query:\n%s\ndiffers from expected:\n%s", s, wanted)
|
||||
}
|
||||
}
|
|
@ -7,70 +7,61 @@
|
|||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
package query
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searchers"
|
||||
)
|
||||
|
||||
type regexpQuery struct {
|
||||
Regexp string `json:"regexp"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
type RegexpQuery struct {
|
||||
Regexp string `json:"regexp"`
|
||||
Field string `json:"field,omitempty"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
compiled *regexp.Regexp
|
||||
}
|
||||
|
||||
// NewRegexpQuery creates a new Query which finds
|
||||
// documents containing terms that match the
|
||||
// specified regular expression.
|
||||
func NewRegexpQuery(regexp string) *regexpQuery {
|
||||
return ®expQuery{
|
||||
Regexp: regexp,
|
||||
BoostVal: 1.0,
|
||||
func NewRegexpQuery(regexp string) *RegexpQuery {
|
||||
return &RegexpQuery{
|
||||
Regexp: regexp,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *regexpQuery) Boost() float64 {
|
||||
return q.BoostVal
|
||||
func (q *RegexpQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.Boost = &boost
|
||||
}
|
||||
|
||||
func (q *regexpQuery) SetBoost(b float64) Query {
|
||||
q.BoostVal = b
|
||||
return q
|
||||
func (q *RegexpQuery) SetField(f string) {
|
||||
q.Field = f
|
||||
}
|
||||
|
||||
func (q *regexpQuery) Field() string {
|
||||
return q.FieldVal
|
||||
}
|
||||
|
||||
func (q *regexpQuery) SetField(f string) Query {
|
||||
q.FieldVal = f
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *regexpQuery) Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.FieldVal
|
||||
if q.FieldVal == "" {
|
||||
field = m.DefaultField
|
||||
func (q *RegexpQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.Field
|
||||
if q.Field == "" {
|
||||
field = m.DefaultSearchField()
|
||||
}
|
||||
err := q.compile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return searchers.NewRegexpSearcher(i, q.compiled, field, q.BoostVal, explain)
|
||||
return searchers.NewRegexpSearcher(i, q.compiled, field, q.Boost.Value(), explain)
|
||||
}
|
||||
|
||||
func (q *regexpQuery) Validate() error {
|
||||
func (q *RegexpQuery) Validate() error {
|
||||
return q.compile()
|
||||
}
|
||||
|
||||
func (q *regexpQuery) compile() error {
|
||||
func (q *RegexpQuery) compile() error {
|
||||
if q.compiled == nil {
|
||||
// require that pattern be anchored to start and end of term
|
||||
actualRegexp := q.Regexp
|
|
@ -7,55 +7,42 @@
|
|||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
package query
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searchers"
|
||||
)
|
||||
|
||||
type termQuery struct {
|
||||
Term string `json:"term"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
type TermQuery struct {
|
||||
Term string `json:"term"`
|
||||
Field string `json:"field,omitempty"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
// NewTermQuery creates a new Query for finding an
|
||||
// exact term match in the index.
|
||||
func NewTermQuery(term string) *termQuery {
|
||||
return &termQuery{
|
||||
Term: term,
|
||||
BoostVal: 1.0,
|
||||
func NewTermQuery(term string) *TermQuery {
|
||||
return &TermQuery{
|
||||
Term: term,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *termQuery) Boost() float64 {
|
||||
return q.BoostVal
|
||||
func (q *TermQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.Boost = &boost
|
||||
}
|
||||
|
||||
func (q *termQuery) SetBoost(b float64) Query {
|
||||
q.BoostVal = b
|
||||
return q
|
||||
func (q *TermQuery) SetField(f string) {
|
||||
q.Field = f
|
||||
}
|
||||
|
||||
func (q *termQuery) Field() string {
|
||||
return q.FieldVal
|
||||
}
|
||||
|
||||
func (q *termQuery) SetField(f string) Query {
|
||||
q.FieldVal = f
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *termQuery) Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.FieldVal
|
||||
if q.FieldVal == "" {
|
||||
field = m.DefaultField
|
||||
func (q *TermQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.Field
|
||||
if q.Field == "" {
|
||||
field = m.DefaultSearchField()
|
||||
}
|
||||
return searchers.NewTermSearcher(i, q.Term, field, q.BoostVal, explain)
|
||||
}
|
||||
|
||||
func (q *termQuery) Validate() error {
|
||||
return nil
|
||||
return searchers.NewTermSearcher(i, q.Term, field, q.Boost.Value(), explain)
|
||||
}
|
|
@ -7,13 +7,14 @@
|
|||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package bleve
|
||||
package query
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searchers"
|
||||
)
|
||||
|
@ -37,10 +38,10 @@ var wildcardRegexpReplacer = strings.NewReplacer(
|
|||
"*", ".*",
|
||||
"?", ".")
|
||||
|
||||
type wildcardQuery struct {
|
||||
Wildcard string `json:"wildcard"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal float64 `json:"boost,omitempty"`
|
||||
type WildcardQuery struct {
|
||||
Wildcard string `json:"wildcard"`
|
||||
Field string `json:"field,omitempty"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
compiled *regexp.Regexp
|
||||
}
|
||||
|
||||
|
@ -49,35 +50,25 @@ type wildcardQuery struct {
|
|||
// specified wildcard. In the wildcard pattern '*'
|
||||
// will match any sequence of 0 or more characters,
|
||||
// and '?' will match any single character.
|
||||
func NewWildcardQuery(wildcard string) *wildcardQuery {
|
||||
return &wildcardQuery{
|
||||
func NewWildcardQuery(wildcard string) *WildcardQuery {
|
||||
return &WildcardQuery{
|
||||
Wildcard: wildcard,
|
||||
BoostVal: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *wildcardQuery) Boost() float64 {
|
||||
return q.BoostVal
|
||||
func (q *WildcardQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.Boost = &boost
|
||||
}
|
||||
|
||||
func (q *wildcardQuery) SetBoost(b float64) Query {
|
||||
q.BoostVal = b
|
||||
return q
|
||||
func (q *WildcardQuery) SetField(f string) {
|
||||
q.Field = f
|
||||
}
|
||||
|
||||
func (q *wildcardQuery) Field() string {
|
||||
return q.FieldVal
|
||||
}
|
||||
|
||||
func (q *wildcardQuery) SetField(f string) Query {
|
||||
q.FieldVal = f
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *wildcardQuery) Searcher(i index.IndexReader, m *IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.FieldVal
|
||||
if q.FieldVal == "" {
|
||||
field = m.DefaultField
|
||||
func (q *WildcardQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {
|
||||
field := q.Field
|
||||
if q.Field == "" {
|
||||
field = m.DefaultSearchField()
|
||||
}
|
||||
if q.compiled == nil {
|
||||
var err error
|
||||
|
@ -87,16 +78,16 @@ func (q *wildcardQuery) Searcher(i index.IndexReader, m *IndexMapping, explain b
|
|||
}
|
||||
}
|
||||
|
||||
return searchers.NewRegexpSearcher(i, q.compiled, field, q.BoostVal, explain)
|
||||
return searchers.NewRegexpSearcher(i, q.compiled, field, q.Boost.Value(), explain)
|
||||
}
|
||||
|
||||
func (q *wildcardQuery) Validate() error {
|
||||
func (q *WildcardQuery) Validate() error {
|
||||
var err error
|
||||
q.compiled, err = q.convertToRegexp()
|
||||
return err
|
||||
}
|
||||
|
||||
func (q *wildcardQuery) convertToRegexp() (*regexp.Regexp, error) {
|
||||
func (q *WildcardQuery) convertToRegexp() (*regexp.Regexp, error) {
|
||||
regexpString := "^" + wildcardRegexpReplacer.Replace(q.Wildcard) + "$"
|
||||
return regexp.Compile(regexpString)
|
||||
}
|
|
@ -24,6 +24,7 @@ import (
|
|||
|
||||
// we must explicitly include any functionality we plan on testing
|
||||
_ "github.com/blevesearch/bleve/analysis/analyzers/keyword_analyzer"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
|
||||
// allow choosing alternate kvstores
|
||||
_ "github.com/blevesearch/bleve/config"
|
||||
|
@ -77,7 +78,7 @@ func runTestDir(t *testing.T, dir, datasetName string) {
|
|||
t.Errorf("error reading mapping: %v", err)
|
||||
return
|
||||
}
|
||||
var mapping bleve.IndexMapping
|
||||
var mapping mapping.IndexMappingImpl
|
||||
err = json.Unmarshal(mappingBytes, &mapping)
|
||||
if err != nil {
|
||||
t.Errorf("error unmarshalling mapping: %v", err)
|
||||
|
|
Loading…
Reference in New Issue