1
0
Fork 0

Adding upstream version 2.5.1.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-19 00:20:02 +02:00
parent c71cb8b61d
commit 982828099e
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
783 changed files with 150650 additions and 0 deletions

View file

@ -0,0 +1,172 @@
// 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 shingle
import (
"container/ring"
"fmt"
"github.com/blevesearch/bleve/v2/analysis"
"github.com/blevesearch/bleve/v2/registry"
)
const Name = "shingle"
type ShingleFilter struct {
min int
max int
outputOriginal bool
tokenSeparator string
fill string
}
func NewShingleFilter(min, max int, outputOriginal bool, sep, fill string) *ShingleFilter {
return &ShingleFilter{
min: min,
max: max,
outputOriginal: outputOriginal,
tokenSeparator: sep,
fill: fill,
}
}
func (s *ShingleFilter) Filter(input analysis.TokenStream) analysis.TokenStream {
rv := make(analysis.TokenStream, 0, len(input))
ring := ring.New(s.max)
itemsInRing := 0
currentPosition := 0
for _, token := range input {
if s.outputOriginal {
rv = append(rv, token)
}
// if there are gaps, insert filler tokens
offset := token.Position - currentPosition
for offset > 1 {
fillerToken := analysis.Token{
Position: 0,
Start: -1,
End: -1,
Type: analysis.AlphaNumeric,
Term: []byte(s.fill),
}
ring.Value = &fillerToken
if itemsInRing < s.max {
itemsInRing++
}
rv = append(rv, s.shingleCurrentRingState(ring, itemsInRing)...)
ring = ring.Next()
offset--
}
currentPosition = token.Position
ring.Value = token
if itemsInRing < s.max {
itemsInRing++
}
rv = append(rv, s.shingleCurrentRingState(ring, itemsInRing)...)
ring = ring.Next()
}
return rv
}
func (s *ShingleFilter) shingleCurrentRingState(ring *ring.Ring, itemsInRing int) analysis.TokenStream {
rv := make(analysis.TokenStream, 0)
for shingleN := s.min; shingleN <= s.max; shingleN++ {
// if there are enough items in the ring
// to produce a shingle of this size
if itemsInRing >= shingleN {
thisShingleRing := ring.Move(-(shingleN - 1))
shingledBytes := make([]byte, 0)
pos := 0
start := -1
end := 0
for i := 0; i < shingleN; i++ {
if i != 0 {
shingledBytes = append(shingledBytes, []byte(s.tokenSeparator)...)
}
curr := thisShingleRing.Value.(*analysis.Token)
if pos == 0 && curr.Position != 0 {
pos = curr.Position
}
if start == -1 && curr.Start != -1 {
start = curr.Start
}
if curr.End != -1 {
end = curr.End
}
shingledBytes = append(shingledBytes, curr.Term...)
thisShingleRing = thisShingleRing.Next()
}
token := analysis.Token{
Type: analysis.Shingle,
Term: shingledBytes,
}
if pos != 0 {
token.Position = pos
}
if start != -1 {
token.Start = start
}
if end != -1 {
token.End = end
}
rv = append(rv, &token)
}
}
return rv
}
func ShingleFilterConstructor(config map[string]interface{}, cache *registry.Cache) (analysis.TokenFilter, error) {
minVal, ok := config["min"].(float64)
if !ok {
return nil, fmt.Errorf("must specify min")
}
min := int(minVal)
maxVal, ok := config["max"].(float64)
if !ok {
return nil, fmt.Errorf("must specify max")
}
max := int(maxVal)
outputOriginal := false
outVal, ok := config["output_original"].(bool)
if ok {
outputOriginal = outVal
}
sep := " "
sepVal, ok := config["separator"].(string)
if ok {
sep = sepVal
}
fill := "_"
fillVal, ok := config["filler"].(string)
if ok {
fill = fillVal
}
return NewShingleFilter(min, max, outputOriginal, sep, fill), nil
}
func init() {
err := registry.RegisterTokenFilter(Name, ShingleFilterConstructor)
if err != nil {
panic(err)
}
}

View file

