1use alloc::string::String;
4use alloc::vec::Vec;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
8pub enum QueryIndexType {
9 #[default]
11 BTree,
12 Gin,
14}
15
16#[derive(Clone, Debug, Default)]
18pub struct TableStats {
19 pub row_count: usize,
21 pub is_sorted: bool,
23 pub indexes: Vec<IndexInfo>,
25}
26
27#[derive(Clone, Debug)]
29pub struct IndexInfo {
30 pub name: String,
32 pub columns: Vec<String>,
34 pub is_unique: bool,
36 pub index_type: QueryIndexType,
38}
39
40impl IndexInfo {
41 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 pub fn new_gin(name: impl Into<String>, columns: Vec<String>) -> Self {
53 Self {
54 name: name.into(),
55 columns,
56 is_unique: false, index_type: QueryIndexType::Gin,
58 }
59 }
60
61 pub fn with_type(mut self, index_type: QueryIndexType) -> Self {
63 self.index_type = index_type;
64 self
65 }
66
67 pub fn is_gin(&self) -> bool {
69 self.index_type == QueryIndexType::Gin
70 }
71}
72
73#[derive(Clone, Debug, Default)]
75pub struct ExecutionContext {
76 table_stats: alloc::collections::BTreeMap<String, TableStats>,
78}
79
80impl ExecutionContext {
81 pub fn new() -> Self {
83 Self {
84 table_stats: alloc::collections::BTreeMap::new(),
85 }
86 }
87
88 pub fn register_table(&mut self, table: impl Into<String>, stats: TableStats) {
90 self.table_stats.insert(table.into(), stats);
91 }
92
93 pub fn get_stats(&self, table: &str) -> Option<&TableStats> {
95 self.table_stats.get(table)
96 }
97
98 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 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 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 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 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}