1pub mod backend;
33pub mod connection;
34pub mod migration;
35pub mod pool;
36pub mod query;
37pub mod relation;
38pub mod repository;
39pub mod timestamp;
40mod decode;
41pub mod uuid;
42#[cfg(test)]
43mod tests;
44#[cfg(test)]
45mod backends;
46#[cfg(test)]
47mod convention_tests;
48#[cfg(test)]
49mod tls_tests;
50
51pub use backend::Backend;
52pub use connection::{DbConfig, DbSslMode, DbTls};
53pub use migration::MigrationStatus;
54pub use pool::{DbPool, DbTransaction};
55pub use query::{Order, QueryBuilder};
56pub use relation::{BelongsTo, HasMany, HasOne};
57pub use repository::{ModelRepository, PrimaryKey, Repository};
58pub use timestamp::Timestamp;
59pub use uuid::Uuid;
60pub use crate::pagination::{CursorPage, Page};
61
62#[derive(Debug, Clone, PartialEq)]
66pub enum Value {
67 Null,
68 Bool(bool),
69 Int(i64),
70 Float(f64),
71 Text(String),
72 Bytes(Vec<u8>),
73}
74
75#[derive(Debug)]
85pub struct DbError(pub String);
86
87impl std::fmt::Display for DbError {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 write!(f, "DbError: {}", self.0)
90 }
91}
92
93impl std::error::Error for DbError {}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98#[non_exhaustive]
99pub enum DbErrorKind {
100 StaleVersion,
104 NotFound,
106 Other,
108}
109
110const STALE_VERSION: &str = "stale version: ";
111const NOT_FOUND: &str = "not found: ";
112
113impl DbError {
114 pub fn new(msg: impl Into<String>) -> Self {
115 DbError(msg.into())
116 }
117
118 pub fn stale_version(detail: impl std::fmt::Display) -> Self {
121 DbError(format!("{STALE_VERSION}{detail}"))
122 }
123
124 pub fn not_found(detail: impl std::fmt::Display) -> Self {
126 DbError(format!("{NOT_FOUND}{detail}"))
127 }
128
129 pub fn kind(&self) -> DbErrorKind {
140 if self.0.starts_with(STALE_VERSION) {
141 DbErrorKind::StaleVersion
142 } else if self.0.starts_with(NOT_FOUND) {
143 DbErrorKind::NotFound
144 } else {
145 DbErrorKind::Other
146 }
147 }
148
149 pub fn is_stale_version(&self) -> bool {
151 self.kind() == DbErrorKind::StaleVersion
152 }
153
154 pub fn is_not_found(&self) -> bool {
156 self.kind() == DbErrorKind::NotFound
157 }
158}
159
160#[derive(Debug, Clone)]
164pub struct ModelRow {
165 #[doc(hidden)]
166 pub columns: Vec<(String, Value)>,
167}
168
169impl ModelRow {
170 pub fn new(columns: Vec<(String, Value)>) -> Self {
171 ModelRow { columns }
172 }
173
174 pub fn get<T: FromColumn>(&self, col: &str) -> Result<T, DbError> {
176 for (name, val) in &self.columns {
177 if name.eq_ignore_ascii_case(col) {
178 return T::from_column(val.clone());
179 }
180 }
181 Err(DbError::new(format!("column '{}' not found in row", col)))
182 }
183}
184
185pub trait FromColumn: Sized {
189 fn from_column(v: Value) -> Result<Self, DbError>;
190}
191
192pub trait ToColumn {
194 fn to_column(&self) -> Value;
195}
196
197impl FromColumn for i16 {
200 fn from_column(v: Value) -> Result<Self, DbError> {
201 match v {
202 Value::Int(n) => Ok(n as i16),
203 Value::Null => Err(DbError::new("unexpected NULL for i16")),
204 other => Err(DbError::new(format!("cannot convert {:?} to i16", other))),
205 }
206 }
207}
208
209impl FromColumn for i32 {
210 fn from_column(v: Value) -> Result<Self, DbError> {
211 match v {
212 Value::Int(n) => Ok(n as i32),
213 Value::Null => Err(DbError::new("unexpected NULL for i32")),
214 other => Err(DbError::new(format!("cannot convert {:?} to i32", other))),
215 }
216 }
217}
218
219impl FromColumn for i64 {
220 fn from_column(v: Value) -> Result<Self, DbError> {
221 match v {
222 Value::Int(n) => Ok(n),
223 Value::Null => Err(DbError::new("unexpected NULL for i64")),
224 other => Err(DbError::new(format!("cannot convert {:?} to i64", other))),
225 }
226 }
227}
228
229impl FromColumn for u32 {
230 fn from_column(v: Value) -> Result<Self, DbError> {
231 match v {
232 Value::Int(n) => Ok(n as u32),
233 Value::Null => Err(DbError::new("unexpected NULL for u32")),
234 other => Err(DbError::new(format!("cannot convert {:?} to u32", other))),
235 }
236 }
237}
238
239impl FromColumn for u64 {
240 fn from_column(v: Value) -> Result<Self, DbError> {
241 match v {
242 Value::Int(n) => Ok(n as u64),
243 Value::Null => Err(DbError::new("unexpected NULL for u64")),
244 other => Err(DbError::new(format!("cannot convert {:?} to u64", other))),
245 }
246 }
247}
248
249impl FromColumn for f32 {
250 fn from_column(v: Value) -> Result<Self, DbError> {
251 match v {
252 Value::Float(f) => Ok(f as f32),
253 Value::Int(n) => Ok(n as f32),
254 Value::Null => Err(DbError::new("unexpected NULL for f32")),
255 other => Err(DbError::new(format!("cannot convert {:?} to f32", other))),
256 }
257 }
258}
259
260impl FromColumn for f64 {
261 fn from_column(v: Value) -> Result<Self, DbError> {
262 match v {
263 Value::Float(f) => Ok(f),
264 Value::Int(n) => Ok(n as f64),
265 Value::Null => Err(DbError::new("unexpected NULL for f64")),
266 other => Err(DbError::new(format!("cannot convert {:?} to f64", other))),
267 }
268 }
269}
270
271impl FromColumn for bool {
272 fn from_column(v: Value) -> Result<Self, DbError> {
273 match v {
274 Value::Bool(b) => Ok(b),
275 Value::Int(n) => Ok(n != 0),
276 Value::Null => Err(DbError::new("unexpected NULL for bool")),
277 other => Err(DbError::new(format!("cannot convert {:?} to bool", other))),
278 }
279 }
280}
281
282impl FromColumn for String {
283 fn from_column(v: Value) -> Result<Self, DbError> {
284 match v {
285 Value::Text(s) => Ok(s),
286 Value::Null => Err(DbError::new("unexpected NULL for String")),
287 other => Err(DbError::new(format!("cannot convert {:?} to String", other))),
288 }
289 }
290}
291
292impl FromColumn for Value {
294 fn from_column(v: Value) -> Result<Self, DbError> {
295 Ok(v)
296 }
297}
298
299impl<T: FromColumn> FromColumn for Option<T> {
300 fn from_column(v: Value) -> Result<Self, DbError> {
301 match v {
302 Value::Null => Ok(None),
303 other => Ok(Some(T::from_column(other)?)),
304 }
305 }
306}
307
308impl ToColumn for i16 {
311 fn to_column(&self) -> Value { Value::Int(*self as i64) }
312}
313impl ToColumn for i32 {
314 fn to_column(&self) -> Value { Value::Int(*self as i64) }
315}
316impl ToColumn for i64 {
317 fn to_column(&self) -> Value { Value::Int(*self) }
318}
319impl ToColumn for u32 {
320 fn to_column(&self) -> Value { Value::Int(*self as i64) }
321}
322impl ToColumn for u64 {
323 fn to_column(&self) -> Value { Value::Int(*self as i64) }
324}
325impl ToColumn for f32 {
326 fn to_column(&self) -> Value { Value::Float(*self as f64) }
327}
328impl ToColumn for f64 {
329 fn to_column(&self) -> Value { Value::Float(*self) }
330}
331impl ToColumn for bool {
332 fn to_column(&self) -> Value { Value::Bool(*self) }
333}
334impl ToColumn for String {
335 fn to_column(&self) -> Value { Value::Text(self.clone()) }
336}
337impl ToColumn for str {
338 fn to_column(&self) -> Value { Value::Text(self.to_owned()) }
339}
340impl ToColumn for &str {
341 fn to_column(&self) -> Value { Value::Text((*self).to_owned()) }
342}
343impl<T: ToColumn> ToColumn for Option<T> {
344 fn to_column(&self) -> Value {
345 match self {
346 Some(v) => v.to_column(),
347 None => Value::Null,
348 }
349 }
350}
351
352pub trait Model: Sized + Send + Sync {
356 fn table_name() -> &'static str;
358
359 fn column_names() -> &'static [&'static str];
361
362 fn primary_key_name() -> &'static str;
364
365 fn primary_key_value(&self) -> Value;
367
368 fn primary_key_auto_increment() -> bool {
370 false
371 }
372
373 fn from_row(row: &ModelRow) -> Result<Self, DbError>;
375
376 fn to_values(&self) -> Vec<(&'static str, Value)>;
379
380 fn created_at_column() -> Option<&'static str> {
383 None
384 }
385
386 fn updated_at_column() -> Option<&'static str> {
389 None
390 }
391
392 fn soft_delete_column() -> Option<&'static str> {
396 None
397 }
398
399 fn version_column() -> Option<&'static str> {
403 None
404 }
405}
406
407#[derive(Clone, Debug, PartialEq, Eq)]
418pub struct ModelInfo {
419 pub type_name: &'static str,
421 pub table: &'static str,
423 pub columns: &'static [&'static str],
425 pub primary_key: &'static str,
427 pub auto_increment: bool,
429}
430
431impl ModelInfo {
432 pub fn of<T: Model>() -> Self {
434 ModelInfo {
435 type_name: std::any::type_name::<T>(),
436 table: T::table_name(),
437 columns: T::column_names(),
438 primary_key: T::primary_key_name(),
439 auto_increment: T::primary_key_auto_increment(),
440 }
441 }
442
443 pub fn short_name(&self) -> &'static str {
446 let base = self.type_name.split('<').next().unwrap_or(self.type_name);
447 base.rsplit("::").next().unwrap_or(base)
448 }
449}