use std::any::Any;
use std::fmt::Debug;
use arrow_array::FixedSizeListArray;
use lance_index::scalar::AnyQuery;
use lance_index::scalar::inverted::DocumentGranularity;
use lance_linalg::distance::DistanceType;
use roaring::RoaringTreemap;
use super::RowPosition;
pub trait MemQuery: Debug + Send + Sync {
fn as_any(&self) -> &dyn Any;
}
impl<T: AnyQuery> MemQuery for T {
fn as_any(&self) -> &dyn Any {
AnyQuery::as_any(self)
}
}
#[derive(Debug)]
pub struct ScalarQuery<'a>(pub &'a dyn AnyQuery);
impl MemQuery for ScalarQuery<'_> {
fn as_any(&self) -> &dyn Any {
self.0.as_any()
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct PositionSet(RoaringTreemap);
impl PositionSet {
pub fn empty() -> Self {
Self(RoaringTreemap::new())
}
pub fn all_visible(max_visible: RowPosition) -> Self {
let mut positions = RoaringTreemap::new();
positions.insert_range(0..=max_visible);
Self(positions)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> u64 {
self.0.len()
}
pub fn contains(&self, position: RowPosition) -> bool {
self.0.contains(position)
}
pub fn insert(&mut self, position: RowPosition) {
self.0.insert(position);
}
pub fn iter(&self) -> impl Iterator<Item = RowPosition> + '_ {
self.0.iter()
}
pub fn truncate_to(mut self, max_visible: RowPosition) -> Self {
if let Some(first_hidden) = max_visible.checked_add(1) {
self.0.remove_range(first_hidden..);
}
self
}
}
impl FromIterator<RowPosition> for PositionSet {
fn from_iter<I: IntoIterator<Item = RowPosition>>(iter: I) -> Self {
Self(iter.into_iter().collect())
}
}
impl From<RoaringTreemap> for PositionSet {
fn from(map: RoaringTreemap) -> Self {
Self(map)
}
}
impl From<PositionSet> for Vec<RowPosition> {
fn from(set: PositionSet) -> Self {
set.0.into_iter().collect()
}
}
impl std::ops::BitAnd for PositionSet {
type Output = Self;
fn bitand(self, rhs: Self) -> Self {
Self(self.0 & rhs.0)
}
}
impl std::ops::BitOr for PositionSet {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct MemSearchResult {
pub at_least: PositionSet,
pub at_most: PositionSet,
}
impl MemSearchResult {
pub fn exact(positions: PositionSet) -> Self {
Self {
at_least: positions.clone(),
at_most: positions,
}
}
pub fn at_most(positions: PositionSet) -> Self {
Self {
at_least: PositionSet::empty(),
at_most: positions,
}
}
pub fn empty() -> Self {
Self::exact(PositionSet::empty())
}
pub fn is_exact(&self) -> bool {
self.at_least == self.at_most
}
pub fn truncate_to(self, max_visible: RowPosition) -> Self {
Self {
at_least: self.at_least.truncate_to(max_visible),
at_most: self.at_most.truncate_to(max_visible),
}
}
}
impl std::ops::BitAnd for MemSearchResult {
type Output = Self;
fn bitand(self, rhs: Self) -> Self {
Self {
at_least: self.at_least & rhs.at_least,
at_most: self.at_most & rhs.at_most,
}
}
}
impl std::ops::BitOr for MemSearchResult {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self {
at_least: self.at_least | rhs.at_least,
at_most: self.at_most | rhs.at_most,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct RankedMatch {
pub position: RowPosition,
pub score: f32,
pub element: Option<Vec<u32>>,
}
impl RankedMatch {
pub fn new(position: RowPosition, score: f32) -> Self {
Self {
position,
score,
element: None,
}
}
pub fn nested(position: RowPosition, score: f32, element: Vec<u32>) -> Self {
Self {
position,
score,
element: Some(element),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum MemMatches {
Filter(MemSearchResult),
Ranked(Vec<RankedMatch>),
}
impl MemMatches {
pub fn exact(positions: impl IntoIterator<Item = RowPosition>) -> Self {
Self::Filter(MemSearchResult::exact(positions.into_iter().collect()))
}
pub fn at_most(positions: impl IntoIterator<Item = RowPosition>) -> Self {
Self::Filter(MemSearchResult::at_most(positions.into_iter().collect()))
}
pub fn ranked(matches: Vec<RankedMatch>) -> Self {
Self::Ranked(matches)
}
pub fn as_filter(&self) -> Option<&MemSearchResult> {
match self {
Self::Filter(result) => Some(result),
Self::Ranked(_) => None,
}
}
pub fn as_ranked(&self) -> Option<&[RankedMatch]> {
match self {
Self::Ranked(matches) => Some(matches),
Self::Filter(_) => None,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct SearchContext {
pub max_visible: RowPosition,
pub match_budget: Option<u64>,
}
impl SearchContext {
pub fn new(max_visible: RowPosition) -> Self {
Self {
max_visible,
match_budget: None,
}
}
pub fn with_match_budget(mut self, budget: u64) -> Self {
self.match_budget = Some(budget);
self
}
}
#[derive(Debug)]
pub struct VectorMemQuery {
pub vector: FixedSizeListArray,
pub k: usize,
pub ef: Option<usize>,
pub distance_type: Option<DistanceType>,
}
impl MemQuery for VectorMemQuery {
fn as_any(&self) -> &dyn Any {
self
}
}
#[derive(Debug)]
pub struct FtsMemQuery {
pub expr: super::fts::FtsQueryExpr,
pub options: super::fts::SearchOptions,
pub granularity: DocumentGranularity,
}
impl FtsMemQuery {
pub fn probe(granularity: DocumentGranularity) -> Self {
Self {
expr: super::fts::FtsQueryExpr::Match {
column: None,
query: String::new(),
operator: Default::default(),
boost: 1.0,
},
options: super::fts::SearchOptions::new(),
granularity,
}
}
}
impl MemQuery for FtsMemQuery {
fn as_any(&self) -> &dyn Any {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
fn set(positions: &[RowPosition]) -> PositionSet {
positions.iter().copied().collect()
}
#[test]
fn answers_combine_bound_by_bound() {
let settled = MemSearchResult::exact(set(&[1, 2]));
let candidates = MemSearchResult::at_most(set(&[2, 3]));
assert_eq!(
settled.clone() & candidates.clone(),
MemSearchResult {
at_least: PositionSet::empty(),
at_most: set(&[2]),
}
);
assert_eq!(
settled | candidates,
MemSearchResult {
at_least: set(&[1, 2]),
at_most: set(&[1, 2, 3]),
}
);
assert!(MemSearchResult::empty().is_exact());
}
#[test]
fn truncating_keeps_only_the_visible_rows() {
let answer = MemSearchResult::exact(set(&[0, 5, u64::MAX]));
assert_eq!(
answer.clone().truncate_to(4),
MemSearchResult::exact(set(&[0]))
);
assert_eq!(answer.clone().truncate_to(u64::MAX), answer);
}
}