hehe_store/traits/
relational.rs1use crate::error::Result;
2use async_trait::async_trait;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::collections::HashMap;
6
7#[derive(Clone, Debug, Serialize, Deserialize)]
8pub struct Row {
9 pub columns: Vec<String>,
10 pub values: Vec<Value>,
11}
12
13impl Row {
14 pub fn new(columns: Vec<String>, values: Vec<Value>) -> Self {
15 Self { columns, values }
16 }
17
18 pub fn get(&self, column: &str) -> Option<&Value> {
19 self.columns
20 .iter()
21 .position(|c| c == column)
22 .and_then(|i| self.values.get(i))
23 }
24
25 pub fn get_str(&self, column: &str) -> Option<&str> {
26 self.get(column).and_then(|v| v.as_str())
27 }
28
29 pub fn get_i64(&self, column: &str) -> Option<i64> {
30 self.get(column).and_then(|v| v.as_i64())
31 }
32
33 pub fn get_f64(&self, column: &str) -> Option<f64> {
34 self.get(column).and_then(|v| v.as_f64())
35 }
36
37 pub fn get_bool(&self, column: &str) -> Option<bool> {
38 self.get(column).and_then(|v| v.as_bool())
39 }
40
41 pub fn to_map(&self) -> HashMap<String, Value> {
42 self.columns
43 .iter()
44 .zip(self.values.iter())
45 .map(|(k, v)| (k.clone(), v.clone()))
46 .collect()
47 }
48}
49
50#[derive(Clone, Debug)]
51pub struct Migration {
52 pub version: u32,
53 pub name: String,
54 pub up: String,
55 pub down: Option<String>,
56}
57
58impl Migration {
59 pub fn new(version: u32, name: impl Into<String>, up: impl Into<String>) -> Self {
60 Self {
61 version,
62 name: name.into(),
63 up: up.into(),
64 down: None,
65 }
66 }
67
68 pub fn with_down(mut self, down: impl Into<String>) -> Self {
69 self.down = Some(down.into());
70 self
71 }
72}
73
74#[async_trait]
75pub trait Transaction: Send {
76 async fn execute(&mut self, sql: &str, params: &[Value]) -> Result<u64>;
77 async fn query(&mut self, sql: &str, params: &[Value]) -> Result<Vec<Row>>;
78 async fn query_one(&mut self, sql: &str, params: &[Value]) -> Result<Option<Row>>;
79 async fn commit(self: Box<Self>) -> Result<()>;
80 async fn rollback(self: Box<Self>) -> Result<()>;
81}
82
83#[async_trait]
84pub trait RelationalStore: Send + Sync {
85 async fn execute(&self, sql: &str, params: &[Value]) -> Result<u64>;
86
87 async fn query(&self, sql: &str, params: &[Value]) -> Result<Vec<Row>>;
88
89 async fn query_one(&self, sql: &str, params: &[Value]) -> Result<Option<Row>>;
90
91 async fn begin(&self) -> Result<Box<dyn Transaction>>;
92
93 async fn migrate(&self, migrations: &[Migration]) -> Result<()>;
94
95 async fn ping(&self) -> Result<()>;
96
97 async fn execute_batch(&self, statements: &[&str]) -> Result<()> {
98 for stmt in statements {
99 self.execute(stmt, &[]).await?;
100 }
101 Ok(())
102 }
103
104 async fn table_exists(&self, table: &str) -> Result<bool>;
105
106 fn backend_name(&self) -> &'static str;
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn test_row_access() {
115 let row = Row::new(
116 vec!["id".into(), "name".into(), "age".into()],
117 vec![
118 Value::Number(1.into()),
119 Value::String("Alice".into()),
120 Value::Number(30.into()),
121 ],
122 );
123
124 assert_eq!(row.get_i64("id"), Some(1));
125 assert_eq!(row.get_str("name"), Some("Alice"));
126 assert_eq!(row.get_i64("age"), Some(30));
127 assert!(row.get("unknown").is_none());
128 }
129
130 #[test]
131 fn test_row_to_map() {
132 let row = Row::new(
133 vec!["a".into(), "b".into()],
134 vec![Value::String("x".into()), Value::String("y".into())],
135 );
136
137 let map = row.to_map();
138 assert_eq!(map.get("a"), Some(&Value::String("x".into())));
139 assert_eq!(map.get("b"), Some(&Value::String("y".into())));
140 }
141
142 #[test]
143 fn test_migration() {
144 let m = Migration::new(1, "create_users", "CREATE TABLE users (id INTEGER PRIMARY KEY)")
145 .with_down("DROP TABLE users");
146
147 assert_eq!(m.version, 1);
148 assert_eq!(m.name, "create_users");
149 assert!(m.down.is_some());
150 }
151}