1use serde_json::Value;
6use std::collections::HashMap;
7
8use crate::Result;
9
10mod sqlite;
11pub use sqlite::SqliteDatabase;
12
13pub mod d1;
14pub use d1::D1Database;
15
16pub mod supabase;
17pub use supabase::SupabaseDatabase;
18
19#[derive(Debug, Default, Clone)]
21pub struct CollectionQuery {
22 pub sort: Option<String>,
24 pub filter: Option<String>,
26 pub search: Option<String>,
28 pub search_fields: Option<String>,
30 pub limit: Option<usize>,
32 pub offset: Option<usize>,
34 pub forced_filters: Vec<String>,
39}
40
41#[derive(Clone)]
43pub enum DatabaseAdapter {
44 Sqlite(SqliteDatabase),
45 D1(D1Database),
46 Supabase(SupabaseDatabase),
47}
48
49impl DatabaseAdapter {
50 pub async fn get_collection(&self, name: &str) -> Option<Vec<Value>> {
52 match self {
53 Self::Sqlite(db) => db
54 .get_collection(name)
55 .await
56 .map_err(|e| {
57 tracing::warn!("SQLite get_collection '{}' error: {}", name, e);
58 e
59 })
60 .ok(),
61 Self::D1(db) => db
62 .get_collection(name)
63 .await
64 .map_err(|e| {
65 tracing::warn!("D1 get_collection '{}' error: {}", name, e);
66 e
67 })
68 .ok(),
69 Self::Supabase(db) => db
70 .get_collection(name)
71 .await
72 .map_err(|e| {
73 tracing::warn!("Supabase get_collection '{}' error: {}", name, e);
74 e
75 })
76 .ok(),
77 }
78 }
79
80 pub async fn query_collection(
82 &self,
83 name: &str,
84 query: &CollectionQuery,
85 ) -> Option<Vec<Value>> {
86 match self {
87 Self::Sqlite(db) => db
88 .query_collection(name, query)
89 .await
90 .map_err(|e| {
91 tracing::warn!("SQLite query_collection '{}' error: {}", name, e);
92 e
93 })
94 .ok(),
95 Self::D1(db) => db
96 .query_collection(name, query)
97 .await
98 .map_err(|e| {
99 tracing::warn!("D1 query_collection '{}' error: {}", name, e);
100 e
101 })
102 .ok(),
103 Self::Supabase(db) => db
104 .query_collection(name, query)
105 .await
106 .map_err(|e| {
107 tracing::warn!("Supabase query_collection '{}' error: {}", name, e);
108 e
109 })
110 .ok(),
111 }
112 }
113
114 pub async fn find_by(&self, collection: &str, field: &str, value: &Value) -> Vec<Value> {
116 match self {
117 Self::Sqlite(db) => db
118 .find_by(collection, field, value)
119 .await
120 .unwrap_or_else(|e| {
121 tracing::warn!("SQLite find_by '{}.{}' error: {}", collection, field, e);
122 Vec::new()
123 }),
124 Self::D1(db) => db
125 .find_by(collection, field, value)
126 .await
127 .unwrap_or_else(|e| {
128 tracing::warn!("D1 find_by '{}.{}' error: {}", collection, field, e);
129 Vec::new()
130 }),
131 Self::Supabase(db) => db
132 .find_by(collection, field, value)
133 .await
134 .unwrap_or_else(|e| {
135 tracing::warn!("Supabase find_by '{}.{}' error: {}", collection, field, e);
136 Vec::new()
137 }),
138 }
139 }
140
141 pub async fn find_one_by(&self, collection: &str, field: &str, value: &Value) -> Option<Value> {
143 match self {
144 Self::Sqlite(db) => db
145 .find_one_by(collection, field, value)
146 .await
147 .map_err(|e| {
148 tracing::warn!("SQLite find_one_by '{}.{}' error: {}", collection, field, e);
149 e
150 })
151 .ok()
152 .flatten(),
153 Self::D1(db) => db
154 .find_one_by(collection, field, value)
155 .await
156 .map_err(|e| {
157 tracing::warn!("D1 find_one_by '{}.{}' error: {}", collection, field, e);
158 e
159 })
160 .ok()
161 .flatten(),
162 Self::Supabase(db) => db
163 .find_one_by(collection, field, value)
164 .await
165 .map_err(|e| {
166 tracing::warn!(
167 "Supabase find_one_by '{}.{}' error: {}",
168 collection,
169 field,
170 e
171 );
172 e
173 })
174 .ok()
175 .flatten(),
176 }
177 }
178
179 pub async fn create(&self, collection: &str, item: Value) -> Result<Value> {
181 match self {
182 Self::Sqlite(db) => db.create(collection, item).await,
183 Self::D1(db) => db.create(collection, item).await,
184 Self::Supabase(db) => db.create(collection, item).await,
185 }
186 }
187
188 pub async fn update(
190 &self,
191 collection: &str,
192 id: &Value,
193 updates: Value,
194 ) -> Result<Option<Value>> {
195 match self {
196 Self::Sqlite(db) => db.update(collection, id, updates).await,
197 Self::D1(db) => db.update(collection, id, updates).await,
198 Self::Supabase(db) => db.update(collection, id, updates).await,
199 }
200 }
201
202 pub async fn delete(&self, collection: &str, id: &Value) -> Result<bool> {
204 match self {
205 Self::Sqlite(db) => db.delete(collection, id).await,
206 Self::D1(db) => db.delete(collection, id).await,
207 Self::Supabase(db) => db.delete(collection, id).await,
208 }
209 }
210
211 pub async fn set(&self, key: &str, value: Value) -> Result<()> {
213 match self {
214 Self::Sqlite(db) => db.set(key, value).await,
215 Self::D1(db) => db.set(key, value).await,
216 Self::Supabase(db) => db.set(key, value).await,
217 }
218 }
219
220 pub async fn get(&self, key: &str) -> Option<Value> {
222 match self {
223 Self::Sqlite(db) => db
224 .get(key)
225 .await
226 .map_err(|e| {
227 tracing::warn!("SQLite get '{}' error: {}", key, e);
228 e
229 })
230 .ok()
231 .flatten(),
232 Self::D1(db) => db
233 .get(key)
234 .await
235 .map_err(|e| {
236 tracing::warn!("D1 get '{}' error: {}", key, e);
237 e
238 })
239 .ok()
240 .flatten(),
241 Self::Supabase(db) => db
242 .get(key)
243 .await
244 .map_err(|e| {
245 tracing::warn!("Supabase get '{}' error: {}", key, e);
246 e
247 })
248 .ok()
249 .flatten(),
250 }
251 }
252
253 pub async fn as_context(&self) -> HashMap<String, Value> {
255 match self {
256 Self::Sqlite(db) => db.as_context().await.unwrap_or_else(|e| {
257 tracing::warn!("SQLite as_context error: {}", e);
258 HashMap::new()
259 }),
260 Self::D1(db) => db.as_context().await.unwrap_or_else(|e| {
261 tracing::warn!("D1 as_context error: {}", e);
262 HashMap::new()
263 }),
264 Self::Supabase(db) => db.as_context().await.unwrap_or_else(|e| {
265 tracing::warn!("Supabase as_context error: {}", e);
266 HashMap::new()
267 }),
268 }
269 }
270
271 pub async fn set_collection(&self, name: &str, items: Vec<Value>) -> Result<()> {
273 match self {
274 Self::Sqlite(db) => db.set_collection(name, items).await,
275 Self::D1(db) => db.set_collection(name, items).await,
276 Self::Supabase(db) => db.set_collection(name, items).await,
277 }
278 }
279
280 pub async fn load_collection(
282 &self,
283 _name: &str,
284 _path: impl AsRef<std::path::Path>,
285 ) -> Result<()> {
286 Ok(())
287 }
288
289 pub async fn atomic_modify<F>(&self, key: &str, f: F) -> Result<Value>
291 where
292 F: FnOnce(Option<&Value>) -> Value + Send + 'static,
293 {
294 match self {
295 Self::Sqlite(db) => db.atomic_modify(key, f).await,
296 Self::D1(db) => db.atomic_modify(key, f).await,
297 Self::Supabase(db) => db.atomic_modify(key, f).await,
298 }
299 }
300
301 pub async fn remove(&self, key: &str) -> Result<Option<Value>> {
303 match self {
304 Self::Sqlite(db) => db.remove(key).await,
305 Self::D1(db) => db.remove(key).await,
306 Self::Supabase(db) => db.remove(key).await,
307 }
308 }
309}
310
311#[cfg(test)]
316fn apply_query_in_memory(mut items: Vec<Value>, query: &CollectionQuery) -> Vec<Value> {
317 if let Some(ref filter_expr) = query.filter {
319 items = apply_filter(&items, filter_expr);
320 }
321
322 if let Some(ref search_term) = query.search {
324 if !search_term.is_empty() {
325 let fields: Vec<&str> = query
326 .search_fields
327 .as_deref()
328 .map(|s| s.split(',').collect())
329 .unwrap_or_default();
330 let term_lower = search_term.to_lowercase();
331 items.retain(|item| {
332 if fields.is_empty() {
333 if let Value::Object(map) = item {
335 map.values().any(|v| {
336 v.as_str()
337 .map(|s| s.to_lowercase().contains(&term_lower))
338 .unwrap_or(false)
339 })
340 } else {
341 false
342 }
343 } else {
344 fields.iter().any(|field| {
345 item.get(field.trim())
346 .and_then(|v| v.as_str())
347 .map(|s| s.to_lowercase().contains(&term_lower))
348 .unwrap_or(false)
349 })
350 }
351 });
352 }
353 }
354
355 if let Some(ref sort_expr) = query.sort {
357 let (field, descending) = parse_sort_expr(sort_expr);
358 items.sort_by(|a, b| {
359 let va = a.get(&field);
360 let vb = b.get(&field);
361 let cmp = compare_json_values(va, vb);
362 if descending { cmp.reverse() } else { cmp }
363 });
364 }
365
366 if let Some(offset) = query.offset {
368 if offset < items.len() {
369 items = items[offset..].to_vec();
370 } else {
371 items.clear();
372 }
373 }
374
375 if let Some(limit) = query.limit {
377 items.truncate(limit);
378 }
379
380 items
381}
382
383#[cfg(test)]
384fn parse_sort_expr(expr: &str) -> (String, bool) {
385 if let Some((field, dir)) = expr.rsplit_once(':') {
386 (field.to_string(), dir.eq_ignore_ascii_case("desc"))
387 } else {
388 (expr.to_string(), false)
389 }
390}
391
392#[cfg(test)]
393fn compare_json_values(a: Option<&Value>, b: Option<&Value>) -> std::cmp::Ordering {
394 match (a, b) {
395 (None, None) => std::cmp::Ordering::Equal,
396 (None, Some(_)) => std::cmp::Ordering::Less,
397 (Some(_), None) => std::cmp::Ordering::Greater,
398 (Some(a), Some(b)) => {
399 if let (Some(na), Some(nb)) = (a.as_f64(), b.as_f64()) {
401 return na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal);
402 }
403 let sa = a.as_str().unwrap_or("");
405 let sb = b.as_str().unwrap_or("");
406 sa.cmp(sb)
407 }
408 }
409}
410
411#[cfg(test)]
412fn apply_filter(items: &[Value], filter_expr: &str) -> Vec<Value> {
413 let or_groups: Vec<&str> = filter_expr.split(',').collect();
415
416 items
417 .iter()
418 .filter(|item| {
419 or_groups.iter().any(|group| {
420 let and_conditions: Vec<&str> = group.split('&').collect();
421 and_conditions.iter().all(|cond| {
422 let cond = cond.trim();
423 evaluate_condition(item, cond)
424 })
425 })
426 })
427 .cloned()
428 .collect()
429}
430
431#[cfg(test)]
432fn evaluate_condition(item: &Value, cond: &str) -> bool {
433 if let Some((field, val)) = cond.split_once(">=") {
434 let field = field.trim();
435 let val = val.trim();
436 item.get(field)
437 .map(|v| {
438 if let (Some(n), Ok(target)) = (v.as_f64(), val.parse::<f64>()) {
439 n >= target
440 } else {
441 v.as_str().unwrap_or("") >= val
442 }
443 })
444 .unwrap_or(false)
445 } else if let Some((field, val)) = cond.split_once("<=") {
446 let field = field.trim();
447 let val = val.trim();
448 item.get(field)
449 .map(|v| {
450 if let (Some(n), Ok(target)) = (v.as_f64(), val.parse::<f64>()) {
451 n <= target
452 } else {
453 v.as_str().unwrap_or("") <= val
454 }
455 })
456 .unwrap_or(false)
457 } else if let Some((field, val)) = cond.split_once('>') {
458 let field = field.trim();
459 let val = val.trim();
460 item.get(field)
461 .map(|v| {
462 if let (Some(n), Ok(target)) = (v.as_f64(), val.parse::<f64>()) {
463 n > target
464 } else {
465 v.as_str().unwrap_or("") > val
466 }
467 })
468 .unwrap_or(false)
469 } else if let Some((field, val)) = cond.split_once('<') {
470 let field = field.trim();
471 let val = val.trim();
472 item.get(field)
473 .map(|v| {
474 if let (Some(n), Ok(target)) = (v.as_f64(), val.parse::<f64>()) {
475 n < target
476 } else {
477 v.as_str().unwrap_or("") < val
478 }
479 })
480 .unwrap_or(false)
481 } else if let Some((field, val)) = cond.split_once('=') {
482 let field = field.trim();
483 let val = val.trim();
484 item.get(field)
485 .map(|v| match v {
486 Value::String(s) => s == val,
487 Value::Number(n) => n.to_string() == val,
488 Value::Bool(b) => b.to_string() == val,
489 _ => false,
490 })
491 .unwrap_or(false)
492 } else {
493 true }
495}
496
497#[cfg(test)]
498mod tests {
499 use super::*;
500 use serde_json::json;
501
502 #[test]
503 fn test_sort_asc() {
504 let items = vec![
505 json!({"name": "Charlie", "age": 30}),
506 json!({"name": "Alice", "age": 25}),
507 json!({"name": "Bob", "age": 28}),
508 ];
509 let query = CollectionQuery {
510 sort: Some("name:asc".to_string()),
511 ..Default::default()
512 };
513 let result = apply_query_in_memory(items, &query);
514 assert_eq!(result[0]["name"], "Alice");
515 assert_eq!(result[1]["name"], "Bob");
516 assert_eq!(result[2]["name"], "Charlie");
517 }
518
519 #[test]
520 fn test_sort_desc() {
521 let items = vec![
522 json!({"name": "Alice", "age": 25}),
523 json!({"name": "Bob", "age": 28}),
524 json!({"name": "Charlie", "age": 30}),
525 ];
526 let query = CollectionQuery {
527 sort: Some("age:desc".to_string()),
528 ..Default::default()
529 };
530 let result = apply_query_in_memory(items, &query);
531 assert_eq!(result[0]["age"], 30);
532 assert_eq!(result[1]["age"], 28);
533 assert_eq!(result[2]["age"], 25);
534 }
535
536 #[test]
537 fn test_filter_exact() {
538 let items = vec![
539 json!({"status": "published", "title": "A"}),
540 json!({"status": "draft", "title": "B"}),
541 json!({"status": "published", "title": "C"}),
542 ];
543 let query = CollectionQuery {
544 filter: Some("status=published".to_string()),
545 ..Default::default()
546 };
547 let result = apply_query_in_memory(items, &query);
548 assert_eq!(result.len(), 2);
549 }
550
551 #[test]
552 fn test_filter_comparison() {
553 let items = vec![
554 json!({"price": 10}),
555 json!({"price": 25}),
556 json!({"price": 50}),
557 ];
558 let query = CollectionQuery {
559 filter: Some("price>20".to_string()),
560 ..Default::default()
561 };
562 let result = apply_query_in_memory(items, &query);
563 assert_eq!(result.len(), 2);
564 }
565
566 #[test]
567 fn test_filter_and() {
568 let items = vec![
569 json!({"status": "published", "category": "tech"}),
570 json!({"status": "published", "category": "food"}),
571 json!({"status": "draft", "category": "tech"}),
572 ];
573 let query = CollectionQuery {
574 filter: Some("status=published&category=tech".to_string()),
575 ..Default::default()
576 };
577 let result = apply_query_in_memory(items, &query);
578 assert_eq!(result.len(), 1);
579 }
580
581 #[test]
582 fn test_filter_or() {
583 let items = vec![
584 json!({"status": "published"}),
585 json!({"status": "draft"}),
586 json!({"status": "archived"}),
587 ];
588 let query = CollectionQuery {
589 filter: Some("status=published,status=draft".to_string()),
590 ..Default::default()
591 };
592 let result = apply_query_in_memory(items, &query);
593 assert_eq!(result.len(), 2);
594 }
595
596 #[test]
597 fn test_search() {
598 let items = vec![
599 json!({"title": "Rust Programming", "content": "Learn Rust"}),
600 json!({"title": "Python Basics", "content": "Learn Python"}),
601 json!({"title": "Rust Web Dev", "content": "Build web apps"}),
602 ];
603 let query = CollectionQuery {
604 search: Some("rust".to_string()),
605 search_fields: Some("title,content".to_string()),
606 ..Default::default()
607 };
608 let result = apply_query_in_memory(items, &query);
609 assert_eq!(result.len(), 2);
610 }
611
612 #[test]
613 fn test_limit_offset() {
614 let items: Vec<Value> = (1..=10).map(|i| json!({"n": i})).collect();
615 let query = CollectionQuery {
616 offset: Some(3),
617 limit: Some(2),
618 ..Default::default()
619 };
620 let result = apply_query_in_memory(items, &query);
621 assert_eq!(result.len(), 2);
622 assert_eq!(result[0]["n"], 4);
623 assert_eq!(result[1]["n"], 5);
624 }
625
626 #[test]
627 fn test_combined_query() {
628 let items = vec![
629 json!({"title": "Rust A", "views": 100, "status": "published"}),
630 json!({"title": "Rust B", "views": 50, "status": "draft"}),
631 json!({"title": "Python", "views": 200, "status": "published"}),
632 json!({"title": "Rust C", "views": 150, "status": "published"}),
633 ];
634 let query = CollectionQuery {
635 filter: Some("status=published".to_string()),
636 sort: Some("views:desc".to_string()),
637 limit: Some(2),
638 ..Default::default()
639 };
640 let result = apply_query_in_memory(items, &query);
641 assert_eq!(result.len(), 2);
642 assert_eq!(result[0]["title"], "Python");
643 assert_eq!(result[1]["title"], "Rust C");
644 }
645}