cratefield_core/ports/
database.rs1use async_trait::async_trait;
5use sea_query::Value as SeaValue;
6use thiserror::Error;
7
8#[derive(Debug, Clone)]
13pub struct Statement {
14 pub sql: String,
15 pub values: sea_query::Values,
16}
17
18impl Statement {
19 pub fn new(sql: impl Into<String>) -> Self {
21 Self {
22 sql: sql.into(),
23 values: sea_query::Values(Vec::new()),
24 }
25 }
26
27 pub fn with_values(sql: impl Into<String>, values: Vec<SeaValue>) -> Self {
29 Self {
30 sql: sql.into(),
31 values: sea_query::Values(values),
32 }
33 }
34
35 pub fn render(query: &impl sea_query::QueryStatementBuilder) -> Self {
39 let (sql, values) = query.build_any(&sea_query::SqliteQueryBuilder);
40 Self { sql, values }
41 }
42}
43
44#[derive(Debug, Clone)]
46pub struct Rows {
47 pub rows: Vec<Row>,
48}
49
50impl Rows {
51 pub fn new(rows: Vec<Row>) -> Self {
52 Self { rows }
53 }
54
55 pub fn is_empty(&self) -> bool {
56 self.rows.is_empty()
57 }
58
59 pub fn len(&self) -> usize {
60 self.rows.len()
61 }
62
63 pub fn first(&self) -> Option<&Row> {
64 self.rows.first()
65 }
66}
67
68#[derive(Debug, Clone)]
70pub struct Row {
71 columns: Vec<(String, SeaValue)>,
72}
73
74impl Row {
75 pub fn new(columns: Vec<(String, SeaValue)>) -> Self {
76 Self { columns }
77 }
78
79 pub fn column_names(&self) -> impl Iterator<Item = &str> {
81 self.columns.iter().map(|(name, _)| name.as_str())
82 }
83
84 pub fn get<T: TryFromValue>(&self, column: &str) -> Option<T> {
87 let value = self
88 .columns
89 .iter()
90 .find(|(name, _)| name == column)
91 .map(|(_, value)| value)?;
92 T::try_from_value(value)
93 }
94}
95
96pub trait TryFromValue: Sized {
98 fn try_from_value(value: &SeaValue) -> Option<Self>;
99}
100
101fn text(value: &SeaValue) -> Option<String> {
102 match value {
103 SeaValue::String(Some(s)) => Some((**s).clone()),
104 SeaValue::Char(Some(c)) => Some(c.to_string()),
105 _ => None,
106 }
107}
108
109macro_rules! impl_int {
110 ($($t:ty),* $(,)?) => {
111 $(
112 impl TryFromValue for $t {
113 fn try_from_value(value: &SeaValue) -> Option<Self> {
114 let i: i64 = match value {
115 SeaValue::TinyInt(Some(v)) => i64::from(*v),
116 SeaValue::SmallInt(Some(v)) => i64::from(*v),
117 SeaValue::Int(Some(v)) => i64::from(*v),
118 SeaValue::BigInt(Some(v)) => *v,
119 _ => return None,
120 };
121 <$t>::try_from(i).ok()
122 }
123 }
124 )*
125 };
126}
127
128impl_int!(i8, i16, i32, i64, u8, u16, u32, u64, usize);
129
130impl TryFromValue for String {
131 fn try_from_value(value: &SeaValue) -> Option<Self> {
132 text(value)
133 }
134}
135
136impl TryFromValue for Vec<u8> {
139 fn try_from_value(value: &SeaValue) -> Option<Self> {
140 match value {
141 SeaValue::Bytes(Some(bytes)) => Some(bytes.as_ref().clone()),
142 _ => None,
143 }
144 }
145}
146
147impl TryFromValue for bool {
148 fn try_from_value(value: &SeaValue) -> Option<Self> {
149 match value {
150 SeaValue::Bool(Some(v)) => Some(*v),
151 SeaValue::Int(Some(v)) => Some(*v != 0),
152 _ => None,
153 }
154 }
155}
156
157impl TryFromValue for f64 {
158 fn try_from_value(value: &SeaValue) -> Option<Self> {
159 match value {
160 SeaValue::Float(Some(v)) => Some(f64::from(*v)),
161 SeaValue::Double(Some(v)) => Some(*v),
162 _ => None,
163 }
164 }
165}
166
167impl TryFromValue for Option<String> {
168 fn try_from_value(value: &SeaValue) -> Option<Self> {
169 match value {
172 SeaValue::String(inner) => Some(inner.as_deref().map(String::from)),
173 _ => None,
174 }
175 }
176}
177
178impl TryFromValue for SeaValue {
179 fn try_from_value(value: &SeaValue) -> Option<Self> {
180 Some(value.clone())
181 }
182}
183
184#[derive(Debug, Clone, PartialEq, Eq, Error)]
186pub enum DbError {
187 #[error("execute failed: {0}")]
188 Execute(String),
189 #[error("query failed: {0}")]
190 Query(String),
191 #[error("batch failed: {0}")]
192 Batch(String),
193}
194
195#[async_trait]
202pub trait Database: Send + Sync {
203 async fn execute(&self, stmt: &Statement) -> Result<u64, DbError>;
204 async fn query(&self, stmt: &Statement) -> Result<Rows, DbError>;
205 async fn batch(&self, stmts: &[Statement]) -> Result<(), DbError>;
206}