f1
This commit is contained in:
299
src/query/filter.rs
Normal file
299
src/query/filter.rs
Normal file
@@ -0,0 +1,299 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
ops::{BitAndAssign, BitOrAssign, BitXorAssign},
|
||||
};
|
||||
|
||||
use ahash::AHashSet;
|
||||
use roaring::RoaringBitmap;
|
||||
|
||||
use crate::{
|
||||
fts::{
|
||||
index::MAX_TOKEN_LENGTH, stemmer::Stemmer, term_index::TermIndex, tokenizers::Tokenizer,
|
||||
},
|
||||
write::Tokenize,
|
||||
BitmapKey, Error, IndexKey, Store, ValueKey, BM_TERM, TERM_EXACT, TERM_STEMMED,
|
||||
};
|
||||
|
||||
use super::{Filter, ResultSet};
|
||||
|
||||
struct State {
|
||||
op: Filter,
|
||||
bm: Option<RoaringBitmap>,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub fn filter(
|
||||
&self,
|
||||
account_id: u32,
|
||||
collection: u8,
|
||||
filters: Vec<Filter>,
|
||||
) -> crate::Result<ResultSet> {
|
||||
let document_ids = self
|
||||
.get_document_ids(account_id, collection)?
|
||||
.unwrap_or_else(RoaringBitmap::new);
|
||||
let mut state: State = Filter::And.into();
|
||||
let mut stack = Vec::new();
|
||||
let mut filters = filters.into_iter().peekable();
|
||||
|
||||
while let Some(filter) = filters.next() {
|
||||
match filter {
|
||||
Filter::HasKeyword { field, value } => {
|
||||
state.op.apply(
|
||||
&mut state.bm,
|
||||
self.get_bitmap(BitmapKey {
|
||||
account_id,
|
||||
collection,
|
||||
family: BM_TERM | TERM_EXACT,
|
||||
field,
|
||||
key: value.as_bytes(),
|
||||
})?,
|
||||
&document_ids,
|
||||
);
|
||||
}
|
||||
Filter::HasKeywords { field, value } => {
|
||||
let tokens = value.tokenize();
|
||||
state.op.apply(
|
||||
&mut state.bm,
|
||||
self.get_bitmaps_intersection(
|
||||
tokens
|
||||
.iter()
|
||||
.map(|key| BitmapKey {
|
||||
account_id,
|
||||
collection,
|
||||
family: BM_TERM | TERM_EXACT,
|
||||
field,
|
||||
key,
|
||||
})
|
||||
.collect(),
|
||||
)?,
|
||||
&document_ids,
|
||||
);
|
||||
}
|
||||
Filter::MatchValue { field, op, value } => {
|
||||
state.op.apply(
|
||||
&mut state.bm,
|
||||
self.range_to_bitmap(
|
||||
IndexKey {
|
||||
account_id,
|
||||
collection,
|
||||
field,
|
||||
key: &value,
|
||||
},
|
||||
op,
|
||||
)?,
|
||||
&document_ids,
|
||||
);
|
||||
}
|
||||
Filter::HasText {
|
||||
field,
|
||||
text,
|
||||
language,
|
||||
match_phrase,
|
||||
} => {
|
||||
if match_phrase {
|
||||
let phrase = Tokenizer::new(&text, language, MAX_TOKEN_LENGTH)
|
||||
.map(|token| token.word)
|
||||
.collect::<Vec<_>>();
|
||||
let mut keys = Vec::with_capacity(phrase.len());
|
||||
|
||||
for word in &phrase {
|
||||
let key = BitmapKey {
|
||||
account_id,
|
||||
collection,
|
||||
family: BM_TERM | TERM_EXACT,
|
||||
field,
|
||||
key: word.as_bytes(),
|
||||
};
|
||||
if !keys.contains(&key) {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve the Term Index for each candidate and match the exact phrase
|
||||
if let Some(candidates) = self.get_bitmaps_intersection(keys)? {
|
||||
let mut results = RoaringBitmap::new();
|
||||
for document_id in candidates.iter() {
|
||||
if let Some(term_index) = self.get_value::<TermIndex>(ValueKey {
|
||||
account_id,
|
||||
collection,
|
||||
document_id,
|
||||
field: u8::MAX,
|
||||
})? {
|
||||
if term_index
|
||||
.match_terms(
|
||||
&phrase
|
||||
.iter()
|
||||
.map(|w| term_index.get_match_term(w, None))
|
||||
.collect::<Vec<_>>(),
|
||||
None,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.map_err(|e| {
|
||||
Error::InternalError(format!(
|
||||
"Corrupted TermIndex for {}: {:?}",
|
||||
document_id, e
|
||||
))
|
||||
})?
|
||||
.is_some()
|
||||
{
|
||||
results.insert(document_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
state.op.apply(&mut state.bm, results.into(), &document_ids);
|
||||
} else {
|
||||
state.op.apply(&mut state.bm, None, &document_ids);
|
||||
}
|
||||
} else {
|
||||
let words = Stemmer::new(&text, language, MAX_TOKEN_LENGTH)
|
||||
.map(|token| (token.word, token.stemmed_word.unwrap_or(Cow::from(""))))
|
||||
.collect::<AHashSet<_>>();
|
||||
let mut requested_keys = AHashSet::default();
|
||||
let mut text_bitmap = None;
|
||||
|
||||
for (word, stemmed_word) in &words {
|
||||
let mut keys = Vec::new();
|
||||
|
||||
for (word, family) in [
|
||||
(word, BM_TERM | TERM_EXACT),
|
||||
(word, BM_TERM | TERM_STEMMED),
|
||||
(stemmed_word, BM_TERM | TERM_EXACT),
|
||||
(stemmed_word, BM_TERM | TERM_STEMMED),
|
||||
] {
|
||||
if !word.is_empty() {
|
||||
let key = BitmapKey {
|
||||
account_id,
|
||||
collection,
|
||||
family,
|
||||
field,
|
||||
key: word.as_bytes(),
|
||||
};
|
||||
if !requested_keys.contains(&key) {
|
||||
requested_keys.insert(key);
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Term already matched on a previous iteration
|
||||
if keys.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
Filter::And.apply(
|
||||
&mut text_bitmap,
|
||||
self.get_bitmaps_union(keys)?,
|
||||
&document_ids,
|
||||
);
|
||||
|
||||
if text_bitmap.as_ref().unwrap().is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
state.op.apply(&mut state.bm, text_bitmap, &document_ids);
|
||||
}
|
||||
}
|
||||
Filter::InBitmap { family, field, key } => {
|
||||
state.op.apply(
|
||||
&mut state.bm,
|
||||
self.get_bitmap(BitmapKey {
|
||||
account_id,
|
||||
collection,
|
||||
family,
|
||||
field,
|
||||
key: &key,
|
||||
})?,
|
||||
&document_ids,
|
||||
);
|
||||
}
|
||||
Filter::DocumentSet(set) => {
|
||||
state.op.apply(&mut state.bm, Some(set), &document_ids);
|
||||
}
|
||||
op @ (Filter::And | Filter::Or | Filter::Not) => {
|
||||
stack.push(state);
|
||||
state = op.into();
|
||||
continue;
|
||||
}
|
||||
Filter::End => {
|
||||
if let Some(mut prev_state) = stack.pop() {
|
||||
prev_state
|
||||
.op
|
||||
.apply(&mut prev_state.bm, state.bm, &document_ids);
|
||||
state = prev_state;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(state.op, Filter::And) && state.bm.as_ref().unwrap().is_empty() {
|
||||
while let Some(filter) = filters.peek() {
|
||||
if matches!(filter, Filter::End) {
|
||||
break;
|
||||
} else {
|
||||
filters.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ResultSet {
|
||||
results: state.bm.unwrap_or_else(RoaringBitmap::new),
|
||||
document_ids,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Filter {
|
||||
#[inline(always)]
|
||||
pub fn apply(
|
||||
&self,
|
||||
dest: &mut Option<RoaringBitmap>,
|
||||
mut src: Option<RoaringBitmap>,
|
||||
not_mask: &RoaringBitmap,
|
||||
) {
|
||||
if let Some(dest) = dest {
|
||||
match self {
|
||||
Filter::And => {
|
||||
if let Some(src) = src {
|
||||
dest.bitand_assign(src);
|
||||
} else {
|
||||
dest.clear();
|
||||
}
|
||||
}
|
||||
Filter::Or => {
|
||||
if let Some(src) = src {
|
||||
dest.bitor_assign(src);
|
||||
}
|
||||
}
|
||||
Filter::Not => {
|
||||
if let Some(mut src) = src {
|
||||
src.bitxor_assign(not_mask);
|
||||
dest.bitand_assign(src);
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else if let Some(ref mut src_) = src {
|
||||
if let Filter::Not = self {
|
||||
src_.bitxor_assign(not_mask);
|
||||
}
|
||||
*dest = src;
|
||||
} else if let Filter::Not = self {
|
||||
*dest = Some(not_mask.clone());
|
||||
} else {
|
||||
*dest = Some(RoaringBitmap::new());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Filter> for State {
|
||||
fn from(value: Filter) -> Self {
|
||||
Self {
|
||||
op: value,
|
||||
bm: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
160
src/query/mod.rs
Normal file
160
src/query/mod.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
pub mod filter;
|
||||
pub mod sort;
|
||||
|
||||
use roaring::RoaringBitmap;
|
||||
|
||||
use crate::{
|
||||
fts::{lang::LanguageDetector, Language},
|
||||
Serialize,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Operator {
|
||||
LowerThan,
|
||||
LowerEqualThan,
|
||||
GreaterThan,
|
||||
GreaterEqualThan,
|
||||
Equal,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Filter {
|
||||
HasKeyword {
|
||||
field: u8,
|
||||
value: String,
|
||||
},
|
||||
HasKeywords {
|
||||
field: u8,
|
||||
value: String,
|
||||
},
|
||||
MatchValue {
|
||||
field: u8,
|
||||
op: Operator,
|
||||
value: Vec<u8>,
|
||||
},
|
||||
HasText {
|
||||
field: u8,
|
||||
text: String,
|
||||
language: Language,
|
||||
match_phrase: bool,
|
||||
},
|
||||
InBitmap {
|
||||
family: u8,
|
||||
field: u8,
|
||||
key: Vec<u8>,
|
||||
},
|
||||
DocumentSet(RoaringBitmap),
|
||||
And,
|
||||
Or,
|
||||
Not,
|
||||
End,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Comparator {
|
||||
Field { field: u8, ascending: bool },
|
||||
DocumentSet { set: RoaringBitmap, ascending: bool },
|
||||
}
|
||||
|
||||
pub struct ResultSet {
|
||||
results: RoaringBitmap,
|
||||
document_ids: RoaringBitmap,
|
||||
}
|
||||
|
||||
pub struct SortedResultRet {
|
||||
pub position: i32,
|
||||
pub ids: Vec<u32>,
|
||||
pub found_anchor: bool,
|
||||
}
|
||||
|
||||
impl Filter {
|
||||
pub fn new_condition(field: impl Into<u8>, op: Operator, value: impl Serialize) -> Self {
|
||||
Filter::MatchValue {
|
||||
field: field.into(),
|
||||
op,
|
||||
value: value.serialize(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq(field: impl Into<u8>, value: impl Serialize) -> Self {
|
||||
Filter::MatchValue {
|
||||
field: field.into(),
|
||||
op: Operator::Equal,
|
||||
value: value.serialize(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lt(field: impl Into<u8>, value: impl Serialize) -> Self {
|
||||
Filter::MatchValue {
|
||||
field: field.into(),
|
||||
op: Operator::LowerThan,
|
||||
value: value.serialize(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn le(field: impl Into<u8>, value: impl Serialize) -> Self {
|
||||
Filter::MatchValue {
|
||||
field: field.into(),
|
||||
op: Operator::LowerEqualThan,
|
||||
value: value.serialize(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gt(field: impl Into<u8>, value: impl Serialize) -> Self {
|
||||
Filter::MatchValue {
|
||||
field: field.into(),
|
||||
op: Operator::GreaterThan,
|
||||
value: value.serialize(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ge(field: impl Into<u8>, value: impl Serialize) -> Self {
|
||||
Filter::MatchValue {
|
||||
field: field.into(),
|
||||
op: Operator::GreaterEqualThan,
|
||||
value: value.serialize(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_text(field: impl Into<u8>, mut text: String, mut language: Language) -> Self {
|
||||
let match_phrase = (text.starts_with('"') && text.ends_with('"'))
|
||||
|| (text.starts_with('\'') && text.ends_with('\''));
|
||||
|
||||
if !match_phrase && language == Language::Unknown {
|
||||
language = if let Some((l, t)) = text
|
||||
.split_once(':')
|
||||
.and_then(|(l, t)| (Language::from_iso_639(l)?, t.to_string()).into())
|
||||
{
|
||||
text = t;
|
||||
l
|
||||
} else {
|
||||
LanguageDetector::detect_single(&text)
|
||||
.and_then(|(l, c)| if c > 0.3 { Some(l) } else { None })
|
||||
.unwrap_or(Language::Unknown)
|
||||
};
|
||||
}
|
||||
|
||||
Filter::HasText {
|
||||
field: field.into(),
|
||||
text,
|
||||
language,
|
||||
match_phrase,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Comparator {
|
||||
pub fn ascending(field: impl Into<u8>) -> Self {
|
||||
Self::Field {
|
||||
field: field.into(),
|
||||
ascending: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn descending(field: impl Into<u8>) -> Self {
|
||||
Self::Field {
|
||||
field: field.into(),
|
||||
ascending: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
424
src/query/sort.rs
Normal file
424
src/query/sort.rs
Normal file
@@ -0,0 +1,424 @@
|
||||
use std::ops::{BitAndAssign, BitXorAssign};
|
||||
|
||||
use roaring::RoaringBitmap;
|
||||
use rocksdb::{
|
||||
DBIteratorWithThreadMode, Direction, IteratorMode, MultiThreaded, OptimisticTransactionDB,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
backend::rocksdb::{ACCOUNT_KEY_LEN, CF_INDEXES},
|
||||
write::key::KeySerializer,
|
||||
Error, Store,
|
||||
};
|
||||
|
||||
use super::{Comparator, ResultSet, SortedResultRet};
|
||||
|
||||
enum IndexType<'x> {
|
||||
DocumentSet {
|
||||
set: RoaringBitmap,
|
||||
it: Option<roaring::bitmap::IntoIter>,
|
||||
},
|
||||
DB {
|
||||
it: Option<DBIteratorWithThreadMode<'x, OptimisticTransactionDB<MultiThreaded>>>,
|
||||
prefix: Vec<u8>,
|
||||
start_key: Vec<u8>,
|
||||
ascending: bool,
|
||||
prev_item: Option<u32>,
|
||||
prev_key: Option<Box<[u8]>>,
|
||||
},
|
||||
}
|
||||
|
||||
struct IndexIterator<'x> {
|
||||
index: IndexType<'x>,
|
||||
remaining: RoaringBitmap,
|
||||
eof: bool,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn sort(
|
||||
&self,
|
||||
account_id: u32,
|
||||
collection: u8,
|
||||
mut result_set: ResultSet,
|
||||
comparators: Vec<Comparator>,
|
||||
limit: usize,
|
||||
mut position: i32,
|
||||
anchor: Option<u32>,
|
||||
mut anchor_offset: i32,
|
||||
) -> crate::Result<SortedResultRet> {
|
||||
let has_anchor = anchor.is_some();
|
||||
let mut anchor_found = false;
|
||||
let requested_position = position;
|
||||
|
||||
let mut result = SortedResultRet {
|
||||
position,
|
||||
ids: Vec::with_capacity(std::cmp::min(limit, result_set.results.len() as usize)),
|
||||
found_anchor: true,
|
||||
};
|
||||
let mut iterators = comparators
|
||||
.into_iter()
|
||||
.map(|comp| IndexIterator {
|
||||
index: match comp {
|
||||
Comparator::Field { field, ascending } => {
|
||||
let prefix = KeySerializer::new(ACCOUNT_KEY_LEN)
|
||||
.write(account_id)
|
||||
.write(collection)
|
||||
.write(field)
|
||||
.finalize();
|
||||
IndexType::DB {
|
||||
it: None,
|
||||
start_key: if !ascending {
|
||||
let (key_account_id, key_collection, key_field) = if field < u8::MAX
|
||||
{
|
||||
(account_id, collection, field + 1)
|
||||
} else if (collection) < u8::MAX {
|
||||
(account_id, (collection) + 1, field)
|
||||
} else {
|
||||
(account_id + 1, collection, field)
|
||||
};
|
||||
KeySerializer::new(ACCOUNT_KEY_LEN)
|
||||
.write(key_account_id)
|
||||
.write(key_collection)
|
||||
.write(key_field)
|
||||
.finalize()
|
||||
} else {
|
||||
prefix.clone()
|
||||
},
|
||||
prefix,
|
||||
ascending,
|
||||
prev_item: None,
|
||||
prev_key: None,
|
||||
}
|
||||
}
|
||||
Comparator::DocumentSet { mut set, ascending } => IndexType::DocumentSet {
|
||||
set: if !ascending {
|
||||
if !set.is_empty() {
|
||||
set.bitxor_assign(&result_set.document_ids);
|
||||
set
|
||||
} else {
|
||||
result_set.document_ids.clone()
|
||||
}
|
||||
} else {
|
||||
set
|
||||
},
|
||||
it: None,
|
||||
},
|
||||
},
|
||||
remaining: std::mem::replace(&mut result_set.results, RoaringBitmap::new()),
|
||||
eof: false,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut current = 0;
|
||||
|
||||
'outer: loop {
|
||||
let mut doc_id;
|
||||
|
||||
'inner: loop {
|
||||
let (it_opts, mut next_it_opts) = if current < iterators.len() - 1 {
|
||||
let (iterators_first, iterators_last) = iterators.split_at_mut(current + 1);
|
||||
(
|
||||
iterators_first.last_mut().unwrap(),
|
||||
iterators_last.first_mut(),
|
||||
)
|
||||
} else {
|
||||
(&mut iterators[current], None)
|
||||
};
|
||||
|
||||
if !matches!(it_opts.index, IndexType::DB { prev_item,.. } if prev_item.is_some())
|
||||
{
|
||||
if it_opts.remaining.is_empty() {
|
||||
if current > 0 {
|
||||
current -= 1;
|
||||
continue 'inner;
|
||||
} else {
|
||||
break 'outer;
|
||||
}
|
||||
} else if it_opts.remaining.len() == 1 || it_opts.eof {
|
||||
doc_id = it_opts.remaining.min().unwrap();
|
||||
it_opts.remaining.remove(doc_id);
|
||||
break 'inner;
|
||||
}
|
||||
}
|
||||
|
||||
match &mut it_opts.index {
|
||||
IndexType::DB {
|
||||
it,
|
||||
prefix,
|
||||
start_key,
|
||||
ascending,
|
||||
prev_item,
|
||||
prev_key,
|
||||
} => {
|
||||
let it = if let Some(it) = it {
|
||||
it
|
||||
} else {
|
||||
*it = Some(self.db.iterator_cf(
|
||||
&self.db.cf_handle(CF_INDEXES).unwrap(),
|
||||
IteratorMode::From(
|
||||
start_key,
|
||||
if *ascending {
|
||||
Direction::Forward
|
||||
} else {
|
||||
Direction::Reverse
|
||||
},
|
||||
),
|
||||
));
|
||||
it.as_mut().unwrap()
|
||||
};
|
||||
|
||||
let mut prev_key_prefix = prev_key
|
||||
.as_ref()
|
||||
.and_then(|k| k.get(..k.len() - std::mem::size_of::<u32>()))
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Some(prev_item) = prev_item.take() {
|
||||
if let Some(next_it_opts) = &mut next_it_opts {
|
||||
next_it_opts.remaining.insert(prev_item);
|
||||
} else {
|
||||
doc_id = prev_item;
|
||||
break 'inner;
|
||||
}
|
||||
}
|
||||
|
||||
let mut is_eof = false;
|
||||
loop {
|
||||
if let Some(result) = it.next() {
|
||||
let (key, _) = result.map_err(|e| {
|
||||
Error::InternalError(format!("Iterator error: {}", e))
|
||||
})?;
|
||||
if !key.starts_with(prefix) {
|
||||
*prev_key = None;
|
||||
is_eof = true;
|
||||
break;
|
||||
}
|
||||
|
||||
doc_id = u32::from_be_bytes(
|
||||
key.get(key.len() - std::mem::size_of::<u32>()..)
|
||||
.ok_or_else(|| {
|
||||
Error::InternalError("Invalid index entry".to_string())
|
||||
})?
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
if it_opts.remaining.contains(doc_id) {
|
||||
it_opts.remaining.remove(doc_id);
|
||||
|
||||
if let Some(next_it_opts) = &mut next_it_opts {
|
||||
if let Some(prev_key_) = &*prev_key {
|
||||
if key.len() != prev_key_.len()
|
||||
|| !key.starts_with(prev_key_prefix)
|
||||
{
|
||||
*prev_item = Some(doc_id);
|
||||
*prev_key = Some(key);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
*prev_key = Some(key);
|
||||
prev_key_prefix = prev_key
|
||||
.as_ref()
|
||||
.and_then(|key| {
|
||||
key.get(
|
||||
..key.len() - std::mem::size_of::<u32>(),
|
||||
)
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
Error::InternalError(
|
||||
"Invalid index entry".to_string(),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
next_it_opts.remaining.insert(doc_id);
|
||||
} else {
|
||||
// doc id found
|
||||
break 'inner;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
is_eof = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if is_eof {
|
||||
if let Some(next_it_opts) = &mut next_it_opts {
|
||||
if !it_opts.remaining.is_empty() {
|
||||
next_it_opts.remaining |= &it_opts.remaining;
|
||||
it_opts.remaining.clear();
|
||||
}
|
||||
*prev_key = None;
|
||||
it_opts.eof = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
IndexType::DocumentSet { set, it } => {
|
||||
if let Some(it) = it {
|
||||
if let Some(_doc_id) = it.next() {
|
||||
doc_id = _doc_id;
|
||||
break 'inner;
|
||||
}
|
||||
} else {
|
||||
let mut set = set.clone();
|
||||
set.bitand_assign(&it_opts.remaining);
|
||||
let set_len = set.len();
|
||||
if set_len > 0 {
|
||||
it_opts.remaining.bitxor_assign(&set);
|
||||
|
||||
match &mut next_it_opts {
|
||||
Some(next_it_opts) if set_len > 1 => {
|
||||
next_it_opts.remaining = set;
|
||||
}
|
||||
_ if set_len == 1 => {
|
||||
doc_id = set.min().unwrap();
|
||||
break 'inner;
|
||||
}
|
||||
_ => {
|
||||
let mut it_ = set.into_iter();
|
||||
let result = it_.next();
|
||||
*it = Some(it_);
|
||||
if let Some(result) = result {
|
||||
doc_id = result;
|
||||
break 'inner;
|
||||
} else {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if !it_opts.remaining.is_empty() {
|
||||
if let Some(ref mut next_it_opts) = next_it_opts {
|
||||
next_it_opts.remaining = std::mem::take(&mut it_opts.remaining);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(next_it_opts) = next_it_opts {
|
||||
if !next_it_opts.remaining.is_empty() {
|
||||
if next_it_opts.remaining.len() == 1 {
|
||||
doc_id = next_it_opts.remaining.min().unwrap();
|
||||
next_it_opts.remaining.remove(doc_id);
|
||||
break 'inner;
|
||||
} else {
|
||||
match &mut next_it_opts.index {
|
||||
IndexType::DB {
|
||||
it,
|
||||
start_key,
|
||||
ascending,
|
||||
prev_item,
|
||||
prev_key,
|
||||
..
|
||||
} => {
|
||||
if let Some(it) = it {
|
||||
*it = self.db.iterator_cf(
|
||||
&self.db.cf_handle(CF_INDEXES).unwrap(),
|
||||
IteratorMode::From(
|
||||
start_key,
|
||||
if *ascending {
|
||||
Direction::Forward
|
||||
} else {
|
||||
Direction::Reverse
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
*prev_item = None;
|
||||
*prev_key = None;
|
||||
}
|
||||
IndexType::DocumentSet { it, .. } => {
|
||||
*it = None;
|
||||
}
|
||||
}
|
||||
|
||||
current += 1;
|
||||
next_it_opts.eof = false;
|
||||
continue 'inner;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it_opts.eof = true;
|
||||
|
||||
if it_opts.remaining.is_empty() {
|
||||
if current > 0 {
|
||||
current -= 1;
|
||||
} else {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pagination
|
||||
if !has_anchor {
|
||||
if position >= 0 {
|
||||
if position > 0 {
|
||||
position -= 1;
|
||||
} else {
|
||||
result.ids.push(doc_id);
|
||||
if limit > 0 && result.ids.len() == limit {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.ids.push(doc_id);
|
||||
}
|
||||
} else if anchor_offset >= 0 {
|
||||
if !anchor_found {
|
||||
if &doc_id != anchor.as_ref().unwrap() {
|
||||
continue 'outer;
|
||||
}
|
||||
anchor_found = true;
|
||||
}
|
||||
|
||||
if anchor_offset > 0 {
|
||||
anchor_offset -= 1;
|
||||
} else {
|
||||
result.ids.push(doc_id);
|
||||
if limit > 0 && result.ids.len() == limit {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
anchor_found = &doc_id == anchor.as_ref().unwrap();
|
||||
result.ids.push(doc_id);
|
||||
|
||||
if !anchor_found {
|
||||
continue 'outer;
|
||||
}
|
||||
|
||||
position = anchor_offset;
|
||||
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
|
||||
if !has_anchor || anchor_found {
|
||||
if !has_anchor && requested_position >= 0 {
|
||||
result.position = if position == 0 { requested_position } else { 0 };
|
||||
} else if position >= 0 {
|
||||
result.position = position;
|
||||
} else {
|
||||
let position = position.unsigned_abs() as usize;
|
||||
let start_offset = if position < result.ids.len() {
|
||||
result.ids.len() - position
|
||||
} else {
|
||||
0
|
||||
};
|
||||
result.position = start_offset as i32;
|
||||
let end_offset = if limit > 0 {
|
||||
std::cmp::min(start_offset + limit, result.ids.len())
|
||||
} else {
|
||||
result.ids.len()
|
||||
};
|
||||
|
||||
result.ids = result.ids[start_offset..end_offset].to_vec()
|
||||
}
|
||||
} else {
|
||||
result.found_anchor = false;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user