mirror of
https://github.com/qdrant/qdrant.git
synced 2026-09-21 13:37:46 -05:00
OptimizedFilter: Option<Vec<T>> -> Vec<T> (#9555)
This commit is contained in:
@@ -12,20 +12,16 @@ pub enum OptimizedCondition<'a> {
|
||||
Filter(OptimizedFilter<'a>),
|
||||
}
|
||||
|
||||
pub struct OptimizedMinShould<'a> {
|
||||
pub conditions: Vec<OptimizedCondition<'a>>,
|
||||
pub min_count: usize,
|
||||
}
|
||||
|
||||
pub struct OptimizedFilter<'a> {
|
||||
/// At least one of those conditions should match
|
||||
pub should: Option<Vec<OptimizedCondition<'a>>>,
|
||||
/// At least one of those conditions should match, if not empty.
|
||||
pub should: Vec<OptimizedCondition<'a>>,
|
||||
/// At least minimum amount of given conditions should match
|
||||
pub min_should: Option<OptimizedMinShould<'a>>,
|
||||
pub min_should: Vec<OptimizedCondition<'a>>,
|
||||
pub min_should_count: usize,
|
||||
/// All conditions must match
|
||||
pub must: Option<Vec<OptimizedCondition<'a>>>,
|
||||
pub must: Vec<OptimizedCondition<'a>>,
|
||||
/// All conditions must NOT match
|
||||
pub must_not: Option<Vec<OptimizedCondition<'a>>>,
|
||||
pub must_not: Vec<OptimizedCondition<'a>>,
|
||||
}
|
||||
|
||||
impl ConditionChecker for OptimizedFilter<'_> {
|
||||
@@ -35,13 +31,14 @@ impl ConditionChecker for OptimizedFilter<'_> {
|
||||
let OptimizedFilter {
|
||||
should,
|
||||
min_should,
|
||||
min_should_count,
|
||||
must,
|
||||
must_not,
|
||||
} = self;
|
||||
|
||||
// `should`: at least one matches.
|
||||
if let Some(conditions) = should
|
||||
&& !conditions
|
||||
// `should`: at least one matches, if not empty.
|
||||
if !should.is_empty()
|
||||
&& !should
|
||||
.iter()
|
||||
.try_any(|condition| condition.check(point_id))?
|
||||
{
|
||||
@@ -49,41 +46,29 @@ impl ConditionChecker for OptimizedFilter<'_> {
|
||||
}
|
||||
|
||||
// `min_should`: at least `min_count` match.
|
||||
if let Some(min_should) = min_should {
|
||||
let OptimizedMinShould {
|
||||
conditions,
|
||||
min_count,
|
||||
} = min_should;
|
||||
let mut matched = 0;
|
||||
|
||||
for condition in conditions {
|
||||
if condition.check(point_id)? {
|
||||
matched += 1;
|
||||
if matched == *min_count {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if matched < *min_count {
|
||||
let mut remaining = *min_should_count;
|
||||
let mut min_should_iter = min_should.iter();
|
||||
while remaining > 0 {
|
||||
let Some(condition) = min_should_iter.next() else {
|
||||
// Not enough conditions to match `min_count`
|
||||
return Ok(false);
|
||||
};
|
||||
if condition.check(point_id)? {
|
||||
remaining -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// `must`: all match.
|
||||
if let Some(conditions) = must {
|
||||
for condition in conditions {
|
||||
if !condition.check(point_id)? {
|
||||
return Ok(false);
|
||||
}
|
||||
for condition in must {
|
||||
if !condition.check(point_id)? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
// `must_not`: none match.
|
||||
if let Some(conditions) = must_not {
|
||||
for condition in conditions {
|
||||
if condition.check(point_id)? {
|
||||
return Ok(false);
|
||||
}
|
||||
for condition in must_not {
|
||||
if condition.check(point_id)? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,7 @@ use crate::index::query_estimator::{
|
||||
combine_min_should_estimations, combine_must_estimations, combine_should_estimations,
|
||||
invert_estimation,
|
||||
};
|
||||
use crate::index::query_optimization::optimized_filter::{
|
||||
OptimizedCondition, OptimizedFilter, OptimizedMinShould,
|
||||
};
|
||||
use crate::index::query_optimization::optimized_filter::{OptimizedCondition, OptimizedFilter};
|
||||
use crate::index::query_optimization::payload_provider::PayloadProvider;
|
||||
use crate::payload_storage::PayloadStorageRead;
|
||||
use crate::types::{Condition, Filter, MinShould};
|
||||
@@ -55,78 +53,63 @@ where
|
||||
hw_counter: &HardwareCounterCell,
|
||||
) -> OperationResult<(OptimizedFilter<'b>, CardinalityEstimation)> {
|
||||
let mut filter_estimations: Vec<CardinalityEstimation> = vec![];
|
||||
let Filter {
|
||||
should,
|
||||
min_should,
|
||||
must,
|
||||
must_not,
|
||||
} = filter;
|
||||
|
||||
let optimized_filter = OptimizedFilter {
|
||||
should: if let Some(conditions) = filter.should.as_ref()
|
||||
&& !conditions.is_empty()
|
||||
{
|
||||
let (optimized_conditions, estimation) = self.optimize_should(
|
||||
conditions,
|
||||
payload_provider.clone(),
|
||||
total,
|
||||
deferred_behavior,
|
||||
hw_counter,
|
||||
)?;
|
||||
filter_estimations.push(estimation);
|
||||
Some(optimized_conditions)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
// Keep an empty `min_should` when `min_count > 0`: it's
|
||||
// unsatisfiable (match-none), and dropping it would match all
|
||||
// (issue #9369). Only the no-op (empty, `min_count == 0`) is dropped.
|
||||
min_should: if let Some(MinShould {
|
||||
let (should, estimation) = self.optimize_should(
|
||||
should.as_deref().unwrap_or(&[]),
|
||||
payload_provider.clone(),
|
||||
total,
|
||||
deferred_behavior,
|
||||
hw_counter,
|
||||
)?;
|
||||
filter_estimations.push(estimation);
|
||||
|
||||
let (min_should, min_should_count) = match min_should.as_ref() {
|
||||
Some(MinShould {
|
||||
conditions,
|
||||
min_count,
|
||||
}) = filter.min_should.as_ref()
|
||||
&& (!conditions.is_empty() || *min_count > 0)
|
||||
{
|
||||
let (optimized_conditions, estimation) = self.optimize_min_should(
|
||||
conditions,
|
||||
*min_count,
|
||||
payload_provider.clone(),
|
||||
total,
|
||||
deferred_behavior,
|
||||
hw_counter,
|
||||
)?;
|
||||
filter_estimations.push(estimation);
|
||||
Some(OptimizedMinShould {
|
||||
conditions: optimized_conditions,
|
||||
min_count: *min_count,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
},
|
||||
must: if let Some(conditions) = filter.must.as_ref()
|
||||
&& !conditions.is_empty()
|
||||
{
|
||||
let (optimized_conditions, estimation) = self.optimize_must(
|
||||
conditions,
|
||||
payload_provider.clone(),
|
||||
total,
|
||||
deferred_behavior,
|
||||
hw_counter,
|
||||
)?;
|
||||
filter_estimations.push(estimation);
|
||||
Some(optimized_conditions)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
must_not: if let Some(conditions) = filter.must_not.as_ref()
|
||||
&& !conditions.is_empty()
|
||||
{
|
||||
let (optimized_conditions, estimation) = self.optimize_must_not(
|
||||
conditions,
|
||||
payload_provider,
|
||||
total,
|
||||
deferred_behavior,
|
||||
hw_counter,
|
||||
)?;
|
||||
filter_estimations.push(estimation);
|
||||
Some(optimized_conditions)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}) => (conditions.as_slice(), *min_count),
|
||||
None => (&[][..], 0),
|
||||
};
|
||||
let (min_should, estimation) = self.optimize_min_should(
|
||||
min_should,
|
||||
min_should_count,
|
||||
payload_provider.clone(),
|
||||
total,
|
||||
deferred_behavior,
|
||||
hw_counter,
|
||||
)?;
|
||||
filter_estimations.push(estimation);
|
||||
|
||||
let (must, estimation) = self.optimize_must(
|
||||
must.as_deref().unwrap_or(&[]),
|
||||
payload_provider.clone(),
|
||||
total,
|
||||
deferred_behavior,
|
||||
hw_counter,
|
||||
)?;
|
||||
filter_estimations.push(estimation);
|
||||
|
||||
let (must_not, estimation) = self.optimize_must_not(
|
||||
must_not.as_deref().unwrap_or(&[]),
|
||||
payload_provider,
|
||||
total,
|
||||
deferred_behavior,
|
||||
hw_counter,
|
||||
)?;
|
||||
filter_estimations.push(estimation);
|
||||
|
||||
let optimized_filter = OptimizedFilter {
|
||||
should,
|
||||
min_should,
|
||||
min_should_count,
|
||||
must,
|
||||
must_not,
|
||||
};
|
||||
|
||||
Ok((
|
||||
@@ -185,6 +168,11 @@ where
|
||||
deferred_behavior: DeferredBehavior,
|
||||
hw_counter: &HardwareCounterCell,
|
||||
) -> OperationResult<(Vec<OptimizedCondition<'b>>, CardinalityEstimation)> {
|
||||
if conditions.is_empty() {
|
||||
// Empty `should` => match every point.
|
||||
return Ok((Vec::new(), CardinalityEstimation::exact(total)));
|
||||
}
|
||||
|
||||
let mut converted = self.convert_conditions(
|
||||
conditions,
|
||||
payload_provider,
|
||||
|
||||
Reference in New Issue
Block a user