Skip to main content

cynos_query/executor/
relation.rs

1//! Relation and RelationEntry types for query execution.
2
3use alloc::rc::Rc;
4use alloc::string::String;
5use alloc::sync::Arc;
6use alloc::vec::Vec;
7use cynos_core::{Row, RowId, Value};
8
9/// Shared table names to avoid repeated cloning during joins.
10pub type SharedTables = Arc<[String]>;
11
12/// Internal storage for table names - either owned or shared.
13#[derive(Clone, Debug)]
14enum TablesStorage {
15    /// Owned vector (for single-table entries).
16    Owned(Vec<String>),
17    /// Shared via Arc (for join results).
18    Shared(SharedTables),
19}
20
21impl TablesStorage {
22    #[inline]
23    fn as_slice(&self) -> &[String] {
24        match self {
25            TablesStorage::Owned(v) => v,
26            TablesStorage::Shared(arc) => arc,
27        }
28    }
29
30    #[inline]
31    #[allow(dead_code)]
32    fn to_vec(&self) -> Vec<String> {
33        match self {
34            TablesStorage::Owned(v) => v.clone(),
35            TablesStorage::Shared(arc) => arc.to_vec(),
36        }
37    }
38
39    #[inline]
40    #[allow(dead_code)]
41    fn len(&self) -> usize {
42        match self {
43            TablesStorage::Owned(v) => v.len(),
44            TablesStorage::Shared(arc) => arc.len(),
45        }
46    }
47}
48
49/// A relation entry wraps a row with table context.
50#[derive(Clone, Debug)]
51pub struct RelationEntry {
52    /// The underlying row (reference counted for efficient sharing).
53    pub row: Rc<Row>,
54    /// Whether this entry is from a joined relation.
55    pub is_combined: bool,
56    /// Table names this entry belongs to.
57    tables: TablesStorage,
58}
59
60impl RelationEntry {
61    /// Creates a relation entry with shared tables (avoids cloning for each row).
62    #[inline]
63    pub fn new_shared(row: Rc<Row>, shared_tables: SharedTables) -> Self {
64        Self {
65            row,
66            is_combined: shared_tables.len() > 1,
67            tables: TablesStorage::Shared(shared_tables),
68        }
69    }
70
71    /// Creates a new relation entry.
72    pub fn new(row: Rc<Row>, tables: Vec<String>) -> Self {
73        Self {
74            row,
75            is_combined: tables.len() > 1,
76            tables: TablesStorage::Owned(tables),
77        }
78    }
79
80    /// Creates a new relation entry from an owned Row.
81    pub fn new_owned(row: Row, tables: Vec<String>) -> Self {
82        Self {
83            row: Rc::new(row),
84            is_combined: tables.len() > 1,
85            tables: TablesStorage::Owned(tables),
86        }
87    }
88
89    /// Creates a combined relation entry with shared tables (for join results).
90    /// This avoids cloning the tables vector for each result row.
91    #[inline]
92    pub fn new_combined(row: Rc<Row>, shared_tables: SharedTables) -> Self {
93        Self {
94            row,
95            is_combined: true,
96            tables: TablesStorage::Shared(shared_tables),
97        }
98    }
99
100    /// Creates a relation entry from a single table.
101    pub fn from_row(row: Rc<Row>, table: impl Into<String>) -> Self {
102        Self {
103            row,
104            is_combined: false,
105            tables: TablesStorage::Owned(alloc::vec![table.into()]),
106        }
107    }
108
109    /// Creates a relation entry from an owned Row and a single table.
110    pub fn from_row_owned(row: Row, table: impl Into<String>) -> Self {
111        Self {
112            row: Rc::new(row),
113            is_combined: false,
114            tables: TablesStorage::Owned(alloc::vec![table.into()]),
115        }
116    }
117
118    /// Returns the row ID.
119    pub fn id(&self) -> RowId {
120        self.row.id()
121    }
122
123    /// Returns the tables this entry belongs to.
124    pub fn tables(&self) -> &[String] {
125        self.tables.as_slice()
126    }
127
128    /// Gets a field value by column index.
129    pub fn get_field(&self, index: usize) -> Option<&Value> {
130        self.row.get(index)
131    }
132
133    /// Combines two entries into a joined entry.
134    /// The combined row's version is the sum of both source row versions.
135    pub fn combine(
136        left: &RelationEntry,
137        left_tables: &[String],
138        right: &RelationEntry,
139        right_tables: &[String],
140    ) -> Self {
141        let left_values = left.row.values();
142        let right_values = right.row.values();
143        let total_len = left_values.len() + right_values.len();
144
145        let mut values = Vec::with_capacity(total_len);
146        values.extend(left_values.iter().cloned());
147        values.extend(right_values.iter().cloned());
148
149        let mut tables = Vec::with_capacity(left_tables.len() + right_tables.len());
150        tables.extend(left_tables.iter().cloned());
151        tables.extend(right_tables.iter().cloned());
152
153        // Sum version for JOIN result
154        let combined_version = left.row.version().wrapping_add(right.row.version());
155
156        Self {
157            row: Rc::new(Row::dummy_with_version(combined_version, values)),
158            is_combined: true,
159            tables: TablesStorage::Owned(tables),
160        }
161    }
162
163    /// Creates a combined entry with null values for the right side (for outer joins).
164    /// The combined row's version is the left row's version (right side is NULL).
165    pub fn combine_with_null(
166        left: &RelationEntry,
167        left_tables: &[String],
168        right_column_count: usize,
169        right_tables: &[String],
170    ) -> Self {
171        let left_values = left.row.values();
172        let total_len = left_values.len() + right_column_count;
173
174        let mut values = Vec::with_capacity(total_len);
175        values.extend(left_values.iter().cloned());
176        values.resize(total_len, Value::Null);
177
178        let mut tables = Vec::with_capacity(left_tables.len() + right_tables.len());
179        tables.extend(left_tables.iter().cloned());
180        tables.extend(right_tables.iter().cloned());
181
182        // For outer join unmatched rows, use left's version
183        let combined_version = left.row.version();
184
185        Self {
186            row: Rc::new(Row::dummy_with_version(combined_version, values)),
187            is_combined: true,
188            tables: TablesStorage::Owned(tables),
189        }
190    }
191
192    /// Fast combine that reuses pre-computed tables (avoids repeated table name cloning).
193    /// The combined row's version is the sum of both source row versions.
194    #[inline]
195    pub fn combine_fast(
196        left: &RelationEntry,
197        right: &RelationEntry,
198        combined_tables: SharedTables,
199    ) -> Self {
200        let left_values = left.row.values();
201        let right_values = right.row.values();
202        let total_len = left_values.len() + right_values.len();
203
204        let mut values = Vec::with_capacity(total_len);
205        values.extend(left_values.iter().cloned());
206        values.extend(right_values.iter().cloned());
207
208        // Sum version for JOIN result
209        let combined_version = left.row.version().wrapping_add(right.row.version());
210
211        Self {
212            row: Rc::new(Row::dummy_with_version(combined_version, values)),
213            is_combined: true,
214            tables: TablesStorage::Shared(combined_tables),
215        }
216    }
217
218    /// Fast combine with null that reuses pre-computed tables.
219    /// The combined row's version is the left row's version (right side is NULL).
220    #[inline]
221    pub fn combine_with_null_fast(
222        left: &RelationEntry,
223        right_column_count: usize,
224        combined_tables: SharedTables,
225    ) -> Self {
226        let left_values = left.row.values();
227        let total_len = left_values.len() + right_column_count;
228
229        let mut values = Vec::with_capacity(total_len);
230        values.extend(left_values.iter().cloned());
231        values.resize(total_len, Value::Null);
232
233        // For outer join unmatched rows, use left's version
234        let combined_version = left.row.version();
235
236        Self {
237            row: Rc::new(Row::dummy_with_version(combined_version, values)),
238            is_combined: true,
239            tables: TablesStorage::Shared(combined_tables),
240        }
241    }
242}
243
244/// A relation is a collection of entries with table context.
245#[derive(Clone, Debug)]
246pub struct Relation {
247    /// The entries in this relation.
248    pub entries: Vec<RelationEntry>,
249    /// Table names in this relation.
250    pub tables: Vec<String>,
251    /// Column counts for each table (used for computing offsets in joined relations).
252    /// The i-th element is the number of columns in the i-th table.
253    pub table_column_counts: Vec<usize>,
254}
255
256impl Relation {
257    /// Creates a new empty relation.
258    pub fn new(tables: Vec<String>) -> Self {
259        let table_count = tables.len();
260        Self {
261            entries: Vec::new(),
262            tables,
263            table_column_counts: alloc::vec![0; table_count],
264        }
265    }
266
267    /// Creates a new empty relation with column counts.
268    pub fn new_with_column_counts(tables: Vec<String>, column_counts: Vec<usize>) -> Self {
269        Self {
270            entries: Vec::new(),
271            tables,
272            table_column_counts: column_counts,
273        }
274    }
275
276    /// Creates an empty relation.
277    pub fn empty() -> Self {
278        Self {
279            entries: Vec::new(),
280            tables: Vec::new(),
281            table_column_counts: Vec::new(),
282        }
283    }
284
285    /// Creates a relation from Rc<Row>s.
286    /// Uses shared tables to avoid cloning for each row.
287    pub fn from_rows(rows: Vec<Rc<Row>>, tables: Vec<String>) -> Self {
288        let shared_tables: SharedTables = Arc::from(tables.as_slice());
289        // Infer column count from first row
290        let column_count = rows.first().map(|r| r.len()).unwrap_or(0);
291        let table_column_counts = if tables.len() == 1 {
292            alloc::vec![column_count]
293        } else {
294            alloc::vec![0; tables.len()]
295        };
296        let entries = rows
297            .into_iter()
298            .map(|row| RelationEntry::new_shared(row, shared_tables.clone()))
299            .collect();
300        Self { entries, tables, table_column_counts }
301    }
302
303    /// Creates a relation from Rc<Row>s with explicit column count.
304    pub fn from_rows_with_column_count(rows: Vec<Rc<Row>>, tables: Vec<String>, column_count: usize) -> Self {
305        let shared_tables: SharedTables = Arc::from(tables.as_slice());
306        let table_column_counts = alloc::vec![column_count];
307        let entries = rows
308            .into_iter()
309            .map(|row| RelationEntry::new_shared(row, shared_tables.clone()))
310            .collect();
311        Self { entries, tables, table_column_counts }
312    }
313
314    /// Creates a relation from owned Rows.
315    /// Uses shared tables to avoid cloning for each row.
316    pub fn from_rows_owned(rows: Vec<Row>, tables: Vec<String>) -> Self {
317        let shared_tables: SharedTables = Arc::from(tables.as_slice());
318        // Infer column count from first row
319        let column_count = rows.first().map(|r| r.len()).unwrap_or(0);
320        let table_column_counts = if tables.len() == 1 {
321            alloc::vec![column_count]
322        } else {
323            alloc::vec![0; tables.len()]
324        };
325        let entries = rows
326            .into_iter()
327            .map(|row| RelationEntry {
328                row: Rc::new(row),
329                is_combined: shared_tables.len() > 1,
330                tables: TablesStorage::Shared(shared_tables.clone()),
331            })
332            .collect();
333        Self { entries, tables, table_column_counts }
334    }
335
336    /// Returns the tables in this relation.
337    pub fn tables(&self) -> &[String] {
338        &self.tables
339    }
340
341    /// Returns the column counts for each table.
342    pub fn table_column_counts(&self) -> &[usize] {
343        &self.table_column_counts
344    }
345
346    /// Computes the column offset for a given table name.
347    /// Returns None if the table is not found.
348    pub fn get_table_offset(&self, table_name: &str) -> Option<usize> {
349        let mut offset = 0;
350        for (i, t) in self.tables.iter().enumerate() {
351            if t == table_name {
352                return Some(offset);
353            }
354            offset += self.table_column_counts.get(i).copied().unwrap_or(0);
355        }
356        None
357    }
358
359    /// Returns the number of entries.
360    pub fn len(&self) -> usize {
361        self.entries.len()
362    }
363
364    /// Returns true if the relation is empty.
365    pub fn is_empty(&self) -> bool {
366        self.entries.is_empty()
367    }
368
369    /// Adds an entry to the relation.
370    pub fn push(&mut self, entry: RelationEntry) {
371        self.entries.push(entry);
372    }
373
374    /// Returns an iterator over the entries.
375    pub fn iter(&self) -> impl Iterator<Item = &RelationEntry> {
376        self.entries.iter()
377    }
378}
379
380impl IntoIterator for Relation {
381    type Item = RelationEntry;
382    type IntoIter = alloc::vec::IntoIter<RelationEntry>;
383
384    fn into_iter(self) -> Self::IntoIter {
385        self.entries.into_iter()
386    }
387}
388
389#[cfg(test)]
390mod tests {
391    use super::*;
392    use alloc::vec;
393
394    #[test]
395    fn test_relation_entry() {
396        let row = Rc::new(Row::new(1, vec![Value::Int64(42), Value::String("test".into())]));
397        let entry = RelationEntry::from_row(row, "users");
398
399        assert_eq!(entry.id(), 1);
400        assert_eq!(entry.tables(), &["users"]);
401        assert_eq!(entry.get_field(0), Some(&Value::Int64(42)));
402        assert!(!entry.is_combined);
403    }
404
405    #[test]
406    fn test_relation_entry_combine() {
407        let left_row = Rc::new(Row::new(1, vec![Value::Int64(1)]));
408        let right_row = Rc::new(Row::new(2, vec![Value::Int64(2)]));
409
410        let left_entry = RelationEntry::from_row(left_row, "a");
411        let right_entry = RelationEntry::from_row(right_row, "b");
412
413        let combined = RelationEntry::combine(
414            &left_entry,
415            &["a".into()],
416            &right_entry,
417            &["b".into()],
418        );
419
420        assert!(combined.is_combined);
421        assert_eq!(combined.tables(), &["a", "b"]);
422        assert_eq!(combined.get_field(0), Some(&Value::Int64(1)));
423        assert_eq!(combined.get_field(1), Some(&Value::Int64(2)));
424    }
425
426    #[test]
427    fn test_relation_from_rows() {
428        let rows = vec![
429            Rc::new(Row::new(1, vec![Value::Int64(1)])),
430            Rc::new(Row::new(2, vec![Value::Int64(2)])),
431        ];
432        let relation = Relation::from_rows(rows, vec!["users".into()]);
433
434        assert_eq!(relation.len(), 2);
435        assert_eq!(relation.tables(), &["users"]);
436    }
437}