Internal search: Return all document ids when no filters are provided
This commit is contained in:
@@ -53,214 +53,223 @@ impl Store {
|
|||||||
.details("Account ID must be specified before other filters"));
|
.details("Account ID must be specified before other filters"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "test_mode")]
|
let mut results;
|
||||||
{
|
|
||||||
if query.filters.len() == 1 {
|
|
||||||
state.bm = Some(mask.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut filters = query.filters.into_iter().peekable();
|
if query.filters.len() > 1 {
|
||||||
while let Some(filter) = filters.next() {
|
let mut filters = query.filters.into_iter().peekable();
|
||||||
let mut result = match filter {
|
while let Some(filter) = filters.next() {
|
||||||
SearchFilter::Operator { field, op, value } => {
|
let mut result = match filter {
|
||||||
if matches!(field, SearchField::AccountId) {
|
SearchFilter::Operator { field, op, value } => {
|
||||||
continue;
|
if matches!(field, SearchField::AccountId) {
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if field.is_text()
|
if field.is_text()
|
||||||
&& matches!(op, SearchOperator::Contains | SearchOperator::Equal)
|
&& matches!(op, SearchOperator::Contains | SearchOperator::Equal)
|
||||||
{
|
{
|
||||||
let (value, language) = match value {
|
let (value, language) = match value {
|
||||||
SearchValue::Text { value, language } => (value, language),
|
SearchValue::Text { value, language } => (value, language),
|
||||||
_ => {
|
_ => {
|
||||||
return Err(trc::StoreEvent::UnexpectedError
|
return Err(trc::StoreEvent::UnexpectedError
|
||||||
.into_err()
|
.into_err()
|
||||||
.details("Expected text value for text field"));
|
.details("Expected text value for text field"));
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if op == SearchOperator::Equal {
|
|
||||||
bitmaps
|
|
||||||
.merge_bitmaps(
|
|
||||||
self,
|
|
||||||
query.index,
|
|
||||||
account_id,
|
|
||||||
language
|
|
||||||
.tokenize_text(&value, MAX_TOKEN_LENGTH)
|
|
||||||
.map(|token| CheekyHash::new(token.word.as_bytes())),
|
|
||||||
field.u8_id(),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
} else {
|
|
||||||
let mut result = RoaringBitmap::new();
|
|
||||||
for token in Stemmer::new(&value, language, MAX_TOKEN_LENGTH) {
|
|
||||||
let mut tokens = Vec::with_capacity(3);
|
|
||||||
tokens.push(CheekyHash::new(token.word.as_bytes()));
|
|
||||||
tokens.push(CheekyHash::new(format!("{}*", token.word).as_bytes()));
|
|
||||||
if let Some(stemmed_word) = token.stemmed_word {
|
|
||||||
tokens.push(CheekyHash::new(
|
|
||||||
format!("{stemmed_word}*").as_bytes(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
let union = bitmaps
|
};
|
||||||
|
|
||||||
|
if op == SearchOperator::Equal {
|
||||||
|
bitmaps
|
||||||
.merge_bitmaps(
|
.merge_bitmaps(
|
||||||
self,
|
self,
|
||||||
query.index,
|
query.index,
|
||||||
account_id,
|
account_id,
|
||||||
tokens.into_iter(),
|
language
|
||||||
|
.tokenize_text(&value, MAX_TOKEN_LENGTH)
|
||||||
|
.map(|token| CheekyHash::new(token.word.as_bytes())),
|
||||||
|
field.u8_id(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
} else {
|
||||||
|
let mut result = RoaringBitmap::new();
|
||||||
|
for token in Stemmer::new(&value, language, MAX_TOKEN_LENGTH) {
|
||||||
|
let mut tokens = Vec::with_capacity(3);
|
||||||
|
tokens.push(CheekyHash::new(token.word.as_bytes()));
|
||||||
|
tokens.push(CheekyHash::new(
|
||||||
|
format!("{}*", token.word).as_bytes(),
|
||||||
|
));
|
||||||
|
if let Some(stemmed_word) = token.stemmed_word {
|
||||||
|
tokens.push(CheekyHash::new(
|
||||||
|
format!("{stemmed_word}*").as_bytes(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let union = bitmaps
|
||||||
|
.merge_bitmaps(
|
||||||
|
self,
|
||||||
|
query.index,
|
||||||
|
account_id,
|
||||||
|
tokens.into_iter(),
|
||||||
|
field.u8_id(),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
if let Some(union) = union {
|
||||||
|
if result.is_empty() {
|
||||||
|
result = union;
|
||||||
|
} else {
|
||||||
|
result.bitand_assign(&union);
|
||||||
|
if result.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !result.is_empty() {
|
||||||
|
Some(result)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if field.is_json() {
|
||||||
|
let (key, value) = match value {
|
||||||
|
SearchValue::KeyValues(kv) => kv.into_iter().next().unwrap(),
|
||||||
|
_ => {
|
||||||
|
return Err(trc::StoreEvent::UnexpectedError
|
||||||
|
.into_err()
|
||||||
|
.details("Expected text value for text field"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !value.is_empty() {
|
||||||
|
bitmaps
|
||||||
|
.merge_bitmaps(
|
||||||
|
self,
|
||||||
|
query.index,
|
||||||
|
account_id,
|
||||||
|
SpaceTokenizer::new(value.as_str(), MAX_TOKEN_LENGTH).map(
|
||||||
|
|value| {
|
||||||
|
CheekyHash::new(format!("{key} {value}").as_bytes())
|
||||||
|
},
|
||||||
|
),
|
||||||
field.u8_id(),
|
field.u8_id(),
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?
|
||||||
if let Some(union) = union {
|
|
||||||
if result.is_empty() {
|
|
||||||
result = union;
|
|
||||||
} else {
|
|
||||||
result.bitand_assign(&union);
|
|
||||||
if result.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result.clear();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !result.is_empty() {
|
|
||||||
Some(result)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
bitmaps
|
||||||
|
.merge_bitmaps(
|
||||||
|
self,
|
||||||
|
query.index,
|
||||||
|
account_id,
|
||||||
|
[CheekyHash::new(key.as_bytes())].into_iter(),
|
||||||
|
field.u8_id(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
}
|
}
|
||||||
}
|
} else if field.is_indexed() {
|
||||||
} else if field.is_json() {
|
let value = match value {
|
||||||
let (key, value) = match value {
|
SearchValue::Text { value, .. } => {
|
||||||
SearchValue::KeyValues(kv) => kv.into_iter().next().unwrap(),
|
let mut value = value.into_bytes();
|
||||||
_ => {
|
value.truncate(SEARCH_INDEX_MAX_FIELD_LEN);
|
||||||
return Err(trc::StoreEvent::UnexpectedError
|
value
|
||||||
.into_err()
|
}
|
||||||
.details("Expected text value for text field"));
|
SearchValue::Int(v) => (v as u64).to_be_bytes().to_vec(),
|
||||||
}
|
SearchValue::Uint(v) => v.to_be_bytes().to_vec(),
|
||||||
};
|
SearchValue::Boolean(v) => vec![v as u8],
|
||||||
|
SearchValue::KeyValues(_) => {
|
||||||
|
return Err(trc::StoreEvent::UnexpectedError
|
||||||
|
.into_err()
|
||||||
|
.details("Expected non key-value for non-text field"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if !value.is_empty() {
|
range_to_bitmap(
|
||||||
bitmaps
|
self,
|
||||||
.merge_bitmaps(
|
query.index,
|
||||||
self,
|
account_id,
|
||||||
query.index,
|
field.u8_id(),
|
||||||
account_id,
|
&value,
|
||||||
SpaceTokenizer::new(value.as_str(), MAX_TOKEN_LENGTH).map(
|
op,
|
||||||
|value| {
|
)
|
||||||
CheekyHash::new(format!("{key} {value}").as_bytes())
|
|
||||||
},
|
|
||||||
),
|
|
||||||
field.u8_id(),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
} else {
|
|
||||||
bitmaps
|
|
||||||
.merge_bitmaps(
|
|
||||||
self,
|
|
||||||
query.index,
|
|
||||||
account_id,
|
|
||||||
[CheekyHash::new(key.as_bytes())].into_iter(),
|
|
||||||
field.u8_id(),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
} else if field.is_indexed() {
|
|
||||||
let value = match value {
|
|
||||||
SearchValue::Text { value, .. } => {
|
|
||||||
let mut value = value.into_bytes();
|
|
||||||
value.truncate(SEARCH_INDEX_MAX_FIELD_LEN);
|
|
||||||
value
|
|
||||||
}
|
|
||||||
SearchValue::Int(v) => (v as u64).to_be_bytes().to_vec(),
|
|
||||||
SearchValue::Uint(v) => v.to_be_bytes().to_vec(),
|
|
||||||
SearchValue::Boolean(v) => vec![v as u8],
|
|
||||||
SearchValue::KeyValues(_) => {
|
|
||||||
return Err(trc::StoreEvent::UnexpectedError
|
|
||||||
.into_err()
|
|
||||||
.details("Expected non key-value for non-text field"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
range_to_bitmap(self, query.index, account_id, field.u8_id(), &value, op)
|
|
||||||
.await?
|
.await?
|
||||||
} else {
|
|
||||||
return Err(trc::StoreEvent::UnexpectedError
|
|
||||||
.into_err()
|
|
||||||
.details(format!("Field {field:?} is not indexed")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SearchFilter::DocumentSet(bitmap) => Some(bitmap),
|
|
||||||
op @ (SearchFilter::And | SearchFilter::Or | SearchFilter::Not) => {
|
|
||||||
stack.push(state);
|
|
||||||
state = State { op, bm: None };
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
SearchFilter::End => {
|
|
||||||
if let Some(prev_state) = stack.pop() {
|
|
||||||
let bm = state.bm;
|
|
||||||
state = prev_state;
|
|
||||||
bm
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Apply logical operation
|
|
||||||
if let Some(dest) = &mut state.bm {
|
|
||||||
match state.op {
|
|
||||||
SearchFilter::And => {
|
|
||||||
if let Some(result) = result {
|
|
||||||
dest.bitand_assign(result);
|
|
||||||
} else {
|
} else {
|
||||||
dest.clear();
|
return Err(trc::StoreEvent::UnexpectedError
|
||||||
|
.into_err()
|
||||||
|
.details(format!("Field {field:?} is not indexed")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SearchFilter::Or => {
|
SearchFilter::DocumentSet(bitmap) => Some(bitmap),
|
||||||
if let Some(result) = result {
|
op @ (SearchFilter::And | SearchFilter::Or | SearchFilter::Not) => {
|
||||||
dest.bitor_assign(result);
|
stack.push(state);
|
||||||
|
state = State { op, bm: None };
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
SearchFilter::End => {
|
||||||
|
if let Some(prev_state) = stack.pop() {
|
||||||
|
let bm = state.bm;
|
||||||
|
state = prev_state;
|
||||||
|
bm
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SearchFilter::Not => {
|
};
|
||||||
if let Some(mut result) = result {
|
|
||||||
result.bitxor_assign(&mask);
|
// Apply logical operation
|
||||||
dest.bitand_assign(result);
|
if let Some(dest) = &mut state.bm {
|
||||||
|
match state.op {
|
||||||
|
SearchFilter::And => {
|
||||||
|
if let Some(result) = result {
|
||||||
|
dest.bitand_assign(result);
|
||||||
|
} else {
|
||||||
|
dest.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
SearchFilter::Or => {
|
||||||
|
if let Some(result) = result {
|
||||||
|
dest.bitor_assign(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SearchFilter::Not => {
|
||||||
|
if let Some(mut result) = result {
|
||||||
|
result.bitxor_assign(&mask);
|
||||||
|
dest.bitand_assign(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
} else if let Some(result_) = &mut result {
|
||||||
|
if let SearchFilter::Not = state.op {
|
||||||
|
result_.bitxor_assign(&mask);
|
||||||
|
}
|
||||||
|
state.bm = result;
|
||||||
|
} else if let SearchFilter::Not = state.op {
|
||||||
|
state.bm = Some(mask.clone());
|
||||||
|
} else {
|
||||||
|
state.bm = Some(RoaringBitmap::new());
|
||||||
}
|
}
|
||||||
} else if let Some(result_) = &mut result {
|
|
||||||
if let SearchFilter::Not = state.op {
|
// And short circuit
|
||||||
result_.bitxor_assign(&mask);
|
if matches!(state.op, SearchFilter::And) && state.bm.as_ref().unwrap().is_empty() {
|
||||||
|
while let Some(filter) = filters.peek() {
|
||||||
|
if matches!(filter, SearchFilter::End) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
filters.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
state.bm = result;
|
|
||||||
} else if let SearchFilter::Not = state.op {
|
|
||||||
state.bm = Some(mask.clone());
|
|
||||||
} else {
|
|
||||||
state.bm = Some(RoaringBitmap::new());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// And short circuit
|
results = state.bm.unwrap_or_default();
|
||||||
if matches!(state.op, SearchFilter::And) && state.bm.as_ref().unwrap().is_empty() {
|
results.bitand_assign(&mask);
|
||||||
while let Some(filter) = filters.peek() {
|
} else {
|
||||||
if matches!(filter, SearchFilter::End) {
|
results = mask;
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
filters.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut results = state.bm.unwrap_or_default();
|
|
||||||
results.bitand_assign(&mask);
|
|
||||||
if results.len() > 1 && !query.comparators.is_empty() {
|
if results.len() > 1 && !query.comparators.is_empty() {
|
||||||
let mut comparators = Vec::with_capacity(query.comparators.len());
|
let mut comparators = Vec::with_capacity(query.comparators.len());
|
||||||
for comparator in query.comparators {
|
for comparator in query.comparators {
|
||||||
|
|||||||
Reference in New Issue
Block a user