Skip to main content

cynos_query/
context.rs

1//! Execution context for query execution.
2
3use alloc::string::String;
4use alloc::vec::Vec;
5
6/// Index type enumeration for query optimization.
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
8pub enum QueryIndexType {
9    /// B+Tree index - O(log n) range queries.
10    #[default]
11    BTree,
12    /// GIN (Generalized Inverted Index) - for JSONB containment queries.
13    Gin,
14}
15
16/// Statistics about a table for query optimization.
17#[derive(Clone, Debug, Default)]
18pub struct TableStats {
19    /// Number of rows in the table.
20    pub row_count: usize,
21    /// Whether the table is sorted by primary key.
22    pub is_sorted: bool,
23    /// Available indexes on this table.
24    pub indexes: Vec<IndexInfo>,
25}
26
27/// Information about an index.
28#[derive(Clone, Debug)]
29pub struct IndexInfo {
30    /// Index name.
31    pub name: String,
32    /// Column names in the index.
33    pub columns: Vec<String>,
34    /// Whether this is a unique index.
35    pub is_unique: bool,
36    /// Index type (BTree or GIN).
37    pub index_type: QueryIndexType,
38}
39
40impl IndexInfo {
41    /// Creates a new index info with default BTree type.
42    pub fn new(name: impl Into<String>, columns: Vec<String>, is_unique: bool) -> Self {
43        Self {
44            name: name.into(),
45            columns,
46            is_unique,
47            index_type: QueryIndexType::BTree,
48        }
49    }
50
51    /// Creates a new GIN index info.
52    pub fn new_gin(name: impl Into<String>, columns: Vec<String>) -> Self {
53        Self {
54            name: name.into(),
55            columns,
56            is_unique: false, // GIN indexes are never unique
57            index_type: QueryIndexType::Gin,
58        }
59    }
60
61    /// Sets the index type.
62    pub fn with_type(mut self, index_type: QueryIndexType) -> Self {
63        self.index_type = index_type;
64        self
65    }
66
67    /// Returns true if this is a GIN index.
68    pub fn is_gin(&self) -> bool {
69        self.index_type == QueryIndexType::Gin
70    }
71}
72
73/// Execution context providing access to table metadata and statistics.
74#[derive(Clone, Debug, Default)]
75pub struct ExecutionContext {
76    /// Table statistics for optimization.
77    table_stats: alloc::collections::BTreeMap<String, TableStats>,
78}
79
80impl ExecutionContext {
81    /// Creates a new empty execution context.
82    pub fn new() -> Self {
83        Self {
84            table_stats: alloc::collections::BTreeMap::new(),
85        }
86    }
87
88    /// Registers table statistics.
89    pub fn register_table(&mut self, table: impl Into<String>, stats: TableStats) {
90        self.table_stats.insert(table.into(), stats);
91    }
92
93    /// Gets statistics for a table.
94    pub fn get_stats(&self, table: &str) -> Option<&TableStats> {
95        self.table_stats.get(table)
96    }
97
98    /// Gets the row count for a table.
99    pub fn row_count(&self, table: &str) -> usize {
100        self.table_stats
101            .get(table)
102            .map(|s| s.row_count)
103            .unwrap_or(0)
104    }
105
106    /// Checks if a table has an index on the given columns.
107    pub fn has_index(&self, table: &str, columns: &[&str]) -> bool {
108        self.table_stats
109            .get(table)
110            .map(|s| {
111                s.indexes.iter().any(|idx| {
112                    idx.columns.len() >= columns.len()
113                        && idx
114                            .columns
115                            .iter()
116                            .zip(columns.iter())
117                            .all(|(a, b)| a == *b)
118                })
119            })
120            .unwrap_or(false)
121    }
122
123    /// Finds an index for the given columns.
124    pub fn find_index(&self, table: &str, columns: &[&str]) -> Option<&IndexInfo> {
125        self.table_stats.get(table).and_then(|s| {
126            s.indexes.iter().find(|idx| {
127                idx.columns.len() >= columns.len()
128                    && idx
129                        .columns
130                        .iter()
131                        .zip(columns.iter())
132                        .all(|(a, b)| a == *b)
133            })
134        })
135    }
136
137    /// Finds a GIN index for the given column.
138    pub fn find_gin_index(&self, table: &str, column: &str) -> Option<&IndexInfo> {
139        self.table_stats.get(table).and_then(|s| {
140            s.indexes.iter().find(|idx| {
141                idx.is_gin() && idx.columns.iter().any(|c| c == column)
142            })
143        })
144    }
145
146    /// Finds the primary key index (unique BTree index) for a table.
147    /// Returns the first unique BTree index found, which is typically the primary key.
148    pub fn find_primary_index(&self, table: &str) -> Option<&IndexInfo> {
149        self.table_stats.get(table).and_then(|s| {
150            s.indexes.iter().find(|idx| {
151                idx.is_unique && idx.index_type == QueryIndexType::BTree
152            })
153        })
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_execution_context() {
163        let mut ctx = ExecutionContext::new();
164
165        let stats = TableStats {
166            row_count: 1000,
167            is_sorted: true,
168            indexes: alloc::vec![IndexInfo::new(
169                "idx_id",
170                alloc::vec!["id".into()],
171                true
172            )],
173        };
174
175        ctx.register_table("users", stats);
176
177        assert_eq!(ctx.row_count("users"), 1000);
178        assert!(ctx.has_index("users", &["id"]));
179        assert!(!ctx.has_index("users", &["name"]));
180    }
181
182    #[test]
183    fn test_find_index() {
184        let mut ctx = ExecutionContext::new();
185
186        let stats = TableStats {
187            row_count: 100,
188            is_sorted: false,
189            indexes: alloc::vec![
190                IndexInfo::new("idx_id", alloc::vec!["id".into()], true),
191                IndexInfo::new("idx_name_age", alloc::vec!["name".into(), "age".into()], false),
192            ],
193        };
194
195        ctx.register_table("users", stats);
196
197        let idx = ctx.find_index("users", &["id"]);
198        assert!(idx.is_some());
199        assert_eq!(idx.unwrap().name, "idx_id");
200
201        let idx = ctx.find_index("users", &["name"]);
202        assert!(idx.is_some());
203        assert_eq!(idx.unwrap().name, "idx_name_age");
204
205        let idx = ctx.find_index("users", &["email"]);
206        assert!(idx.is_none());
207    }
208}