Implemented boolean field support
This commit is contained in:
parent
d533b326f6
commit
680be52f87
|
@ -16,3 +16,4 @@
|
|||
/utils/bleve_registry/bleve_registry
|
||||
/y.output
|
||||
*.test
|
||||
tags
|
||||
|
|
|
@ -28,6 +28,7 @@ const (
|
|||
Shingle
|
||||
Single
|
||||
Double
|
||||
Boolean
|
||||
)
|
||||
|
||||
// Token represents one occurrence of a term at a particular location in a
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
// 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 document
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/analysis"
|
||||
)
|
||||
|
||||
const DefaultBooleanIndexingOptions = StoreField | IndexField
|
||||
|
||||
type BooleanField struct {
|
||||
name string
|
||||
arrayPositions []uint64
|
||||
options IndexingOptions
|
||||
value []byte
|
||||
}
|
||||
|
||||
func (b *BooleanField) Name() string {
|
||||
return b.name
|
||||
}
|
||||
|
||||
func (b *BooleanField) ArrayPositions() []uint64 {
|
||||
return b.arrayPositions
|
||||
}
|
||||
|
||||
func (b *BooleanField) Options() IndexingOptions {
|
||||
return b.options
|
||||
}
|
||||
|
||||
func (b *BooleanField) Analyze() (int, analysis.TokenFrequencies) {
|
||||
tokens := make(analysis.TokenStream, 0)
|
||||
tokens = append(tokens, &analysis.Token{
|
||||
Start: 0,
|
||||
End: len(b.value),
|
||||
Term: b.value,
|
||||
Position: 1,
|
||||
Type: analysis.Boolean,
|
||||
})
|
||||
|
||||
fieldLength := len(tokens)
|
||||
tokenFreqs := analysis.TokenFrequency(tokens, b.arrayPositions, b.options.IncludeTermVectors())
|
||||
return fieldLength, tokenFreqs
|
||||
}
|
||||
|
||||
func (b *BooleanField) Value() []byte {
|
||||
return b.value
|
||||
}
|
||||
|
||||
func (b *BooleanField) Boolean() (bool, error) {
|
||||
if len(b.value) == 1 {
|
||||
return b.value[0] == 'T', nil
|
||||
}
|
||||
return false, fmt.Errorf("boolean field has %d bytes", len(b.value))
|
||||
}
|
||||
|
||||
func (b *BooleanField) GoString() string {
|
||||
return fmt.Sprintf("&document.BooleanField{Name:%s, Options: %s, Value: %s}", b.name, b.options, b.value)
|
||||
}
|
||||
|
||||
func NewBooleanFieldFromBytes(name string, arrayPositions []uint64, value []byte) *BooleanField {
|
||||
return &BooleanField{
|
||||
name: name,
|
||||
arrayPositions: arrayPositions,
|
||||
value: value,
|
||||
options: DefaultNumericIndexingOptions,
|
||||
}
|
||||
}
|
||||
|
||||
func NewBooleanField(name string, arrayPositions []uint64, b bool) *BooleanField {
|
||||
return NewBooleanFieldWithIndexingOptions(name, arrayPositions, b, DefaultNumericIndexingOptions)
|
||||
}
|
||||
|
||||
func NewBooleanFieldWithIndexingOptions(name string, arrayPositions []uint64, b bool, options IndexingOptions) *BooleanField {
|
||||
v := []byte("F")
|
||||
if b {
|
||||
v = []byte("T")
|
||||
}
|
||||
return &BooleanField{
|
||||
name: name,
|
||||
arrayPositions: arrayPositions,
|
||||
value: v,
|
||||
options: options,
|
||||
}
|
||||
}
|
|
@ -158,6 +158,8 @@ func encodeFieldType(f document.Field) byte {
|
|||
fieldType = 'n'
|
||||
case *document.DateTimeField:
|
||||
fieldType = 'd'
|
||||
case *document.BooleanField:
|
||||
fieldType = 'b'
|
||||
case *document.CompositeField:
|
||||
fieldType = 'c'
|
||||
}
|
||||
|
|
|
@ -112,6 +112,8 @@ func (r *firestormReader) decodeFieldType(name string, pos []uint64, value []byt
|
|||
return document.NewNumericFieldFromBytes(name, pos, value[1:])
|
||||
case 'd':
|
||||
return document.NewDateTimeFieldFromBytes(name, pos, value[1:])
|
||||
case 'b':
|
||||
return document.NewBooleanFieldFromBytes(name, pos, value[1:])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -496,6 +496,8 @@ func encodeFieldType(f document.Field) byte {
|
|||
fieldType = 'n'
|
||||
case *document.DateTimeField:
|
||||
fieldType = 'd'
|
||||
case *document.BooleanField:
|
||||
fieldType = 'b'
|
||||
case *document.CompositeField:
|
||||
fieldType = 'c'
|
||||
}
|
||||
|
@ -657,6 +659,8 @@ func decodeFieldType(typ byte, name string, pos []uint64, value []byte) document
|
|||
return document.NewNumericFieldFromBytes(name, pos, value)
|
||||
case 'd':
|
||||
return document.NewDateTimeFieldFromBytes(name, pos, value)
|
||||
case 'b':
|
||||
return document.NewBooleanFieldFromBytes(name, pos, value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -486,6 +486,11 @@ func (i *indexImpl) Search(req *SearchRequest) (sr *SearchResult, err error) {
|
|||
if err == nil {
|
||||
value = datetime.Format(time.RFC3339)
|
||||
}
|
||||
case *document.BooleanField:
|
||||
boolean, err := docF.Boolean()
|
||||
if err == nil {
|
||||
value = boolean
|
||||
}
|
||||
}
|
||||
if value != nil {
|
||||
hit.AddFieldValue(docF.Name(), value)
|
||||
|
|
|
@ -421,6 +421,8 @@ func TestStoredFieldPreserved(t *testing.T) {
|
|||
doca := map[string]interface{}{
|
||||
"name": "Marty",
|
||||
"desc": "GopherCON India",
|
||||
"bool": true,
|
||||
"num": float64(1),
|
||||
}
|
||||
err = index.Index("a", doca)
|
||||
if err != nil {
|
||||
|
@ -429,7 +431,7 @@ func TestStoredFieldPreserved(t *testing.T) {
|
|||
|
||||
q := NewTermQuery("marty")
|
||||
req := NewSearchRequest(q)
|
||||
req.Fields = []string{"name", "desc"}
|
||||
req.Fields = []string{"name", "desc", "bool", "num"}
|
||||
res, err := index.Search(req)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
@ -438,14 +440,18 @@ func TestStoredFieldPreserved(t *testing.T) {
|
|||
if len(res.Hits) != 1 {
|
||||
t.Fatalf("expected 1 hit, got %d", len(res.Hits))
|
||||
}
|
||||
|
||||
if res.Hits[0].Fields["name"] != "Marty" {
|
||||
t.Errorf("expected 'Marty' got '%s'", res.Hits[0].Fields["name"])
|
||||
}
|
||||
if res.Hits[0].Fields["desc"] != "GopherCON India" {
|
||||
t.Errorf("expected 'GopherCON India' got '%s'", res.Hits[0].Fields["desc"])
|
||||
}
|
||||
|
||||
if res.Hits[0].Fields["num"] != float64(1) {
|
||||
t.Errorf("expected '1' got '%v'", res.Hits[0].Fields["num"])
|
||||
}
|
||||
if res.Hits[0].Fields["bool"] != true {
|
||||
t.Errorf("expected 'true' got '%v'", res.Hits[0].Fields["bool"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestDict(t *testing.T) {
|
||||
|
@ -1311,3 +1317,59 @@ func TestDocumentFieldArrayPositionsBug295(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBooleanFieldMappingIssue109(t *testing.T) {
|
||||
defer func() {
|
||||
err := os.RemoveAll("testidx")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
m := NewIndexMapping()
|
||||
m.DefaultMapping = NewDocumentMapping()
|
||||
m.DefaultMapping.AddFieldMappingsAt("Bool", NewBooleanFieldMapping())
|
||||
|
||||
index, err := New("testidx", m)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
type doc struct {
|
||||
Bool bool
|
||||
}
|
||||
index.Index("true", &doc{Bool: true})
|
||||
index.Index("false", &doc{Bool: false})
|
||||
|
||||
sreq := NewSearchRequest(NewBoolFieldQuery(true).SetField("Bool"))
|
||||
sres, err := index.Search(sreq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sres.Total != 1 {
|
||||
t.Errorf("expected 1 results, got %d", sres.Total)
|
||||
}
|
||||
|
||||
sreq = NewSearchRequest(NewBoolFieldQuery(false).SetField("Bool"))
|
||||
sres, err = index.Search(sreq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sres.Total != 1 {
|
||||
t.Errorf("expected 1 results, got %d", sres.Total)
|
||||
}
|
||||
|
||||
sreq = NewSearchRequest(NewBoolFieldQuery(true))
|
||||
sres, err = index.Search(sreq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sres.Total != 1 {
|
||||
t.Errorf("expected 1 results, got %d", sres.Total)
|
||||
}
|
||||
|
||||
err = index.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ func (dm *DocumentMapping) validate(cache *registry.Cache) error {
|
|||
}
|
||||
}
|
||||
switch field.Type {
|
||||
case "text", "datetime", "number":
|
||||
case "text", "datetime", "number", "boolean":
|
||||
default:
|
||||
return fmt.Errorf("unknown field type: '%s'", field.Type)
|
||||
}
|
||||
|
@ -352,6 +352,18 @@ func (dm *DocumentMapping) processProperty(property interface{}, path []string,
|
|||
fieldMapping := NewNumericFieldMapping()
|
||||
fieldMapping.processFloat64(propertyValFloat, pathString, path, indexes, context)
|
||||
}
|
||||
case reflect.Bool:
|
||||
propertyValBool := propertyValue.Bool()
|
||||
if subDocMapping != nil {
|
||||
// index by explicit mapping
|
||||
for _, fieldMapping := range subDocMapping.Fields {
|
||||
fieldMapping.processBoolean(propertyValBool, pathString, path, indexes, context)
|
||||
}
|
||||
} else if dm.Dynamic {
|
||||
// automatic indexing behavior
|
||||
fieldMapping := NewBooleanFieldMapping()
|
||||
fieldMapping.processBoolean(propertyValBool, pathString, path, indexes, context)
|
||||
}
|
||||
case reflect.Struct:
|
||||
switch property := property.(type) {
|
||||
case time.Time:
|
||||
|
|
|
@ -73,6 +73,16 @@ func NewDateTimeFieldMapping() *FieldMapping {
|
|||
}
|
||||
}
|
||||
|
||||
// NewBooleanFieldMapping returns a default field mapping for dates
|
||||
func NewBooleanFieldMapping() *FieldMapping {
|
||||
return &FieldMapping{
|
||||
Type: "boolean",
|
||||
Store: true,
|
||||
Index: true,
|
||||
IncludeInAll: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Options returns the indexing options for this field.
|
||||
func (fm *FieldMapping) Options() document.IndexingOptions {
|
||||
var rv document.IndexingOptions
|
||||
|
@ -144,6 +154,19 @@ func (fm *FieldMapping) processTime(propertyValueTime time.Time, pathString stri
|
|||
}
|
||||
}
|
||||
|
||||
func (fm *FieldMapping) processBoolean(propertyValueBool bool, pathString string, path []string, indexes []uint64, context *walkContext) {
|
||||
fieldName := getFieldName(pathString, path, fm)
|
||||
if fm.Type == "boolean" {
|
||||
options := fm.Options()
|
||||
field := document.NewBooleanFieldWithIndexingOptions(fieldName, indexes, propertyValueBool, options)
|
||||
context.doc.AddField(field)
|
||||
|
||||
if !fm.IncludeInAll {
|
||||
context.excludedFromAll = append(context.excludedFromAll, fieldName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (fm *FieldMapping) analyzerForField(path []string, context *walkContext) *analysis.Analyzer {
|
||||
analyzerName := fm.Analyzer
|
||||
if analyzerName == "" {
|
||||
|
|
|
@ -324,3 +324,47 @@ func TestMappingWithTokenizerDeps(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMappingBool(t *testing.T) {
|
||||
boolMapping := NewBooleanFieldMapping()
|
||||
docMapping := NewDocumentMapping()
|
||||
docMapping.AddFieldMappingsAt("prop", boolMapping)
|
||||
mapping := NewIndexMapping()
|
||||
mapping.AddDocumentMapping("doc", docMapping)
|
||||
|
||||
pprop := false
|
||||
x := struct {
|
||||
Prop bool `json:"prop"`
|
||||
PProp *bool `json:"pprop"`
|
||||
}{
|
||||
Prop: true,
|
||||
PProp: &pprop,
|
||||
}
|
||||
|
||||
doc := document.NewDocument("1")
|
||||
err := mapping.mapDocument(doc, x)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
foundProp := false
|
||||
foundPProp := false
|
||||
count := 0
|
||||
for _, f := range doc.Fields {
|
||||
if f.Name() == "prop" {
|
||||
foundProp = true
|
||||
}
|
||||
if f.Name() == "pprop" {
|
||||
foundPProp = true
|
||||
}
|
||||
count++
|
||||
}
|
||||
if !foundProp {
|
||||
t.Errorf("expected to find bool field named 'prop'")
|
||||
}
|
||||
if !foundPProp {
|
||||
t.Errorf("expected to find pointer to bool field named 'pprop'")
|
||||
}
|
||||
if count != 2 {
|
||||
t.Errorf("expected to find 1 find, found %d", count)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 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
|
||||
}
|
Loading…
Reference in New Issue