@ -0,0 +1,416 @@
// 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 shingle
import (
"reflect"
"testing"
"github.com/blevesearch/bleve/v2/analysis"
)
func TestShingleFilter(t *testing.T) {
tests := []struct {
min int
max int
outputOriginal bool
separator string
filler string
input analysis.TokenStream
output analysis.TokenStream
}{
{
min: 2,
max: 2,
outputOriginal: false,
separator: " ",
filler: "_",
input: analysis.TokenStream{
&analysis.Token{
Term: []byte("the"),
},
&analysis.Token{
Term: []byte("quick"),
},
&analysis.Token{
Term: []byte("brown"),
},
&analysis.Token{
Term: []byte("fox"),
},
},
output: analysis.TokenStream{
&analysis.Token{
Term: []byte("the quick"),
Type: analysis.Shingle,
},
&analysis.Token{
Term: []byte("quick brown"),
Type: analysis.Shingle,
},
&analysis.Token{
Term: []byte("brown fox"),
Type: analysis.Shingle,
},
},
},
{
min: 3,
max: 3,
outputOriginal: false,
separator: " ",
filler: "_",
input: analysis.TokenStream{
&analysis.Token{
Term: []byte("the"),
},
&analysis.Token{
Term: []byte("quick"),
},
&analysis.Token{
Term: []byte("brown"),
},
&analysis.Token{
Term: []byte("fox"),
},
},
output: analysis.TokenStream{
&analysis.Token{
Term: []byte("the quick brown"),
Type: analysis.Shingle,
},
&analysis.Token{
Term: []byte("quick brown fox"),
Type: analysis.Shingle,
},
},
},
{
min: 2,
max: 3,
outputOriginal: false,
separator: " ",
filler: "_",
input: analysis.TokenStream{
&analysis.Token{
Term: []byte("the"),
},
&analysis.Token{
Term: []byte("quick"),
},
&analysis.Token{
Term: []byte("brown"),
},
&analysis.Token{
Term: []byte("fox"),
},
},
output: analysis.TokenStream{
&analysis.Token{
Term: []byte("the quick"),
Type: analysis.Shingle,
},
&analysis.Token{
Term: []byte("quick brown"),
Type: analysis.Shingle,
},
&analysis.Token{
Term: []byte("the quick brown"),
Type: analysis.Shingle,
},
&analysis.Token{
Term: []byte("brown fox"),
Type: analysis.Shingle,
},
&analysis.Token{
Term: []byte("quick brown fox"),
Type: analysis.Shingle,
},
},
},
{
min: 3,
max: 3,
outputOriginal: false,
separator: " ",
filler: "_",
input: analysis.TokenStream{
&analysis.Token{
Term: []byte("ugly"),
Position: 1,
},
&analysis.Token{
Term: []byte("quick"),
Position: 3,
},
&analysis.Token{
Term: []byte("brown"),
Position: 4,
},
},
output: analysis.TokenStream{
&analysis.Token{
Term: []byte("ugly _ quick"),
Type: analysis.Shingle,
Position: 1,
},
&analysis.Token{
Term: []byte("_ quick brown"),
Type: analysis.Shingle,
Position: 3,
},
},
},
{
min: 1,
max: 5,
outputOriginal: false,
separator: " ",
filler: "_",
input: analysis.TokenStream{
&analysis.Token{
Term: []byte("test"),
Position: 1,
},
&analysis.Token{
Term: []byte("text"),
Position: 2,
},
// token 3 removed by stop filter
&analysis.Token{
Term: []byte("see"),
Position: 4,
},
&analysis.Token{
Term: []byte("shingles"),
Position: 5,
},
},
output: analysis.TokenStream{
&analysis.Token{
Term: []byte("test"),
Type: analysis.Shingle,
Position: 1,
},
&analysis.Token{
Term: []byte("text"),
Type: analysis.Shingle,
Position: 2,
},
&analysis.Token{
Term: []byte("test text"),
Type: analysis.Shingle,
Position: 1,
},
&analysis.Token{
Term: []byte("_"),
Type: analysis.Shingle,
},
&analysis.Token{
Term: []byte("text _"),
Type: analysis.Shingle,
Position: 2,
},
&analysis.Token{
Term: []byte("test text _"),
Type: analysis.Shingle,
Position: 1,
},
&analysis.Token{
Term: []byte("see"),
Type: analysis.Shingle,
Position: 4,
},
&analysis.Token{
Term: []byte("_ see"),
Type: analysis.Shingle,
Position: 4,
},
&analysis.Token{
Term: []byte("text _ see"),
Type: analysis.Shingle,
Position: 2,
},
&analysis.Token{
Term: []byte("test text _ see"),
Type: analysis.Shingle,
Position: 1,
},
&analysis.Token{
Term: []byte("shingles"),
Type: analysis.Shingle,
Position: 5,
},
&analysis.Token{
Term: []byte("see shingles"),
Type: analysis.Shingle,
Position: 4,
},
&analysis.Token{
Term: []byte("_ see shingles"),
Type: analysis.Shingle,
Position: 4,
},
&analysis.Token{
Term: []byte("text _ see shingles"),
Type: analysis.Shingle,
Position: 2,
},
&analysis.Token{
Term: []byte("test text _ see shingles"),
Type: analysis.Shingle,
Position: 1,
},
},
},
{
min: 2,
max: 2,
outputOriginal: true,
separator: " ",
filler: "_",
input: analysis.TokenStream{
&analysis.Token{
Term: []byte("the"),
},
&analysis.Token{
Term: []byte("quick"),
},
&analysis.Token{
Term: []byte("brown"),
},
&analysis.Token{
Term: []byte("fox"),
},
},
output: analysis.TokenStream{
&analysis.Token{
Term: []byte("the"),
},
&analysis.Token{
Term: []byte("quick"),
},
&analysis.Token{
Term: []byte("the quick"),
Type: analysis.Shingle,
},
&analysis.Token{
Term: []byte("brown"),
},
&analysis.Token{
Term: []byte("quick brown"),
Type: analysis.Shingle,
},
&analysis.Token{
Term: []byte("fox"),
},
&analysis.Token{
Term: []byte("brown fox"),
Type: analysis.Shingle,
},
},
},
}
for _, test := range tests {
shingleFilter := NewShingleFilter(test.min, test.max, test.outputOriginal, test.separator, test.filler)
actual := shingleFilter.Filter(test.input)
if !reflect.DeepEqual(actual, test.output) {
t.Errorf("expected %s, got %s", test.output, actual)
}
}
}
// TestShingleFilterBug431 tests that the shingle filter is in fact stateless
// by making using the same filter instance twice and ensuring we do not get
// contaminated output
func TestShingleFilterBug431(t *testing.T) {
tests := []struct {
input analysis.TokenStream
output analysis.TokenStream
}{
{
input: analysis.TokenStream{
&analysis.Token{
Term: []byte("the"),
},
&analysis.Token{
Term: []byte("quick"),
},
&analysis.Token{
Term: []byte("brown"),
},
&analysis.Token{
Term: []byte("fox"),
},
},
output: analysis.TokenStream{
&analysis.Token{
Term: []byte("the quick"),
Type: analysis.Shingle,
},
&analysis.Token{
Term: []byte("quick brown"),
Type: analysis.Shingle,
},
&analysis.Token{
Term: []byte("brown fox"),
Type: analysis.Shingle,
},
},
},
{
input: analysis.TokenStream{
&analysis.Token{
Term: []byte("a"),
},
&analysis.Token{
Term: []byte("sad"),
},
&analysis.Token{
Term: []byte("dirty"),
},
&analysis.Token{
Term: []byte("sock"),
},
},
output: analysis.TokenStream{
&analysis.Token{
Term: []byte("a sad"),
Type: analysis.Shingle,
},
&analysis.Token{
Term: []byte("sad dirty"),
Type: analysis.Shingle,
},
&analysis.Token{
Term: []byte("dirty sock"),
Type: analysis.Shingle,
},
},
},
}
shingleFilter := NewShingleFilter(2, 2, false, " ", "_")
for _, test := range tests {
actual := shingleFilter.Filter(test.input)
if !reflect.DeepEqual(actual, test.output) {
t.Errorf("expected %s, got %s", test.output, actual)
}
}
}