1use crate::ast::ddl::IndexMethod;
7
8#[derive(Debug, Clone)]
39pub struct IndexMetadata {
40 pub index_id: u32,
42 pub name: String,
44 pub catalog_name: String,
46 pub namespace_name: String,
48 pub table: String,
50 pub columns: Vec<String>,
52 pub column_indices: Vec<usize>,
54 pub unique: bool,
56 pub method: Option<IndexMethod>,
58 pub options: Vec<(String, String)>,
60}
61
62impl IndexMetadata {
63 pub fn new(
73 index_id: u32,
74 name: impl Into<String>,
75 table: impl Into<String>,
76 columns: Vec<String>,
77 ) -> Self {
78 Self {
79 index_id,
80 name: name.into(),
81 catalog_name: "default".to_string(),
82 namespace_name: "default".to_string(),
83 table: table.into(),
84 columns,
85 column_indices: Vec::new(),
86 unique: false,
87 method: None,
88 options: Vec::new(),
89 }
90 }
91
92 pub fn with_column_indices(mut self, indices: Vec<usize>) -> Self {
94 self.column_indices = indices;
95 self
96 }
97
98 pub fn with_unique(mut self, unique: bool) -> Self {
100 self.unique = unique;
101 self
102 }
103
104 pub fn with_method(mut self, method: IndexMethod) -> Self {
106 self.method = Some(method);
107 self
108 }
109
110 pub fn with_option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
112 self.options.push((key.into(), value.into()));
113 self
114 }
115
116 pub fn with_options(mut self, options: Vec<(String, String)>) -> Self {
118 self.options = options;
119 self
120 }
121
122 pub fn get_option(&self, key: &str) -> Option<&str> {
126 self.options
127 .iter()
128 .find(|(k, _)| k == key)
129 .map(|(_, v)| v.as_str())
130 }
131
132 pub fn covers_column(&self, column: &str) -> bool {
134 self.columns.iter().any(|c| c == column)
135 }
136
137 pub fn is_single_column(&self) -> bool {
139 self.columns.len() == 1
140 }
141
142 pub fn first_column(&self) -> Option<&str> {
146 self.columns.first().map(|s| s.as_str())
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn test_index_metadata_new() {
156 let index = IndexMetadata::new(1, "idx_users_id", "users", vec!["id".into()]);
157
158 assert_eq!(index.index_id, 1);
159 assert_eq!(index.name, "idx_users_id");
160 assert_eq!(index.table, "users");
161 assert_eq!(index.columns, vec!["id"]);
162 assert!(index.column_indices.is_empty());
163 assert!(!index.unique);
164 assert!(index.method.is_none());
165 assert!(index.options.is_empty());
166 }
167
168 #[test]
169 fn test_index_metadata_with_column_indices() {
170 let index =
171 IndexMetadata::new(1, "idx", "table", vec!["col".into()]).with_column_indices(vec![2]);
172
173 assert_eq!(index.column_indices, vec![2]);
174 }
175
176 #[test]
177 fn test_index_metadata_with_unique() {
178 let index = IndexMetadata::new(1, "idx", "table", vec!["col".into()]).with_unique(true);
179
180 assert!(index.unique);
181 }
182
183 #[test]
184 fn test_index_metadata_composite() {
185 let index = IndexMetadata::new(
186 5,
187 "idx_composite",
188 "orders",
189 vec!["user_id".into(), "order_date".into()],
190 )
191 .with_column_indices(vec![0, 3])
192 .with_unique(true);
193
194 assert_eq!(index.index_id, 5);
195 assert_eq!(index.columns.len(), 2);
196 assert_eq!(index.column_indices, vec![0, 3]);
197 assert!(index.unique);
198 assert!(!index.is_single_column());
199 }
200
201 #[test]
202 fn test_index_metadata_with_method() {
203 let index = IndexMetadata::new(1, "idx_users_name", "users", vec!["name".into()])
204 .with_method(IndexMethod::BTree);
205
206 assert_eq!(index.method, Some(IndexMethod::BTree));
207 }
208
209 #[test]
210 fn test_index_metadata_hnsw_with_options() {
211 let index = IndexMetadata::new(1, "idx_items_embedding", "items", vec!["embedding".into()])
212 .with_method(IndexMethod::Hnsw)
213 .with_option("m", "16")
214 .with_option("ef_construction", "200");
215
216 assert_eq!(index.method, Some(IndexMethod::Hnsw));
217 assert_eq!(index.options.len(), 2);
218 assert_eq!(index.get_option("m"), Some("16"));
219 assert_eq!(index.get_option("ef_construction"), Some("200"));
220 assert_eq!(index.get_option("nonexistent"), None);
221 }
222
223 #[test]
224 fn test_index_metadata_with_options_bulk() {
225 let options = vec![
226 ("m".to_string(), "32".to_string()),
227 ("ef_construction".to_string(), "400".to_string()),
228 ];
229 let index = IndexMetadata::new(1, "idx", "table", vec!["col".into()]).with_options(options);
230
231 assert_eq!(index.options.len(), 2);
232 assert_eq!(index.get_option("m"), Some("32"));
233 }
234
235 #[test]
236 fn test_covers_column() {
237 let index = IndexMetadata::new(1, "idx", "table", vec!["a".into(), "b".into()]);
238
239 assert!(index.covers_column("a"));
240 assert!(index.covers_column("b"));
241 assert!(!index.covers_column("c"));
242 }
243
244 #[test]
245 fn test_is_single_column() {
246 let single = IndexMetadata::new(1, "idx", "table", vec!["a".into()]);
247 let composite = IndexMetadata::new(2, "idx", "table", vec!["a".into(), "b".into()]);
248
249 assert!(single.is_single_column());
250 assert!(!composite.is_single_column());
251 }
252
253 #[test]
254 fn test_first_column() {
255 let index = IndexMetadata::new(1, "idx", "table", vec!["first".into(), "second".into()]);
256 let empty = IndexMetadata::new(2, "idx", "table", vec![]);
257
258 assert_eq!(index.first_column(), Some("first"));
259 assert_eq!(empty.first_column(), None);
260 }
261}