0
0
Fork 0

Implemented boolean field support

This commit is contained in:
slavikm 2016-01-11 17:18:03 -08:00
parent d533b326f6
commit 680be52f87
12 changed files with 317 additions and 4 deletions

1
.gitignore vendored
View File

@ -16,3 +16,4 @@
/utils/bleve_registry/bleve_registry
/y.output
*.test
tags

View File

@ -28,6 +28,7 @@ const (
Shingle
Single
Double
Boolean
)
// Token represents one occurrence of a term at a particular location in a

93
document/field_boolean.go Normal file
View File

@ -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,
}
}

View File

@ -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'
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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:

View File

@ -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 == "" {

View File

@ -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)
}
}

64
query_bool_field.go Normal file
View File

@ -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
}