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
123 lines
4.3 KiB
Go
123 lines
4.3 KiB
Go
// 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 geov2
|
|
|
|
import segment "github.com/blevesearch/scorch_segment_api/v2"
|
|
|
|
type queryEvaluator struct {
|
|
queryInnerCells []uint64
|
|
queryCrossCells []uint64
|
|
|
|
innerCells []uint64
|
|
innerDocIDs []uint32
|
|
|
|
crossCells []uint64
|
|
crossDocIDs []uint32
|
|
}
|
|
|
|
func NewQueryEvaluator(query Query, geoData segment.GeoShapeV2Data) *queryEvaluator {
|
|
return &queryEvaluator{
|
|
queryInnerCells: query.InnerCells(),
|
|
queryCrossCells: query.CrossCells(),
|
|
innerCells: geoData.InnerCells(),
|
|
innerDocIDs: geoData.InnerDocIDs(),
|
|
crossCells: geoData.CrossCells(),
|
|
crossDocIDs: geoData.CrossDocIDs(),
|
|
}
|
|
}
|
|
|
|
// find the leftmost index in arr where arr[i] >= target, or
|
|
// len(arr) if no such index exists
|
|
func binarySearchLeftmostGreaterOrEqual(arr []uint64, target uint64) int {
|
|
lo, hi := 0, len(arr)
|
|
for lo < hi {
|
|
mid := int(uint(lo+hi) >> 1) // bitshift avoids the addition overflow risk
|
|
if arr[mid] < target {
|
|
lo = mid + 1
|
|
} else {
|
|
hi = mid
|
|
}
|
|
}
|
|
return lo
|
|
}
|
|
|
|
// forEachScoredDoc invokes fn exactly once for every docID present in either
|
|
// score map, passing that doc's inner and cross scores (0 when the doc is
|
|
// absent from the corresponding map).
|
|
func forEachScoredDoc(innerScores, crossScores map[uint32]uint64,
|
|
fn func(id uint32, inner, cross uint64)) {
|
|
for id, inner := range innerScores {
|
|
fn(id, inner, crossScores[id])
|
|
}
|
|
for id, cross := range crossScores {
|
|
if _, ok := innerScores[id]; !ok {
|
|
fn(id, 0, cross)
|
|
}
|
|
}
|
|
}
|
|
|
|
// scan and score the overlap of query inner cells with all index cells
|
|
func (qe *queryEvaluator) rangeScanInner(innerScores, crossScores map[uint32]uint64) {
|
|
for _, cell := range qe.queryInnerCells {
|
|
minVal, maxVal := getCellSearchBounds(cell)
|
|
cellLevel := getCellLevel(cell)
|
|
rangeScanOne(cell, minVal, maxVal, cellLevel, qe.innerCells, qe.innerDocIDs, innerScores)
|
|
rangeScanOne(cell, minVal, maxVal, cellLevel, qe.crossCells, qe.crossDocIDs, crossScores)
|
|
}
|
|
}
|
|
|
|
// scan and score the overlap of query cross cells with all index cells
|
|
func (qe *queryEvaluator) rangeScanCross(innerScores, crossScores map[uint32]uint64) {
|
|
for _, cell := range qe.queryCrossCells {
|
|
minVal, maxVal := getCellSearchBounds(cell)
|
|
cellLevel := getCellLevel(cell)
|
|
rangeScanOne(cell, minVal, maxVal, cellLevel, qe.innerCells, qe.innerDocIDs, innerScores)
|
|
rangeScanOne(cell, minVal, maxVal, cellLevel, qe.crossCells, qe.crossDocIDs, crossScores)
|
|
}
|
|
}
|
|
|
|
// scan and score the overlap of a single query cell with the given index cells
|
|
func rangeScanOne(queryCell uint64, minVal, maxVal, cellLevel uint64,
|
|
indexCells []uint64, docIds []uint32, scores map[uint32]uint64) {
|
|
// find the range of index cells within the min/max bounds of the query cell
|
|
// end will be < start if there are no index cells within the bounds
|
|
start := binarySearchLeftmostGreaterOrEqual(indexCells, minVal)
|
|
end := binarySearchLeftmostGreaterOrEqual(indexCells, maxVal+1) - 1
|
|
|
|
// score all index cells within the bounds of the query cell
|
|
for i := start; i <= end; i++ {
|
|
id := docIds[i]
|
|
val := indexCells[i]
|
|
|
|
valLevel := getCellLevel(val)
|
|
scores[id] += calcScore(cellLevel, valLevel)
|
|
}
|
|
|
|
// score all parent cells of the query cell that are present in the index
|
|
// since parent cells are not within the min/max bounds
|
|
for level := int(cellLevel) - 1; level >= 0; level-- {
|
|
// get the parent cell of the query cell at this level
|
|
parentCell := getParentCell(queryCell, level)
|
|
// search for the leftmost index of this parent cell in the index cells
|
|
parentStart := binarySearchLeftmostGreaterOrEqual(indexCells, parentCell)
|
|
|
|
// score all index cells that match this parent cell exactly
|
|
for i := parentStart; i < len(indexCells) && indexCells[i] == parentCell; i++ {
|
|
id := docIds[i]
|
|
scores[id] += calcScore(cellLevel, uint64(level))
|
|
}
|
|
}
|
|
}
|