1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct QueryPlan {
8 pub schema_version: u32,
10 pub original_query: String,
12 pub optimized_query: String,
14 pub steps: Vec<ExecutionStep>,
16 pub execution_time_ms: u64,
18 pub used_index: bool,
20 pub cache_status: CacheStatus,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct ExecutionStep {
27 pub step_num: usize,
29 pub operation: String,
31 pub result_count: usize,
33 pub time_ms: u64,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct CacheStatus {
40 pub parse_cache_hit: bool,
42 pub result_cache_hit: bool,
44}
45
46impl QueryPlan {
47 pub const SCHEMA_VERSION: u32 = 1;
49
50 #[must_use]
52 pub fn new(
53 original_query: String,
54 optimized_query: String,
55 steps: Vec<ExecutionStep>,
56 execution_time_ms: u64,
57 used_index: bool,
58 cache_status: CacheStatus,
59 ) -> Self {
60 Self {
61 schema_version: Self::SCHEMA_VERSION,
62 original_query,
63 optimized_query,
64 steps,
65 execution_time_ms,
66 used_index,
67 cache_status,
68 }
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
78 fn test_query_plan_creation() {
79 let plan = QueryPlan::new(
80 "kind:function".to_string(),
81 "kind:function".to_string(),
82 vec![],
83 10,
84 true,
85 CacheStatus {
86 parse_cache_hit: false,
87 result_cache_hit: false,
88 },
89 );
90 assert_eq!(plan.schema_version, QueryPlan::SCHEMA_VERSION);
91 assert_eq!(plan.original_query, "kind:function");
92 assert_eq!(plan.execution_time_ms, 10);
93 assert!(plan.used_index);
94 }
95
96 #[test]
97 fn test_query_plan_with_steps() {
98 let steps = vec![
99 ExecutionStep {
100 step_num: 1,
101 operation: "Parse query".to_string(),
102 result_count: 0,
103 time_ms: 2,
104 },
105 ExecutionStep {
106 step_num: 2,
107 operation: "Index lookup".to_string(),
108 result_count: 100,
109 time_ms: 5,
110 },
111 ExecutionStep {
112 step_num: 3,
113 operation: "Filter results".to_string(),
114 result_count: 25,
115 time_ms: 3,
116 },
117 ];
118
119 let plan = QueryPlan::new(
120 "kind:function name:test".to_string(),
121 "name:test kind:function".to_string(),
122 steps.clone(),
123 10,
124 true,
125 CacheStatus {
126 parse_cache_hit: false,
127 result_cache_hit: false,
128 },
129 );
130
131 assert_eq!(plan.steps.len(), 3);
132 assert_eq!(plan.steps[0].step_num, 1);
133 assert_eq!(plan.steps[1].result_count, 100);
134 assert_eq!(plan.steps[2].operation, "Filter results");
135 }
136
137 #[test]
138 fn test_query_plan_optimized_differs() {
139 let plan = QueryPlan::new(
140 "visibility:public name:foo".to_string(),
141 "name:foo visibility:public".to_string(), vec![],
143 15,
144 true,
145 CacheStatus {
146 parse_cache_hit: true,
147 result_cache_hit: false,
148 },
149 );
150
151 assert_ne!(plan.original_query, plan.optimized_query);
152 assert!(plan.original_query.starts_with("visibility"));
153 assert!(plan.optimized_query.starts_with("name"));
154 }
155
156 #[test]
157 fn test_query_plan_no_index() {
158 let plan = QueryPlan::new(
159 "content~=/regex/".to_string(),
160 "content~=/regex/".to_string(),
161 vec![],
162 100,
163 false, CacheStatus {
165 parse_cache_hit: false,
166 result_cache_hit: false,
167 },
168 );
169
170 assert!(!plan.used_index);
171 }
172
173 #[test]
174 fn test_query_plan_clone() {
175 let plan = QueryPlan::new(
176 "kind:class".to_string(),
177 "kind:class".to_string(),
178 vec![ExecutionStep {
179 step_num: 1,
180 operation: "test".to_string(),
181 result_count: 10,
182 time_ms: 5,
183 }],
184 5,
185 true,
186 CacheStatus {
187 parse_cache_hit: true,
188 result_cache_hit: true,
189 },
190 );
191
192 let cloned = plan.clone();
193 assert_eq!(plan.original_query, cloned.original_query);
194 assert_eq!(plan.steps.len(), cloned.steps.len());
195 assert_eq!(
196 plan.cache_status.parse_cache_hit,
197 cloned.cache_status.parse_cache_hit
198 );
199 }
200
201 #[test]
202 fn test_query_plan_debug() {
203 let plan = QueryPlan::new(
204 "test".to_string(),
205 "test".to_string(),
206 vec![],
207 0,
208 false,
209 CacheStatus {
210 parse_cache_hit: false,
211 result_cache_hit: false,
212 },
213 );
214
215 let debug_str = format!("{:?}", plan);
216 assert!(debug_str.contains("QueryPlan"));
217 assert!(debug_str.contains("schema_version"));
218 }
219
220 #[test]
221 fn test_query_plan_schema_version_constant() {
222 assert_eq!(QueryPlan::SCHEMA_VERSION, 1);
223 }
224
225 #[test]
226 fn test_query_plan_serde_json() {
227 let plan = QueryPlan::new(
228 "kind:function".to_string(),
229 "kind:function".to_string(),
230 vec![ExecutionStep {
231 step_num: 1,
232 operation: "test".to_string(),
233 result_count: 5,
234 time_ms: 2,
235 }],
236 10,
237 true,
238 CacheStatus {
239 parse_cache_hit: false,
240 result_cache_hit: true,
241 },
242 );
243
244 let json = serde_json::to_string(&plan).unwrap();
245 assert!(json.contains("\"schema_version\":1"));
246 assert!(json.contains("\"original_query\":\"kind:function\""));
247
248 let parsed: QueryPlan = serde_json::from_str(&json).unwrap();
249 assert_eq!(parsed.original_query, plan.original_query);
250 assert_eq!(parsed.steps.len(), 1);
251 }
252
253 #[test]
255 fn test_execution_step() {
256 let step = ExecutionStep {
257 step_num: 1,
258 operation: "Parse query".to_string(),
259 result_count: 0,
260 time_ms: 1,
261 };
262 assert_eq!(step.step_num, 1);
263 assert_eq!(step.operation, "Parse query");
264 }
265
266 #[test]
267 fn test_execution_step_clone() {
268 let step = ExecutionStep {
269 step_num: 2,
270 operation: "Index lookup".to_string(),
271 result_count: 50,
272 time_ms: 10,
273 };
274
275 let cloned = step.clone();
276 assert_eq!(step.step_num, cloned.step_num);
277 assert_eq!(step.operation, cloned.operation);
278 assert_eq!(step.result_count, cloned.result_count);
279 assert_eq!(step.time_ms, cloned.time_ms);
280 }
281
282 #[test]
283 fn test_execution_step_debug() {
284 let step = ExecutionStep {
285 step_num: 1,
286 operation: "test".to_string(),
287 result_count: 0,
288 time_ms: 0,
289 };
290
291 let debug_str = format!("{:?}", step);
292 assert!(debug_str.contains("ExecutionStep"));
293 assert!(debug_str.contains("step_num"));
294 assert!(debug_str.contains("operation"));
295 }
296
297 #[test]
298 fn test_execution_step_serde_json() {
299 let step = ExecutionStep {
300 step_num: 3,
301 operation: "Filter".to_string(),
302 result_count: 100,
303 time_ms: 15,
304 };
305
306 let json = serde_json::to_string(&step).unwrap();
307 assert!(json.contains("\"step_num\":3"));
308 assert!(json.contains("\"operation\":\"Filter\""));
309
310 let parsed: ExecutionStep = serde_json::from_str(&json).unwrap();
311 assert_eq!(parsed.step_num, step.step_num);
312 assert_eq!(parsed.result_count, step.result_count);
313 }
314
315 #[test]
317 fn test_cache_status() {
318 let status = CacheStatus {
319 parse_cache_hit: true,
320 result_cache_hit: false,
321 };
322 assert!(status.parse_cache_hit);
323 assert!(!status.result_cache_hit);
324 }
325
326 #[test]
327 fn test_cache_status_both_hit() {
328 let status = CacheStatus {
329 parse_cache_hit: true,
330 result_cache_hit: true,
331 };
332 assert!(status.parse_cache_hit);
333 assert!(status.result_cache_hit);
334 }
335
336 #[test]
337 fn test_cache_status_both_miss() {
338 let status = CacheStatus {
339 parse_cache_hit: false,
340 result_cache_hit: false,
341 };
342 assert!(!status.parse_cache_hit);
343 assert!(!status.result_cache_hit);
344 }
345
346 #[test]
347 fn test_cache_status_clone() {
348 let status = CacheStatus {
349 parse_cache_hit: true,
350 result_cache_hit: false,
351 };
352
353 let cloned = status.clone();
354 assert_eq!(status.parse_cache_hit, cloned.parse_cache_hit);
355 assert_eq!(status.result_cache_hit, cloned.result_cache_hit);
356 }
357
358 #[test]
359 fn test_cache_status_debug() {
360 let status = CacheStatus {
361 parse_cache_hit: false,
362 result_cache_hit: true,
363 };
364
365 let debug_str = format!("{:?}", status);
366 assert!(debug_str.contains("CacheStatus"));
367 assert!(debug_str.contains("parse_cache_hit"));
368 assert!(debug_str.contains("result_cache_hit"));
369 }
370
371 #[test]
372 fn test_cache_status_serde_json() {
373 let status = CacheStatus {
374 parse_cache_hit: true,
375 result_cache_hit: false,
376 };
377
378 let json = serde_json::to_string(&status).unwrap();
379 assert!(json.contains("\"parse_cache_hit\":true"));
380 assert!(json.contains("\"result_cache_hit\":false"));
381
382 let parsed: CacheStatus = serde_json::from_str(&json).unwrap();
383 assert_eq!(parsed.parse_cache_hit, status.parse_cache_hit);
384 assert_eq!(parsed.result_cache_hit, status.result_cache_hit);
385 }
386}