akar_processor/physical/order_aggregate/
topk.rs1use crate::physical::common::{take_global_rows, value_cmp};
3use crate::physical::types::{OperatorResult, PhysicalOperatorExec};
4use akar_common::types::Value;
5use akar_common::vector::DataChunk;
6use std::collections::BinaryHeap;
7
8pub struct PhysicalTopK {
17 pub sort_keys: Vec<(u32, bool)>,
18 pub limit: u64,
19 pub offset: u64,
20}
21
22#[derive(Debug, Clone)]
24enum DirectedSortKey {
25 Asc(Value),
26 Desc(Value),
27}
28
29impl Eq for DirectedSortKey {}
30impl PartialEq for DirectedSortKey {
31 fn eq(&self, other: &Self) -> bool {
32 self.cmp(other) == std::cmp::Ordering::Equal
33 }
34}
35impl PartialOrd for DirectedSortKey {
36 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
37 Some(self.cmp(other))
38 }
39}
40impl Ord for DirectedSortKey {
41 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
42 match (self, other) {
43 (DirectedSortKey::Asc(a), DirectedSortKey::Asc(b)) => value_cmp(a, b),
44 (DirectedSortKey::Desc(a), DirectedSortKey::Desc(b)) => value_cmp(b, a),
45 _ => std::cmp::Ordering::Equal,
46 }
47 }
48}
49
50#[derive(Debug, Clone)]
51struct TopKHeapEntry {
52 sort_key: Vec<DirectedSortKey>,
53 row_idx: usize,
54}
55
56impl Eq for TopKHeapEntry {}
57impl PartialEq for TopKHeapEntry {
58 fn eq(&self, other: &Self) -> bool {
59 self.sort_key == other.sort_key
60 }
61}
62impl PartialOrd for TopKHeapEntry {
63 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
64 Some(self.cmp(other))
65 }
66}
67impl Ord for TopKHeapEntry {
68 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
69 for (a, b) in self.sort_key.iter().zip(other.sort_key.iter()) {
70 let cmp = a.cmp(b);
71 if cmp != std::cmp::Ordering::Equal {
72 return cmp;
73 }
74 }
75 std::cmp::Ordering::Equal
76 }
77}
78
79impl PhysicalOperatorExec for PhysicalTopK {
80 fn operator_type(&self) -> &str {
81 "top_k"
82 }
83
84 fn execute(&self, input: Vec<DataChunk>) -> OperatorResult {
85 let capacity = (self.limit + self.offset) as usize;
86 if capacity == 0 || input.is_empty() {
87 return Ok(Vec::new());
88 }
89
90 let total_rows: usize = input.iter().map(|c| c.size).sum();
91 if total_rows == 0 {
92 return Ok(Vec::new());
93 }
94
95 let num_fields = input[0].num_fields();
96
97 let mut all_values: Vec<Vec<(Value, bool)>> = (0..num_fields).map(|_| Vec::with_capacity(total_rows)).collect();
99 for chunk in &input {
100 for row in 0..chunk.size {
101 for col in 0..num_fields {
102 if let Some(field) = chunk.fields.get(col) {
103 let val = chunk.get_value(col, row).unwrap_or(Value::Null);
104 let is_null = field.is_null(row);
105 all_values[col].push((val, is_null));
106 }
107 }
108 }
109 }
110
111 let mut heap: BinaryHeap<TopKHeapEntry> = BinaryHeap::with_capacity(capacity.min(total_rows) + 1);
113
114 for row_idx in 0..total_rows {
115 let sort_key: Vec<DirectedSortKey> = self
116 .sort_keys
117 .iter()
118 .map(|&(col, asc)| {
119 let val = if col as usize >= num_fields {
120 Value::Null
121 } else {
122 all_values[col as usize][row_idx].0.clone()
123 };
124 if asc {
125 DirectedSortKey::Asc(val)
126 } else {
127 DirectedSortKey::Desc(val)
128 }
129 })
130 .collect();
131
132 heap.push(TopKHeapEntry { sort_key, row_idx });
133 if heap.len() > capacity {
134 heap.pop();
135 }
136 }
137
138 let sorted: Vec<TopKHeapEntry> = heap.into_sorted_vec();
140
141 let start = (self.offset as usize).min(sorted.len());
143 let end = (start + self.limit as usize).min(sorted.len());
144 let entries = &sorted[start..end];
145
146 if entries.is_empty() {
147 return Ok(Vec::new());
148 }
149
150 let field_names = input[0].field_names.clone();
152
153 let global_indices: Vec<usize> = entries.iter().map(|e| e.row_idx).collect();
156 let chunk_size = 100usize;
157 let mut output = Vec::new();
158 for chunk_start in (0..global_indices.len()).step_by(chunk_size) {
159 let chunk_end = (chunk_start + chunk_size).min(global_indices.len());
160 output.push(take_global_rows(
161 &input,
162 &global_indices[chunk_start..chunk_end],
163 field_names.clone(),
164 )?);
165 }
166
167 Ok(output)
168 }
169}