Merge branch 'slavikm-master'
This commit is contained in:
commit
b286466787
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -16,3 +16,4 @@
|
||||||
/utils/bleve_registry/bleve_registry
|
/utils/bleve_registry/bleve_registry
|
||||||
/y.output
|
/y.output
|
||||||
*.test
|
*.test
|
||||||
|
tags
|
||||||
|
|
|
@ -28,6 +28,7 @@ const (
|
||||||
Shingle
|
Shingle
|
||||||
Single
|
Single
|
||||||
Double
|
Double
|
||||||
|
Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
// Token represents one occurrence of a term at a particular location in a
|
// Token represents one occurrence of a term at a particular location in a
|
||||||
|
|
93
document/field_boolean.go
Normal file
93
document/field_boolean.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -158,6 +158,8 @@ func encodeFieldType(f document.Field) byte {
|
||||||
fieldType = 'n'
|
fieldType = 'n'
|
||||||
case *document.DateTimeField:
|
case *document.DateTimeField:
|
||||||
fieldType = 'd'
|
fieldType = 'd'
|
||||||
|
case *document.BooleanField:
|
||||||
|
fieldType = 'b'
|
||||||
case *document.CompositeField:
|
case *document.CompositeField:
|
||||||
fieldType = 'c'
|
fieldType = 'c'
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,8 @@ func (r *firestormReader) decodeFieldType(name string, pos []uint64, value []byt
|
||||||
return document.NewNumericFieldFromBytes(name, pos, value[1:])
|
return document.NewNumericFieldFromBytes(name, pos, value[1:])
|
||||||
case 'd':
|
case 'd':
|
||||||
return document.NewDateTimeFieldFromBytes(name, pos, value[1:])
|
return document.NewDateTimeFieldFromBytes(name, pos, value[1:])
|
||||||
|
case 'b':
|
||||||
|
return document.NewBooleanFieldFromBytes(name, pos, value[1:])
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -531,6 +531,8 @@ func encodeFieldType(f document.Field) byte {
|
||||||
fieldType = 'n'
|
fieldType = 'n'
|
||||||
case *document.DateTimeField:
|
case *document.DateTimeField:
|
||||||
fieldType = 'd'
|
fieldType = 'd'
|
||||||
|
case *document.BooleanField:
|
||||||
|
fieldType = 'b'
|
||||||
case *document.CompositeField:
|
case *document.CompositeField:
|
||||||
fieldType = 'c'
|
fieldType = 'c'
|
||||||
}
|
}
|
||||||
|
@ -682,6 +684,8 @@ func decodeFieldType(typ byte, name string, pos []uint64, value []byte) document
|
||||||
return document.NewNumericFieldFromBytes(name, pos, value)
|
return document.NewNumericFieldFromBytes(name, pos, value)
|
||||||
case 'd':
|
case 'd':
|
||||||
return document.NewDateTimeFieldFromBytes(name, pos, value)
|
return document.NewDateTimeFieldFromBytes(name, pos, value)
|
||||||
|
case 'b':
|
||||||
|
return document.NewBooleanFieldFromBytes(name, pos, value)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -486,6 +486,11 @@ func (i *indexImpl) Search(req *SearchRequest) (sr *SearchResult, err error) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
value = datetime.Format(time.RFC3339)
|
value = datetime.Format(time.RFC3339)
|
||||||
}
|
}
|
||||||
|
case *document.BooleanField:
|
||||||
|
boolean, err := docF.Boolean()
|
||||||
|
if err == nil {
|
||||||
|
value = boolean
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if value != nil {
|
if value != nil {
|
||||||
hit.AddFieldValue(docF.Name(), value)
|
hit.AddFieldValue(docF.Name(), value)
|
||||||
|
|
|
@ -421,6 +421,8 @@ func TestStoredFieldPreserved(t *testing.T) {
|
||||||
doca := map[string]interface{}{
|
doca := map[string]interface{}{
|
||||||
"name": "Marty",
|
"name": "Marty",
|
||||||
"desc": "GopherCON India",
|
"desc": "GopherCON India",
|
||||||
|
"bool": true,
|
||||||
|
"num": float64(1),
|
||||||
}
|
}
|
||||||
err = index.Index("a", doca)
|
err = index.Index("a", doca)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -429,7 +431,7 @@ func TestStoredFieldPreserved(t *testing.T) {
|
||||||
|
|
||||||
q := NewTermQuery("marty")
|
q := NewTermQuery("marty")
|
||||||
req := NewSearchRequest(q)
|
req := NewSearchRequest(q)
|
||||||
req.Fields = []string{"name", "desc"}
|
req.Fields = []string{"name", "desc", "bool", "num"}
|
||||||
res, err := index.Search(req)
|
res, err := index.Search(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
|
@ -438,14 +440,18 @@ func TestStoredFieldPreserved(t *testing.T) {
|
||||||
if len(res.Hits) != 1 {
|
if len(res.Hits) != 1 {
|
||||||
t.Fatalf("expected 1 hit, got %d", len(res.Hits))
|
t.Fatalf("expected 1 hit, got %d", len(res.Hits))
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.Hits[0].Fields["name"] != "Marty" {
|
if res.Hits[0].Fields["name"] != "Marty" {
|
||||||
t.Errorf("expected 'Marty' got '%s'", res.Hits[0].Fields["name"])
|
t.Errorf("expected 'Marty' got '%s'", res.Hits[0].Fields["name"])
|
||||||
}
|
}
|
||||||
if res.Hits[0].Fields["desc"] != "GopherCON India" {
|
if res.Hits[0].Fields["desc"] != "GopherCON India" {
|
||||||
t.Errorf("expected 'GopherCON India' got '%s'", res.Hits[0].Fields["desc"])
|
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) {
|
func TestDict(t *testing.T) {
|
||||||
|
@ -1311,3 +1317,65 @@ func TestDocumentFieldArrayPositionsBug295(t *testing.T) {
|
||||||
t.Fatal(err)
|
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
|
||||||
|
}
|
||||||
|
err = index.Index("true", &doc{Bool: true})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = index.Index("false", &doc{Bool: false})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
switch field.Type {
|
||||||
case "text", "datetime", "number":
|
case "text", "datetime", "number", "boolean":
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown field type: '%s'", field.Type)
|
return fmt.Errorf("unknown field type: '%s'", field.Type)
|
||||||
}
|
}
|
||||||
|
@ -352,6 +352,18 @@ func (dm *DocumentMapping) processProperty(property interface{}, path []string,
|
||||||
fieldMapping := newNumericFieldMappingDynamic()
|
fieldMapping := newNumericFieldMappingDynamic()
|
||||||
fieldMapping.processFloat64(propertyValFloat, pathString, path, indexes, context)
|
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 := newBooleanFieldMappingDynamic()
|
||||||
|
fieldMapping.processBoolean(propertyValBool, pathString, path, indexes, context)
|
||||||
|
}
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
switch property := property.(type) {
|
switch property := property.(type) {
|
||||||
case time.Time:
|
case time.Time:
|
||||||
|
|
|
@ -100,6 +100,23 @@ func newDateTimeFieldMappingDynamic() *FieldMapping {
|
||||||
return rv
|
return rv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewBooleanFieldMapping returns a default field mapping for booleans
|
||||||
|
func NewBooleanFieldMapping() *FieldMapping {
|
||||||
|
return &FieldMapping{
|
||||||
|
Type: "boolean",
|
||||||
|
Store: true,
|
||||||
|
Index: true,
|
||||||
|
IncludeInAll: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBooleanFieldMappingDynamic() *FieldMapping {
|
||||||
|
rv := NewBooleanFieldMapping()
|
||||||
|
rv.Store = StoreDynamic
|
||||||
|
rv.Index = IndexDynamic
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
|
||||||
// Options returns the indexing options for this field.
|
// Options returns the indexing options for this field.
|
||||||
func (fm *FieldMapping) Options() document.IndexingOptions {
|
func (fm *FieldMapping) Options() document.IndexingOptions {
|
||||||
var rv document.IndexingOptions
|
var rv document.IndexingOptions
|
||||||
|
@ -171,6 +188,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 {
|
func (fm *FieldMapping) analyzerForField(path []string, context *walkContext) *analysis.Analyzer {
|
||||||
analyzerName := fm.Analyzer
|
analyzerName := fm.Analyzer
|
||||||
if analyzerName == "" {
|
if analyzerName == "" {
|
||||||
|
|
|
@ -357,5 +357,48 @@ func TestEnablingDisablingStoringDynamicFields(t *testing.T) {
|
||||||
t.Errorf("expected field 'name' to be not stored, is")
|
t.Errorf("expected field 'name' to be not stored, is")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
64
query_bool_field.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user