qdrant_edge/shard/grouping/
mod.rs1pub mod aggregator;
6mod driver;
7
8use ahash::AHashMap;
9use fnv::FnvBuildHasher;
10use indexmap::IndexSet;
11use crate::segment::data_types::groups::GroupId;
12use crate::segment::json_path::JsonPath;
13use crate::segment::types::{
14 AnyVariants, Condition, FieldCondition, Filter, Match, PointIdType, ScoredPoint,
15 WithPayloadInterface,
16};
17use serde_json::Value;
18
19pub use self::aggregator::GroupsAggregator;
20pub use self::driver::{GroupByDriver, RequestBudget};
21use crate::shard::query::{ShardPrefetch, ShardQueryRequest};
22
23#[derive(PartialEq, Debug)]
24pub enum AggregatorError {
25 BadKeyType,
26 KeyNotFound,
27}
28
29#[derive(Debug, Clone)]
31pub struct Group {
32 pub hits: Vec<ScoredPoint>,
33 pub key: GroupId,
34}
35
36impl Group {
37 pub fn hydrate_from(&mut self, map: &AHashMap<PointIdType, ScoredPoint>) {
38 self.hits.iter_mut().for_each(|hit| {
39 if let Some(point) = map.get(&hit.id) {
40 hit.payload.clone_from(&point.payload);
41 hit.vector.clone_from(&point.vector);
42 }
43 });
44 }
45}
46
47fn group_by_to_payload_selector(group_by: &JsonPath) -> WithPayloadInterface {
49 WithPayloadInterface::Fields(vec![group_by.strip_wildcard_suffix()])
50}
51
52fn merge_filter(target: &mut Option<Filter>, extra: Filter) {
54 *target = Some(match target.take() {
55 Some(filter) => filter.merge_owned(extra),
56 None => extra,
57 });
58}
59
60fn shape_candidates_query(
65 query: &mut ShardQueryRequest,
66 group_by: &JsonPath,
67 limit: usize,
68 group_size: usize,
69) {
70 query.limit = limit;
71 query.offset = 0;
72 query
73 .prefetches
74 .iter_mut()
75 .for_each(|prefetch| increase_limit_for_group(prefetch, group_size));
76
77 let key_not_empty = Filter::new_must_not(Condition::IsEmpty(group_by.clone().into()));
78 merge_filter(&mut query.filter, key_not_empty);
79
80 query.with_payload = group_by_to_payload_selector(group_by);
81}
82
83fn increase_limit_for_group(shard_prefetch: &mut ShardPrefetch, group_size: usize) {
84 shard_prefetch.limit *= group_size;
85 shard_prefetch.prefetches.iter_mut().for_each(|prefetch| {
86 increase_limit_for_group(prefetch, group_size);
87 });
88}
89
90fn except_on(path: &JsonPath, values: &[Value]) -> Vec<Condition> {
92 values_to_any_variants(values)
93 .into_iter()
94 .map(|v| {
95 Condition::Field(FieldCondition::new_match(
96 path.clone(),
97 Match::new_except(v),
98 ))
99 })
100 .collect()
101}
102
103fn match_on(path: &JsonPath, values: &[Value]) -> Vec<Condition> {
105 values_to_any_variants(values)
106 .into_iter()
107 .map(|any_variants| {
108 Condition::Field(FieldCondition::new_match(
109 path.clone(),
110 Match::new_any(any_variants),
111 ))
112 })
113 .collect()
114}
115
116fn values_to_any_variants(values: &[Value]) -> Vec<AnyVariants> {
117 let mut any_variants = Vec::new();
118
119 let ints: IndexSet<_, FnvBuildHasher> = values.iter().filter_map(|v| v.as_i64()).collect();
121
122 if !ints.is_empty() {
123 any_variants.push(AnyVariants::Integers(ints));
124 }
125
126 let strs: IndexSet<_, FnvBuildHasher> = values
128 .iter()
129 .filter_map(|v| v.as_str().map(Into::into))
130 .collect();
131
132 if !strs.is_empty() {
133 any_variants.push(AnyVariants::Strings(strs));
134 }
135
136 any_variants
137}