Import upstream v0.16.22, stripped
Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
@@ -0,0 +1,337 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
SearchStore, Store,
|
||||
search::{
|
||||
IndexDocument, SearchComparator, SearchField, SearchFilter, SearchOperator, SearchQuery,
|
||||
SearchValue,
|
||||
split::{SplitFilter, split_filters},
|
||||
},
|
||||
write::SearchIndex,
|
||||
};
|
||||
use std::cmp::Ordering;
|
||||
use trc::AddContext;
|
||||
|
||||
impl SearchStore {
|
||||
pub async fn query_account(&self, query: SearchQuery) -> trc::Result<Vec<u32>> {
|
||||
// Pre-filter by mask
|
||||
if query.mask.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
// If the store does not support FTS, use the internal FTS store
|
||||
if let Some(store) = self.internal_fts() {
|
||||
return store.query_account(query).await;
|
||||
}
|
||||
|
||||
// If all filters and comparators are external, delegate to the underlying store
|
||||
let mut account_id = u32::MAX;
|
||||
let mut has_local_filters = false;
|
||||
let mut has_external_filters = false;
|
||||
for filter in &query.filters {
|
||||
match filter {
|
||||
SearchFilter::Operator {
|
||||
field: SearchField::AccountId,
|
||||
op: SearchOperator::Equal,
|
||||
value: SearchValue::Uint(id),
|
||||
} => {
|
||||
account_id = *id as u32;
|
||||
}
|
||||
SearchFilter::DocumentSet(_) => {
|
||||
has_local_filters = true;
|
||||
}
|
||||
SearchFilter::Operator { .. } => {
|
||||
has_external_filters = true;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
if account_id == u32::MAX {
|
||||
return Err(trc::StoreEvent::UnexpectedError
|
||||
.reason("Account ID filter is required for account queries")
|
||||
.caused_by(trc::location!()));
|
||||
}
|
||||
|
||||
if !has_local_filters && !has_external_filters && query.comparators.is_empty() {
|
||||
return Ok(query.mask.iter().collect());
|
||||
}
|
||||
|
||||
if !has_local_filters && query.comparators.iter().all(|c| c.is_external()) {
|
||||
return self
|
||||
.sub_query(query.index, &query.filters, &query.comparators)
|
||||
.await
|
||||
.map(|results| {
|
||||
if !results.is_empty() || has_external_filters {
|
||||
results
|
||||
.into_iter()
|
||||
.filter(|id| query.mask.contains(*id))
|
||||
.collect()
|
||||
} else {
|
||||
// Database sort is broken, return masked results
|
||||
query.mask.iter().collect()
|
||||
}
|
||||
})
|
||||
.caused_by(trc::location!());
|
||||
}
|
||||
|
||||
let filters = if has_external_filters {
|
||||
// Split filters
|
||||
let split_filters = split_filters(query.filters).ok_or_else(|| {
|
||||
trc::StoreEvent::UnexpectedError
|
||||
.reason("Invalid filter query")
|
||||
.caused_by(trc::location!())
|
||||
})?;
|
||||
|
||||
let mut filters = Vec::with_capacity(split_filters.len());
|
||||
for split_filter in split_filters {
|
||||
match split_filter {
|
||||
SplitFilter::External(external) => {
|
||||
// Execute sub-query
|
||||
filters.push(SearchFilter::DocumentSet(
|
||||
self.sub_query(query.index, &external, &[])
|
||||
.await?
|
||||
.into_iter()
|
||||
.collect(),
|
||||
));
|
||||
}
|
||||
SplitFilter::Internal(filter) => {
|
||||
filters.push(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filters
|
||||
} else {
|
||||
query.filters
|
||||
};
|
||||
|
||||
// Merge results locally
|
||||
let results = SearchQuery::new(query.index)
|
||||
.with_filters(filters)
|
||||
.with_mask(query.mask)
|
||||
.filter();
|
||||
|
||||
let total_results = results.results().len();
|
||||
match total_results.cmp(&1) {
|
||||
Ordering::Equal => Ok(vec![results.results().min().unwrap()]),
|
||||
Ordering::Less => Ok(vec![]),
|
||||
Ordering::Greater => {
|
||||
if !query.comparators.is_empty() {
|
||||
let mut local = Vec::with_capacity(query.comparators.len());
|
||||
let mut external = Vec::with_capacity(query.comparators.len());
|
||||
let mut external_first = false;
|
||||
for (pos, comparator) in query.comparators.into_iter().enumerate() {
|
||||
if comparator.is_external() {
|
||||
external.push(comparator);
|
||||
if pos == 0 {
|
||||
external_first = true;
|
||||
}
|
||||
} else {
|
||||
local.push(comparator);
|
||||
}
|
||||
}
|
||||
|
||||
if !external.is_empty() {
|
||||
let mut results = results.results().clone();
|
||||
let filters = vec![
|
||||
SearchFilter::Operator {
|
||||
field: SearchField::AccountId,
|
||||
op: SearchOperator::Equal,
|
||||
value: SearchValue::Uint(account_id as u64),
|
||||
},
|
||||
SearchFilter::Operator {
|
||||
field: SearchField::DocumentId,
|
||||
op: SearchOperator::GreaterEqualThan,
|
||||
value: SearchValue::Uint(results.min().unwrap() as u64),
|
||||
},
|
||||
SearchFilter::Operator {
|
||||
field: SearchField::DocumentId,
|
||||
op: SearchOperator::LowerEqualThan,
|
||||
value: SearchValue::Uint(results.max().unwrap() as u64),
|
||||
},
|
||||
];
|
||||
|
||||
let mut ordered_results = Vec::with_capacity(total_results as usize);
|
||||
for ordered_result in
|
||||
self.sub_query(query.index, &filters, &external).await?
|
||||
{
|
||||
if results.remove(ordered_result) {
|
||||
ordered_results.push(ordered_result);
|
||||
}
|
||||
}
|
||||
// Add any remaining results not yet in the index
|
||||
ordered_results.extend(results);
|
||||
|
||||
if local.is_empty() {
|
||||
return Ok(ordered_results);
|
||||
}
|
||||
|
||||
let comparator = SearchComparator::SortedSet {
|
||||
set: ordered_results
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(pos, id)| (id, pos as u32))
|
||||
.collect(),
|
||||
ascending: true,
|
||||
};
|
||||
|
||||
if external_first {
|
||||
local.insert(0, comparator);
|
||||
} else {
|
||||
local.push(comparator);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(results.with_comparators(local).into_sorted())
|
||||
} else {
|
||||
Ok(results.results().iter().collect())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn sub_query(
|
||||
&self,
|
||||
index: SearchIndex,
|
||||
filters: &[SearchFilter],
|
||||
sort: &[SearchComparator],
|
||||
) -> trc::Result<Vec<u32>> {
|
||||
match self {
|
||||
SearchStore::Store(store) => match store {
|
||||
#[cfg(feature = "postgres")]
|
||||
Store::PostgreSQL(store) => store.query(index, filters, sort).await,
|
||||
#[cfg(feature = "mysql")]
|
||||
Store::MySQL(store) => store.query(index, filters, sort).await,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
SearchStore::ElasticSearch(store) => store.query(index, filters, sort).await,
|
||||
SearchStore::MeiliSearch(store) => store.query(index, filters, sort).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_global(&self, query: SearchQuery) -> trc::Result<Vec<u64>> {
|
||||
match self {
|
||||
SearchStore::Store(store) => match store {
|
||||
#[cfg(feature = "postgres")]
|
||||
Store::PostgreSQL(store) => {
|
||||
store
|
||||
.query(query.index, &query.filters, &query.comparators)
|
||||
.await
|
||||
}
|
||||
#[cfg(feature = "mysql")]
|
||||
Store::MySQL(store) => {
|
||||
store
|
||||
.query(query.index, &query.filters, &query.comparators)
|
||||
.await
|
||||
}
|
||||
store => store.query_global(query).await,
|
||||
},
|
||||
SearchStore::ElasticSearch(store) => {
|
||||
store
|
||||
.query(query.index, &query.filters, &query.comparators)
|
||||
.await
|
||||
}
|
||||
SearchStore::MeiliSearch(store) => {
|
||||
store
|
||||
.query(query.index, &query.filters, &query.comparators)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn index(&self, documents: Vec<IndexDocument>) -> trc::Result<()> {
|
||||
match self {
|
||||
SearchStore::Store(store) => match store {
|
||||
#[cfg(feature = "postgres")]
|
||||
Store::PostgreSQL(store) => store.index(documents).await,
|
||||
#[cfg(feature = "mysql")]
|
||||
Store::MySQL(store) => store.index(documents).await,
|
||||
store => store.index(documents).await,
|
||||
},
|
||||
SearchStore::ElasticSearch(store) => store.index(documents).await,
|
||||
SearchStore::MeiliSearch(store) => store.index(documents).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn unindex(&self, query: SearchQuery) -> trc::Result<u64> {
|
||||
match self {
|
||||
SearchStore::Store(store) => match store {
|
||||
#[cfg(feature = "postgres")]
|
||||
Store::PostgreSQL(store) => store.unindex(query).await,
|
||||
#[cfg(feature = "mysql")]
|
||||
Store::MySQL(store) => store.unindex(query).await,
|
||||
store => store.unindex(query).await.map(|_| 0),
|
||||
},
|
||||
SearchStore::ElasticSearch(store) => store.unindex(query).await,
|
||||
SearchStore::MeiliSearch(store) => store.unindex(query).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn internal_fts(&self) -> Option<&Store> {
|
||||
match self {
|
||||
SearchStore::Store(store) => match store {
|
||||
#[cfg(feature = "postgres")]
|
||||
Store::PostgreSQL(_) => None,
|
||||
#[cfg(feature = "mysql")]
|
||||
Store::MySQL(_) => None,
|
||||
store => Some(store),
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_mysql(&self) -> bool {
|
||||
match self {
|
||||
#[cfg(feature = "mysql")]
|
||||
SearchStore::Store(Store::MySQL(_)) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_postgres(&self) -> bool {
|
||||
match self {
|
||||
#[cfg(feature = "postgres")]
|
||||
SearchStore::Store(Store::PostgreSQL(_)) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_elasticsearch(&self) -> bool {
|
||||
matches!(self, SearchStore::ElasticSearch(_))
|
||||
}
|
||||
|
||||
pub fn is_meilisearch(&self) -> bool {
|
||||
matches!(self, SearchStore::MeiliSearch(_))
|
||||
}
|
||||
|
||||
pub async fn create_indexes(&self) -> trc::Result<()> {
|
||||
match self {
|
||||
SearchStore::Store(store) => match store {
|
||||
#[cfg(feature = "postgres")]
|
||||
Store::PostgreSQL(store) => store.create_search_tables().await,
|
||||
#[cfg(feature = "mysql")]
|
||||
Store::MySQL(store) => store.create_search_tables().await,
|
||||
_ => Ok(()),
|
||||
},
|
||||
SearchStore::ElasticSearch(store) => store.create_indexes().await,
|
||||
SearchStore::MeiliSearch(store) => store.create_indexes().await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchFilter {
|
||||
pub fn is_external(&self) -> bool {
|
||||
matches!(self, SearchFilter::Operator { .. })
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchComparator {
|
||||
pub fn is_external(&self) -> bool {
|
||||
matches!(self, SearchComparator::Field { .. })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user