snapshot of https://github.com/blevesearch/bleve.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
Coverage / coverage (push) Failing after 6s
Tests / test (1.24.x, ubuntu-latest) (push) Failing after 42s
Tests / test (1.26.x, ubuntu-latest) (push) Failing after 38s
Tests / test (1.25.x, ubuntu-latest) (push) Failing after 35s
Tests / test (1.25.x, windows-latest) (push) Has been cancelled
Tests / test (1.26.x, macos-latest) (push) Has been cancelled
Tests / test (1.26.x, windows-latest) (push) Has been cancelled
Tests / test (1.24.x, macos-latest) (push) Has been cancelled
Tests / test (1.24.x, windows-latest) (push) Has been cancelled
Tests / test (1.25.x, macos-latest) (push) Has been cancelled
Some checks failed
Coverage / coverage (push) Failing after 6s
Tests / test (1.24.x, ubuntu-latest) (push) Failing after 42s
Tests / test (1.26.x, ubuntu-latest) (push) Failing after 38s
Tests / test (1.25.x, ubuntu-latest) (push) Failing after 35s
Tests / test (1.25.x, windows-latest) (push) Has been cancelled
Tests / test (1.26.x, macos-latest) (push) Has been cancelled
Tests / test (1.26.x, windows-latest) (push) Has been cancelled
Tests / test (1.24.x, macos-latest) (push) Has been cancelled
Tests / test (1.24.x, windows-latest) (push) Has been cancelled
Tests / test (1.25.x, macos-latest) (push) Has been cancelled
This commit is contained in:
146
term_limit_integration_test.go
Normal file
146
term_limit_integration_test.go
Normal file
@@ -0,0 +1,146 @@
|
||||
// Copyright (c) 2026 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/blevesearch/bleve/v2/search"
|
||||
"github.com/blevesearch/bleve/v2/search/query"
|
||||
)
|
||||
|
||||
// disjunctionOfTerms builds a disjunction of n distinct term queries over the
|
||||
// "name" field. Each term query becomes one leaf term searcher at construction
|
||||
// time, regardless of whether it matches any document.
|
||||
func disjunctionOfTerms(prefix string, n int) query.Query {
|
||||
dq := NewDisjunctionQuery()
|
||||
for i := 0; i < n; i++ {
|
||||
tq := NewTermQuery(fmt.Sprintf("%s%d", prefix, i))
|
||||
tq.SetField("name")
|
||||
dq.AddQuery(tq)
|
||||
}
|
||||
return dq
|
||||
}
|
||||
|
||||
// conjunctionOfDisjunctions builds a conjunction of `groups` disjunctions, each
|
||||
// containing `per` distinct term queries -> groups*per leaf term searchers, but
|
||||
// no single fan-out node has more than `per` children.
|
||||
func conjunctionOfDisjunctions(groups, per int) query.Query {
|
||||
cq := NewConjunctionQuery()
|
||||
for g := 0; g < groups; g++ {
|
||||
cq.AddQuery(disjunctionOfTerms(fmt.Sprintf("g%d_", g), per))
|
||||
}
|
||||
return cq
|
||||
}
|
||||
|
||||
// TestBleveMaxTermSearchers exercises the full bleveMaxTerms wiring end-to-end
|
||||
// through SearchInContext: the per-search counter is installed from the ctx
|
||||
// limit, every leaf term searcher is counted, and construction fails once the
|
||||
// running total exceeds the limit. This complements TestRecordTermSearcher
|
||||
// (which only covers the counter primitive) by validating that the counter is
|
||||
// actually installed and consulted along the real search path.
|
||||
func TestBleveMaxTermSearchers(t *testing.T) {
|
||||
idx, err := NewMemOnly(NewIndexMapping())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if cerr := idx.Close(); cerr != nil {
|
||||
t.Fatal(cerr)
|
||||
}
|
||||
}()
|
||||
|
||||
// A couple of real documents so the term searchers open real readers.
|
||||
if err := idx.Index("doc0", map[string]interface{}{"name": "alpha beta gamma"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := idx.Index("doc1", map[string]interface{}{"name": "delta epsilon zeta"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
runSearch := func(q query.Query, limit int) error {
|
||||
ctx := context.WithValue(context.Background(), search.MaxTermSearchersKey, limit)
|
||||
_, serr := idx.SearchInContext(ctx, NewSearchRequest(q))
|
||||
return serr
|
||||
}
|
||||
|
||||
t.Run("disabled when limit unset (0)", func(t *testing.T) {
|
||||
if err := runSearch(disjunctionOfTerms("t", 6), 0); err != nil {
|
||||
t.Fatalf("limit 0 should disable the check, got: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("disabled when limit is negative", func(t *testing.T) {
|
||||
// A non-positive limit (as produced upstream when a request explicitly
|
||||
// disables bleveMaxTerms) must be treated as "no limit", not enforced.
|
||||
if err := runSearch(disjunctionOfTerms("t", 6), -1); err != nil {
|
||||
t.Fatalf("negative limit should disable the check, got: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("under the limit succeeds", func(t *testing.T) {
|
||||
// 6 leaf term searchers, limit 100.
|
||||
if err := runSearch(disjunctionOfTerms("t", 6), 100); err != nil {
|
||||
t.Fatalf("6 term searchers under limit 100 should succeed, got: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("exactly at the limit succeeds", func(t *testing.T) {
|
||||
// 6 leaf term searchers, limit 6: 6 is not > 6, so it is allowed.
|
||||
if err := runSearch(disjunctionOfTerms("t", 6), 6); err != nil {
|
||||
t.Fatalf("6 term searchers at limit 6 should succeed, got: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("over the limit fails with TooManyTermSearchers", func(t *testing.T) {
|
||||
// 6 leaf term searchers, limit 5 -> trips at the 6th.
|
||||
err := runSearch(disjunctionOfTerms("t", 6), 5)
|
||||
if err == nil {
|
||||
t.Fatal("6 term searchers over limit 5 should fail, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "TooManyTermSearchers") {
|
||||
t.Fatalf("expected a TooManyTermSearchers error, got: %v", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "set to 5") {
|
||||
t.Fatalf("error should report the effective limit (5), got: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("counts globally across the whole query tree", func(t *testing.T) {
|
||||
// 2 disjunctions * 4 terms = 8 leaf term searchers, but no single node
|
||||
// has more than 4 children. With DisjunctionMaxClauseCount unset this
|
||||
// can only be tripped by the global bleveMaxTerms count, proving the two
|
||||
// checks are distinct: bleveMaxClauseCount is per-node, bleveMaxTerms is
|
||||
// the sum across the tree.
|
||||
err := runSearch(conjunctionOfDisjunctions(2, 4), 5)
|
||||
if err == nil {
|
||||
t.Fatal("8 term searchers over limit 5 should fail, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "TooManyTermSearchers") {
|
||||
t.Fatalf("expected a TooManyTermSearchers error, got: %v", err)
|
||||
}
|
||||
if strings.Contains(err.Error(), "TooManyClauses") {
|
||||
t.Fatalf("should be the global term-count check, not the per-node clause check: %v", err)
|
||||
}
|
||||
|
||||
// Same query, limit 8 -> allowed (8 is not > 8).
|
||||
if err := runSearch(conjunctionOfDisjunctions(2, 4), 8); err != nil {
|
||||
t.Fatalf("8 term searchers at limit 8 should succeed, got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user