1use r2d2::Pool;
12use r2d2_sqlite::SqliteConnectionManager;
13use rusqlite::{Connection, params};
14use serde_json::{Map, Value, json};
15use std::collections::HashMap;
16use std::path::Path;
17
18use super::CollectionQuery;
19use crate::Result;
20
21#[derive(Clone)]
25pub struct SqliteDatabase {
26 pool: Pool<SqliteConnectionManager>,
27}
28
29#[derive(Debug)]
31struct WhatCustomizer;
32
33impl r2d2::CustomizeConnection<Connection, rusqlite::Error> for WhatCustomizer {
34 fn on_acquire(&self, conn: &mut Connection) -> std::result::Result<(), rusqlite::Error> {
35 conn.execute_batch("PRAGMA busy_timeout=5000; PRAGMA synchronous=NORMAL;")?;
36 Ok(())
37 }
38}
39
40impl SqliteDatabase {
41 pub fn open(path: impl AsRef<Path>) -> Result<Self> {
44 let manager = SqliteConnectionManager::file(path);
45 let pool = Pool::builder()
46 .max_size(8)
47 .connection_customizer(Box::new(WhatCustomizer))
48 .build(manager)
49 .map_err(|e| crate::Error::Data(format!("Pool creation failed: {}", e)))?;
50 let db = Self { pool };
51 db.init()?;
52 Ok(db)
53 }
54
55 pub fn in_memory() -> Result<Self> {
58 let manager = SqliteConnectionManager::memory();
59 let pool = Pool::builder()
60 .max_size(1)
61 .connection_customizer(Box::new(WhatCustomizer))
62 .build(manager)
63 .map_err(|e| crate::Error::Data(format!("Pool creation failed: {}", e)))?;
64 let db = Self { pool };
65 db.init()?;
66 Ok(db)
67 }
68
69 fn init(&self) -> Result<()> {
70 let conn = self
71 .pool
72 .get()
73 .map_err(|e| crate::Error::Data(format!("Pool get failed: {}", e)))?;
74 conn.execute_batch("PRAGMA journal_mode=WAL;")?;
76 conn.execute(
77 "CREATE TABLE IF NOT EXISTS _kv_store (
78 key TEXT PRIMARY KEY,
79 value TEXT NOT NULL
80 )",
81 [],
82 )?;
83 conn.execute(
84 "CREATE TABLE IF NOT EXISTS _collections (
85 name TEXT PRIMARY KEY
86 )",
87 [],
88 )?;
89 Ok(())
90 }
91
92 pub fn import_json_collection(&self, name: &str, items: &[Value]) {
95 let conn = match self.pool.get() {
96 Ok(c) => c,
97 Err(_) => return,
98 };
99 let safe_name = sanitize_table_name(name);
100 if ensure_table_sync(&conn, name).is_err() {
101 return;
102 }
103
104 let count: i64 = conn
105 .query_row(
106 &format!("SELECT COUNT(*) FROM \"{}\"", safe_name),
107 [],
108 |r| r.get(0),
109 )
110 .unwrap_or(0);
111 if count > 0 {
112 return;
113 }
114
115 for item in items {
116 let mut data_map = match item {
117 Value::Object(map) => map.clone(),
118 _ => continue,
119 };
120 data_map.remove("id");
121 let data_str = serde_json::to_string(&Value::Object(data_map)).unwrap_or_default();
122 conn.execute(
123 &format!("INSERT INTO \"{}\" (data) VALUES (?1)", safe_name),
124 params![data_str],
125 )
126 .ok();
127 }
128 tracing::info!(
129 "Imported {} items from store.json into '{}'",
130 items.len(),
131 name
132 );
133 }
134
135 pub async fn get_collection(&self, name: &str) -> Result<Vec<Value>> {
137 let pool = self.pool.clone();
138 let name = name.to_string();
139 tokio::task::spawn_blocking(move || {
140 let conn = pool.get().map_err(|e| crate::Error::Data(e.to_string()))?;
141 ensure_table_sync(&conn, &name)?;
142 let safe_name = sanitize_table_name(&name);
143 let mut stmt = conn.prepare(&format!(
144 "SELECT id, data FROM \"{}\" ORDER BY id",
145 safe_name
146 ))?;
147 let rows = stmt.query_map([], |row| {
148 let id: i64 = row.get(0)?;
149 let data_str: String = row.get(1)?;
150 Ok((id, data_str))
151 })?;
152 let mut items = Vec::new();
153 for row in rows {
154 let (id, data_str) = row?;
155 let mut item: Value = serde_json::from_str(&data_str).unwrap_or(json!({}));
156 if let Value::Object(ref mut map) = item {
157 map.insert("id".to_string(), json!(id));
158 }
159 items.push(item);
160 }
161 Ok(items)
162 })
163 .await
164 .unwrap()
165 }
166
167 pub async fn query_collection(
169 &self,
170 name: &str,
171 query: &CollectionQuery,
172 ) -> Result<Vec<Value>> {
173 let pool = self.pool.clone();
174 let name = name.to_string();
175 let query = query.clone();
176 tokio::task::spawn_blocking(move || {
177 let conn = pool.get().map_err(|e| crate::Error::Data(e.to_string()))?;
178 ensure_table_sync(&conn, &name)?;
179 let safe_name = sanitize_table_name(&name);
180
181 let mut sql = format!("SELECT id, data FROM \"{}\"", safe_name);
182 let mut where_clauses = Vec::new();
183 let mut bind_values: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
184
185 if let Some(ref filter_expr) = query.filter {
186 let (clause, values) = build_filter_sql(filter_expr);
187 if !clause.is_empty() {
188 where_clauses.push(clause);
189 bind_values.extend(values);
190 }
191 }
192
193 for forced in &query.forced_filters {
195 let (clause, values) = build_filter_sql(forced);
196 if !clause.is_empty() {
197 where_clauses.push(clause);
198 bind_values.extend(values);
199 }
200 }
201
202 if let Some(ref search_term) = query.search {
203 if !search_term.is_empty() {
204 let fields: Vec<&str> = query
205 .search_fields
206 .as_deref()
207 .map(|s| s.split(',').map(|f| f.trim()).collect())
208 .unwrap_or_default();
209
210 if fields.is_empty() {
211 where_clauses.push("data LIKE ?".to_string());
212 bind_values.push(Box::new(format!("%{}%", search_term)));
213 } else {
214 let field_conditions: Vec<String> = fields
215 .iter()
216 .map(|f| {
217 bind_values.push(Box::new(format!("%{}%", search_term)));
218 format!("json_extract(data, '$.{}') LIKE ?", sanitize_field_name(f))
219 })
220 .collect();
221 where_clauses.push(format!("({})", field_conditions.join(" OR ")));
222 }
223 }
224 }
225
226 if !where_clauses.is_empty() {
227 sql.push_str(" WHERE ");
228 sql.push_str(&where_clauses.join(" AND "));
229 }
230
231 if let Some(ref sort_expr) = query.sort {
232 let (field, desc) = parse_sort(sort_expr);
233 let safe_field = sanitize_field_name(&field);
234 let dir = if desc { "DESC" } else { "ASC" };
235 sql.push_str(&format!(
236 " ORDER BY json_extract(data, '$.{}') {}",
237 safe_field, dir
238 ));
239 } else {
240 sql.push_str(" ORDER BY id");
241 }
242
243 if let Some(limit) = query.limit {
244 sql.push_str(&format!(" LIMIT {}", limit));
245 }
246 if let Some(offset) = query.offset {
247 sql.push_str(&format!(" OFFSET {}", offset));
248 }
249
250 let mut stmt = conn.prepare(&sql)?;
251 let params_ref: Vec<&dyn rusqlite::types::ToSql> =
252 bind_values.iter().map(|b| b.as_ref()).collect();
253 let rows = stmt.query_map(params_ref.as_slice(), |row| {
254 let id: i64 = row.get(0)?;
255 let data_str: String = row.get(1)?;
256 Ok((id, data_str))
257 })?;
258
259 let mut items = Vec::new();
260 for row in rows {
261 let (id, data_str) = row?;
262 let mut item: Value = serde_json::from_str(&data_str).unwrap_or(json!({}));
263 if let Value::Object(ref mut map) = item {
264 map.insert("id".to_string(), json!(id));
265 }
266 items.push(item);
267 }
268 Ok(items)
269 })
270 .await
271 .unwrap()
272 }
273
274 pub async fn find_by(
276 &self,
277 collection: &str,
278 field: &str,
279 value: &Value,
280 ) -> Result<Vec<Value>> {
281 let pool = self.pool.clone();
282 let collection = collection.to_string();
283 let field = field.to_string();
284 let value = value.clone();
285 tokio::task::spawn_blocking(move || {
286 let conn = pool.get().map_err(|e| crate::Error::Data(e.to_string()))?;
287 ensure_table_sync(&conn, &collection)?;
288 let safe_name = sanitize_table_name(&collection);
289 let safe_field = sanitize_field_name(&field);
290
291 let value_str = match &value {
292 Value::String(s) => s.clone(),
293 Value::Number(n) => n.to_string(),
294 Value::Bool(b) => b.to_string(),
295 _ => serde_json::to_string(&value).unwrap_or_default(),
296 };
297
298 let sql = if field == "id" {
299 format!("SELECT id, data FROM \"{}\" WHERE id = ?1", safe_name)
300 } else {
301 format!(
302 "SELECT id, data FROM \"{}\" WHERE json_extract(data, '$.{}') = ?1",
303 safe_name, safe_field
304 )
305 };
306
307 let mut stmt = conn.prepare(&sql)?;
308 let rows = stmt.query_map(params![value_str], |row| {
309 let id: i64 = row.get(0)?;
310 let data_str: String = row.get(1)?;
311 Ok((id, data_str))
312 })?;
313
314 let mut items = Vec::new();
315 for row in rows {
316 let (id, data_str) = row?;
317 let mut item: Value = serde_json::from_str(&data_str).unwrap_or(json!({}));
318 if let Value::Object(ref mut map) = item {
319 map.insert("id".to_string(), json!(id));
320 }
321 items.push(item);
322 }
323 Ok(items)
324 })
325 .await
326 .unwrap()
327 }
328
329 pub async fn find_one_by(
331 &self,
332 collection: &str,
333 field: &str,
334 value: &Value,
335 ) -> Result<Option<Value>> {
336 let items = self.find_by(collection, field, value).await?;
337 Ok(items.into_iter().next())
338 }
339
340 pub async fn create(&self, collection: &str, item: Value) -> Result<Value> {
342 let pool = self.pool.clone();
343 let collection = collection.to_string();
344 tokio::task::spawn_blocking(move || {
345 let conn = pool.get().map_err(|e| crate::Error::Data(e.to_string()))?;
346 ensure_table_sync(&conn, &collection)?;
347 let safe_name = sanitize_table_name(&collection);
348
349 let mut data_map = match item {
350 Value::Object(map) => map,
351 _ => Map::new(),
352 };
353 data_map.remove("id");
354 if !data_map.contains_key("created_at") {
357 data_map.insert(
358 "created_at".to_string(),
359 json!(chrono::Local::now()
360 .format("%Y-%m-%dT%H:%M:%S%.3f")
361 .to_string()),
362 );
363 }
364 let data_str = serde_json::to_string(&Value::Object(data_map.clone()))?;
365
366 conn.execute(
367 &format!("INSERT INTO \"{}\" (data) VALUES (?1)", safe_name),
368 params![data_str],
369 )?;
370
371 let id = conn.last_insert_rowid();
372 data_map.insert("id".to_string(), json!(id));
373 Ok(Value::Object(data_map))
374 })
375 .await
376 .unwrap()
377 }
378
379 pub async fn update(
381 &self,
382 collection: &str,
383 id: &Value,
384 updates: Value,
385 ) -> Result<Option<Value>> {
386 let pool = self.pool.clone();
387 let collection = collection.to_string();
388 let id = id.clone();
389 tokio::task::spawn_blocking(move || {
390 let conn = pool.get().map_err(|e| crate::Error::Data(e.to_string()))?;
391 ensure_table_sync(&conn, &collection)?;
392 let safe_name = sanitize_table_name(&collection);
393
394 let id_num = match &id {
395 Value::Number(n) => n.as_i64().unwrap_or(0),
396 Value::String(s) => s.parse::<i64>().unwrap_or(0),
397 _ => 0,
398 };
399
400 let current: Option<String> = conn
401 .query_row(
402 &format!("SELECT data FROM \"{}\" WHERE id = ?1", safe_name),
403 params![id_num],
404 |row| row.get(0),
405 )
406 .ok();
407
408 let Some(current_str) = current else {
409 return Ok(None);
410 };
411
412 let mut current_data: Map<String, Value> =
413 serde_json::from_str(¤t_str).unwrap_or_default();
414
415 if let Value::Object(update_map) = updates {
416 for (k, v) in update_map {
417 if k != "id" {
418 current_data.insert(k, v);
419 }
420 }
421 }
422
423 let updated_str = serde_json::to_string(&Value::Object(current_data.clone()))?;
424 conn.execute(
425 &format!("UPDATE \"{}\" SET data = ?1 WHERE id = ?2", safe_name),
426 params![updated_str, id_num],
427 )?;
428
429 current_data.insert("id".to_string(), json!(id_num));
430 Ok(Some(Value::Object(current_data)))
431 })
432 .await
433 .unwrap()
434 }
435
436 pub async fn delete(&self, collection: &str, id: &Value) -> Result<bool> {
438 let pool = self.pool.clone();
439 let collection = collection.to_string();
440 let id = id.clone();
441 tokio::task::spawn_blocking(move || {
442 let conn = pool.get().map_err(|e| crate::Error::Data(e.to_string()))?;
443 ensure_table_sync(&conn, &collection)?;
444 let safe_name = sanitize_table_name(&collection);
445
446 let id_num = match &id {
447 Value::Number(n) => n.as_i64().unwrap_or(0),
448 Value::String(s) => s.parse::<i64>().unwrap_or(0),
449 _ => 0,
450 };
451
452 let rows = conn.execute(
453 &format!("DELETE FROM \"{}\" WHERE id = ?1", safe_name),
454 params![id_num],
455 )?;
456
457 Ok(rows > 0)
458 })
459 .await
460 .unwrap()
461 }
462
463 pub async fn set(&self, key: &str, value: Value) -> Result<()> {
465 let pool = self.pool.clone();
466 let key = key.to_string();
467 tokio::task::spawn_blocking(move || {
468 let conn = pool.get().map_err(|e| crate::Error::Data(e.to_string()))?;
469 let value_str = serde_json::to_string(&value)?;
470 conn.execute(
471 "INSERT OR REPLACE INTO _kv_store (key, value) VALUES (?1, ?2)",
472 params![key, value_str],
473 )?;
474 Ok(())
475 })
476 .await
477 .unwrap()
478 }
479
480 pub async fn get(&self, key: &str) -> Result<Option<Value>> {
482 let pool = self.pool.clone();
483 let key = key.to_string();
484 tokio::task::spawn_blocking(move || {
485 let conn = pool.get().map_err(|e| crate::Error::Data(e.to_string()))?;
486 let result: Option<String> = conn
487 .query_row(
488 "SELECT value FROM _kv_store WHERE key = ?1",
489 params![key],
490 |row| row.get(0),
491 )
492 .ok();
493 match result {
494 Some(s) => Ok(Some(serde_json::from_str(&s)?)),
495 None => Ok(None),
496 }
497 })
498 .await
499 .unwrap()
500 }
501
502 pub async fn as_context(&self) -> Result<HashMap<String, Value>> {
504 let pool = self.pool.clone();
505 tokio::task::spawn_blocking(move || {
506 let conn = pool.get().map_err(|e| crate::Error::Data(e.to_string()))?;
507 let mut context = HashMap::new();
508
509 let mut stmt = conn.prepare("SELECT name FROM _collections")?;
510 let names: Vec<String> = stmt
511 .query_map([], |row| row.get(0))?
512 .filter_map(|r| r.ok())
513 .collect();
514 drop(stmt);
515
516 for name in names {
517 let safe_name = sanitize_table_name(&name);
518 let mut stmt = conn.prepare(&format!(
519 "SELECT id, data FROM \"{}\" ORDER BY id",
520 safe_name
521 ))?;
522 let items: Vec<Value> = stmt
523 .query_map([], |row| {
524 let id: i64 = row.get(0)?;
525 let data_str: String = row.get(1)?;
526 Ok((id, data_str))
527 })?
528 .filter_map(|r| r.ok())
529 .map(|(id, data_str)| {
530 let mut item: Value = serde_json::from_str(&data_str).unwrap_or(json!({}));
531 if let Value::Object(ref mut map) = item {
532 map.insert("id".to_string(), json!(id));
533 }
534 item
535 })
536 .collect();
537 drop(stmt);
538 context.insert(name, Value::Array(items));
539 }
540
541 let mut stmt = conn.prepare("SELECT key, value FROM _kv_store")?;
542 let kvs: Vec<(String, String)> = stmt
543 .query_map([], |row| Ok((row.get(0)?, row.get(1)?)))?
544 .filter_map(|r| r.ok())
545 .collect();
546 drop(stmt);
547
548 for (key, value_str) in kvs {
549 if let Ok(value) = serde_json::from_str(&value_str) {
550 context.insert(key, value);
551 }
552 }
553
554 Ok(context)
555 })
556 .await
557 .unwrap()
558 }
559
560 pub async fn set_collection(&self, name: &str, items: Vec<Value>) -> Result<()> {
562 let pool = self.pool.clone();
563 let name = name.to_string();
564 tokio::task::spawn_blocking(move || {
565 let conn = pool.get().map_err(|e| crate::Error::Data(e.to_string()))?;
566 ensure_table_sync(&conn, &name)?;
567 let safe_name = sanitize_table_name(&name);
568
569 conn.execute(&format!("DELETE FROM \"{}\"", safe_name), [])?;
570
571 let mut stmt =
572 conn.prepare(&format!("INSERT INTO \"{}\" (data) VALUES (?1)", safe_name))?;
573 for item in items {
574 let mut data = match item {
575 Value::Object(map) => map,
576 _ => Map::new(),
577 };
578 data.remove("id");
579 let data_str = serde_json::to_string(&Value::Object(data))?;
580 stmt.execute(params![data_str])?;
581 }
582
583 Ok(())
584 })
585 .await
586 .unwrap()
587 }
588
589 pub async fn atomic_modify<F>(&self, key: &str, f: F) -> Result<Value>
591 where
592 F: FnOnce(Option<&Value>) -> Value + Send + 'static,
593 {
594 let pool = self.pool.clone();
595 let key = key.to_string();
596 tokio::task::spawn_blocking(move || {
597 let conn = pool.get().map_err(|e| crate::Error::Data(e.to_string()))?;
598 let current: Option<Value> = conn
599 .query_row(
600 "SELECT value FROM _kv_store WHERE key = ?1",
601 params![key],
602 |row| {
603 let s: String = row.get(0)?;
604 Ok(serde_json::from_str(&s).ok())
605 },
606 )
607 .ok()
608 .flatten();
609
610 let new_value = f(current.as_ref());
611 let value_str = serde_json::to_string(&new_value)?;
612 conn.execute(
613 "INSERT OR REPLACE INTO _kv_store (key, value) VALUES (?1, ?2)",
614 params![key, value_str],
615 )?;
616 Ok(new_value)
617 })
618 .await
619 .unwrap()
620 }
621
622 pub async fn remove(&self, key: &str) -> Result<Option<Value>> {
624 let pool = self.pool.clone();
625 let key = key.to_string();
626 tokio::task::spawn_blocking(move || {
627 let conn = pool.get().map_err(|e| crate::Error::Data(e.to_string()))?;
628 let current: Option<String> = conn
629 .query_row(
630 "SELECT value FROM _kv_store WHERE key = ?1",
631 params![key],
632 |row| row.get(0),
633 )
634 .ok();
635 let result = current.and_then(|s| serde_json::from_str(&s).ok());
636 conn.execute("DELETE FROM _kv_store WHERE key = ?1", params![key])?;
637 Ok(result)
638 })
639 .await
640 .unwrap()
641 }
642}
643
644fn ensure_table_sync(conn: &Connection, name: &str) -> Result<()> {
650 let safe_name = sanitize_table_name(name);
651 conn.execute(
652 &format!(
653 "CREATE TABLE IF NOT EXISTS \"{}\" (
654 id INTEGER PRIMARY KEY AUTOINCREMENT,
655 data TEXT NOT NULL DEFAULT '{{}}'
656 )",
657 safe_name
658 ),
659 [],
660 )?;
661 conn.execute(
662 "INSERT OR IGNORE INTO _collections (name) VALUES (?1)",
663 params![name],
664 )?;
665 Ok(())
666}
667
668fn sanitize_table_name(name: &str) -> String {
669 name.chars()
670 .filter(|c| c.is_ascii_alphanumeric() || *c == '_')
671 .collect()
672}
673
674fn sanitize_field_name(name: &str) -> String {
675 name.chars()
676 .filter(|c| c.is_ascii_alphanumeric() || *c == '_')
677 .collect()
678}
679
680fn build_filter_sql(filter_expr: &str) -> (String, Vec<Box<dyn rusqlite::types::ToSql>>) {
681 let mut clauses = Vec::new();
682 let mut values: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
683
684 let or_groups: Vec<&str> = filter_expr.split(',').collect();
685 let mut or_parts = Vec::new();
686
687 for group in or_groups {
688 let and_conditions: Vec<&str> = group.split('&').collect();
689 let mut and_parts = Vec::new();
690
691 for cond in and_conditions {
692 let cond = cond.trim();
693 if let Some((field, val)) = cond.split_once(">=") {
694 let field = sanitize_field_name(field.trim());
695 values.push(Box::new(val.trim().to_string()));
696 and_parts.push(format!("json_extract(data, '$.{}') >= ?", field));
697 } else if let Some((field, val)) = cond.split_once("<=") {
698 let field = sanitize_field_name(field.trim());
699 values.push(Box::new(val.trim().to_string()));
700 and_parts.push(format!("json_extract(data, '$.{}') <= ?", field));
701 } else if let Some((field, val)) = cond.split_once('>') {
702 let field = sanitize_field_name(field.trim());
703 values.push(Box::new(val.trim().to_string()));
704 and_parts.push(format!("json_extract(data, '$.{}') > ?", field));
705 } else if let Some((field, val)) = cond.split_once('<') {
706 let field = sanitize_field_name(field.trim());
707 values.push(Box::new(val.trim().to_string()));
708 and_parts.push(format!("json_extract(data, '$.{}') < ?", field));
709 } else if let Some((field, val)) = cond.split_once('=') {
710 let field = sanitize_field_name(field.trim());
711 values.push(Box::new(val.trim().to_string()));
712 and_parts.push(format!("json_extract(data, '$.{}') = ?", field));
713 }
714 }
715
716 if !and_parts.is_empty() {
717 or_parts.push(format!("({})", and_parts.join(" AND ")));
718 }
719 }
720
721 if !or_parts.is_empty() {
722 clauses.push(format!("({})", or_parts.join(" OR ")));
723 }
724
725 (clauses.join(" AND "), values)
726}
727
728fn parse_sort(expr: &str) -> (String, bool) {
729 if let Some((field, dir)) = expr.rsplit_once(':') {
730 (field.to_string(), dir.eq_ignore_ascii_case("desc"))
731 } else {
732 (expr.to_string(), false)
733 }
734}
735
736#[cfg(test)]
737mod tests {
738 use super::*;
739 use serde_json::json;
740
741 #[tokio::test]
742 async fn test_create_and_get() {
743 let db = SqliteDatabase::in_memory().unwrap();
744 let item = json!({"title": "Hello", "content": "World"});
745 let created = db.create("posts", item).await.unwrap();
746 assert_eq!(created.get("id"), Some(&json!(1)));
747 assert_eq!(created.get("title"), Some(&json!("Hello")));
748
749 let items = db.get_collection("posts").await.unwrap();
750 assert_eq!(items.len(), 1);
751 assert_eq!(items[0].get("title"), Some(&json!("Hello")));
752 }
753
754 #[tokio::test]
755 async fn test_find_by() {
756 let db = SqliteDatabase::in_memory().unwrap();
757 db.create("users", json!({"name": "Alice", "role": "admin"}))
758 .await
759 .unwrap();
760 db.create("users", json!({"name": "Bob", "role": "user"}))
761 .await
762 .unwrap();
763 db.create("users", json!({"name": "Charlie", "role": "admin"}))
764 .await
765 .unwrap();
766
767 let admins = db.find_by("users", "role", &json!("admin")).await.unwrap();
768 assert_eq!(admins.len(), 2);
769 }
770
771 #[tokio::test]
772 async fn test_find_by_id() {
773 let db = SqliteDatabase::in_memory().unwrap();
774 db.create("posts", json!({"title": "First"})).await.unwrap();
775 db.create("posts", json!({"title": "Second"}))
776 .await
777 .unwrap();
778
779 let found = db.find_one_by("posts", "id", &json!(2)).await.unwrap();
780 assert!(found.is_some());
781 assert_eq!(found.unwrap().get("title"), Some(&json!("Second")));
782 }
783
784 #[tokio::test]
785 async fn test_update() {
786 let db = SqliteDatabase::in_memory().unwrap();
787 db.create("posts", json!({"title": "Draft"})).await.unwrap();
788
789 let updated = db
790 .update(
791 "posts",
792 &json!(1),
793 json!({"title": "Published", "status": "live"}),
794 )
795 .await
796 .unwrap();
797 assert!(updated.is_some());
798 let u = updated.unwrap();
799 assert_eq!(u.get("title"), Some(&json!("Published")));
800 assert_eq!(u.get("status"), Some(&json!("live")));
801 }
802
803 #[tokio::test]
804 async fn test_delete() {
805 let db = SqliteDatabase::in_memory().unwrap();
806 db.create("posts", json!({"title": "To Delete"}))
807 .await
808 .unwrap();
809
810 let deleted = db.delete("posts", &json!(1)).await.unwrap();
811 assert!(deleted);
812
813 let items = db.get_collection("posts").await.unwrap();
814 assert!(items.is_empty());
815 }
816
817 #[tokio::test]
818 async fn test_kv_store() {
819 let db = SqliteDatabase::in_memory().unwrap();
820 db.set("counter", json!(42)).await.unwrap();
821
822 let val = db.get("counter").await.unwrap();
823 assert_eq!(val, Some(json!(42)));
824
825 let removed = db.remove("counter").await.unwrap();
826 assert_eq!(removed, Some(json!(42)));
827
828 let val = db.get("counter").await.unwrap();
829 assert_eq!(val, None);
830 }
831
832 #[tokio::test]
833 async fn test_query_with_filter() {
834 let db = SqliteDatabase::in_memory().unwrap();
835 db.create("posts", json!({"title": "A", "status": "published"}))
836 .await
837 .unwrap();
838 db.create("posts", json!({"title": "B", "status": "draft"}))
839 .await
840 .unwrap();
841 db.create("posts", json!({"title": "C", "status": "published"}))
842 .await
843 .unwrap();
844
845 let query = CollectionQuery {
846 filter: Some("status=published".to_string()),
847 ..Default::default()
848 };
849 let items = db.query_collection("posts", &query).await.unwrap();
850 assert_eq!(items.len(), 2);
851 }
852
853 #[tokio::test]
854 async fn test_query_with_sort_and_limit() {
855 let db = SqliteDatabase::in_memory().unwrap();
856 for i in 1..=5 {
857 db.create("items", json!({"n": i})).await.unwrap();
858 }
859
860 let query = CollectionQuery {
861 sort: Some("n:desc".to_string()),
862 limit: Some(3),
863 ..Default::default()
864 };
865 let items = db.query_collection("items", &query).await.unwrap();
866 assert_eq!(items.len(), 3);
867 assert_eq!(items[0]["n"], 5);
868 assert_eq!(items[1]["n"], 4);
869 assert_eq!(items[2]["n"], 3);
870 }
871
872 #[tokio::test]
873 async fn test_query_with_search() {
874 let db = SqliteDatabase::in_memory().unwrap();
875 db.create(
876 "posts",
877 json!({"title": "Rust Programming", "content": "Learn Rust"}),
878 )
879 .await
880 .unwrap();
881 db.create(
882 "posts",
883 json!({"title": "Python Basics", "content": "Learn Python"}),
884 )
885 .await
886 .unwrap();
887
888 let query = CollectionQuery {
889 search: Some("rust".to_string()),
890 search_fields: Some("title".to_string()),
891 ..Default::default()
892 };
893 let items = db.query_collection("posts", &query).await.unwrap();
894 assert_eq!(items.len(), 1);
895 assert_eq!(items[0]["title"], "Rust Programming");
896 }
897
898 #[tokio::test]
899 async fn test_as_context() {
900 let db = SqliteDatabase::in_memory().unwrap();
901 db.create("posts", json!({"title": "Hello"})).await.unwrap();
902 db.set("site_name", json!("My Site")).await.unwrap();
903
904 let ctx = db.as_context().await.unwrap();
905 assert!(ctx.contains_key("posts"));
906 assert!(ctx.contains_key("site_name"));
907 assert_eq!(ctx["site_name"], json!("My Site"));
908 }
909
910 #[tokio::test]
911 async fn test_set_collection() {
912 let db = SqliteDatabase::in_memory().unwrap();
913 let items = vec![json!({"name": "A"}), json!({"name": "B"})];
914 db.set_collection("letters", items).await.unwrap();
915
916 let result = db.get_collection("letters").await.unwrap();
917 assert_eq!(result.len(), 2);
918 }
919}