linkrs_query/executor/streaming/operators/
intersect_build.rs1use std::collections::HashMap;
9
10use linkrs_core::Value;
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
18pub enum IntersectKey {
19 Int(i64),
20 Str(String),
21 Other(String),
22}
23
24impl From<&Value> for IntersectKey {
25 fn from(value: &Value) -> Self {
26 match value {
27 Value::SmallInt(v) => Self::Int(*v as i64),
28 Value::Int(v) => Self::Int(*v as i64),
29 Value::BigInt(v) => Self::Int(*v),
30 Value::String(s) => Self::Str(s.to_string()),
31 Value::FixedString(s) => Self::Str(s.clone()),
32 Value::Bool(v) => Self::Other(format!("bool:{v}")),
33 other => Self::Other(format!("{other:?}")),
34 }
35 }
36}
37
38#[derive(Debug, Default)]
40pub struct IntersectBuild {
41 bound_col: usize,
42 intersect_col: usize,
43 table: HashMap<IntersectKey, Vec<Vec<Value>>>,
44 sealed: bool,
45}
46
47impl IntersectBuild {
48 pub fn new(bound_col: usize, intersect_col: usize) -> Self {
51 Self {
52 bound_col,
53 intersect_col,
54 table: HashMap::new(),
55 sealed: false,
56 }
57 }
58
59 pub fn bound_col(&self) -> usize {
60 self.bound_col
61 }
62
63 pub fn intersect_col(&self) -> usize {
64 self.intersect_col
65 }
66
67 pub fn append_row(&mut self, row: Vec<Value>) {
70 let width = self.bound_col.max(self.intersect_col) + 1;
71 if row.len() < width {
72 return;
73 }
74 debug_assert!(!self.sealed, "appended after finish; resort needed");
75 let key = IntersectKey::from(&row[self.bound_col]);
76 self.table.entry(key).or_default().push(row);
77 self.sealed = false;
78 }
79
80 pub fn append_rows(&mut self, rows: &[Vec<Value>]) {
82 for row in rows {
83 self.append_row(row.clone());
84 }
85 }
86
87 pub fn finish(&mut self) {
90 for rows in self.table.values_mut() {
91 rows.sort_by(|a, b| {
92 IntersectKey::from(&a[self.intersect_col])
93 .cmp(&IntersectKey::from(&b[self.intersect_col]))
94 });
95 }
96 self.sealed = true;
97 }
98
99 pub fn lookup(&self, bound: &Value) -> &[Vec<Value>] {
102 let key = IntersectKey::from(bound);
103 self.table.get(&key).map(Vec::as_slice).unwrap_or(&[])
104 }
105
106 pub fn num_keys(&self) -> usize {
108 self.table.len()
109 }
110
111 pub fn num_rows(&self) -> usize {
113 self.table.values().map(Vec::len).sum()
114 }
115
116 pub fn is_sealed(&self) -> bool {
117 self.sealed
118 }
119
120 pub fn clear(&mut self) {
121 self.table.clear();
122 self.sealed = false;
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 fn build() -> IntersectBuild {
131 let mut b = IntersectBuild::new(0, 1);
132 b.append_rows(&[
133 vec![Value::Int(1), Value::Int(20)],
134 vec![Value::Int(1), Value::Int(10)],
135 vec![Value::Int(2), Value::Int(30)],
136 ]);
137 b.finish();
138 b
139 }
140
141 #[test]
142 fn finish_sorts_adjacency_by_intersect_column() {
143 let b = build();
144 let rows = b.lookup(&Value::Int(1));
145 assert_eq!(rows.len(), 2);
146 assert_eq!(rows[0][1], Value::Int(10));
147 assert_eq!(rows[1][1], Value::Int(20));
148 }
149
150 #[test]
151 fn missing_bound_yields_empty_slice() {
152 let b = build();
153 assert!(b.lookup(&Value::Int(99)).is_empty());
154 }
155
156 #[test]
157 fn narrow_rows_are_skipped() {
158 let mut b = IntersectBuild::new(0, 1);
159 b.append_row(vec![Value::Int(1)]);
160 b.append_row(vec![Value::Int(1), Value::Int(5)]);
161 b.finish();
162 assert_eq!(b.num_rows(), 1);
163 }
164
165 #[test]
166 fn key_ordering_groups_integers_before_strings() {
167 assert!(IntersectKey::Int(1) < IntersectKey::Int(2));
168 assert!(IntersectKey::Str("a".to_string()) < IntersectKey::Str("b".to_string()));
169 }
170}