Skip to main content

nextsql_backend_rust_runtime/
lib.rs

1use std::future::Future;
2
3/// Represents a field in a partial update operation.
4/// - `Unchanged`: field is not included in the SET clause
5/// - `Set(T)`: field is set to the given value (use `Set(None)` for nullable columns to set NULL)
6#[derive(Debug, Clone, PartialEq)]
7pub enum UpdateField<T> {
8    Unchanged,
9    Set(T),
10}
11
12impl<T> UpdateField<T> {
13    pub fn is_set(&self) -> bool {
14        matches!(self, UpdateField::Set(_))
15    }
16
17    pub fn is_unchanged(&self) -> bool {
18        matches!(self, UpdateField::Unchanged)
19    }
20}
21
22impl<T> Default for UpdateField<T> {
23    fn default() -> Self {
24        UpdateField::Unchanged
25    }
26}
27
28/// Trait for values that can be bound as SQL query parameters.
29pub trait ToSqlParam: Send + Sync {
30    /// Convert this value into a format the database driver can accept.
31    /// The returned Any should be downcastable to the backend's native param type.
32    fn as_any(&self) -> &(dyn std::any::Any + Send + Sync);
33}
34
35/// Trait for reading typed values from a database result row.
36pub trait Row: Send {
37    fn get_i16(&self, idx: usize) -> i16;
38    fn get_i32(&self, idx: usize) -> i32;
39    fn get_i64(&self, idx: usize) -> i64;
40    fn get_f32(&self, idx: usize) -> f32;
41    fn get_f64(&self, idx: usize) -> f64;
42    fn get_string(&self, idx: usize) -> String;
43    fn get_bool(&self, idx: usize) -> bool;
44    fn get_uuid(&self, idx: usize) -> uuid::Uuid;
45    fn get_timestamp(&self, idx: usize) -> chrono::NaiveDateTime;
46    fn get_timestamptz(&self, idx: usize) -> chrono::DateTime<chrono::Utc>;
47    fn get_date(&self, idx: usize) -> chrono::NaiveDate;
48    fn get_decimal(&self, idx: usize) -> rust_decimal::Decimal;
49    fn get_json(&self, idx: usize) -> serde_json::Value;
50
51    // Optional variants for nullable columns
52    fn get_opt_i16(&self, idx: usize) -> Option<i16>;
53    fn get_opt_i32(&self, idx: usize) -> Option<i32>;
54    fn get_opt_i64(&self, idx: usize) -> Option<i64>;
55    fn get_opt_f32(&self, idx: usize) -> Option<f32>;
56    fn get_opt_f64(&self, idx: usize) -> Option<f64>;
57    fn get_opt_string(&self, idx: usize) -> Option<String>;
58    fn get_opt_bool(&self, idx: usize) -> Option<bool>;
59    fn get_opt_uuid(&self, idx: usize) -> Option<uuid::Uuid>;
60    fn get_opt_timestamp(&self, idx: usize) -> Option<chrono::NaiveDateTime>;
61    fn get_opt_timestamptz(&self, idx: usize) -> Option<chrono::DateTime<chrono::Utc>>;
62    fn get_opt_date(&self, idx: usize) -> Option<chrono::NaiveDate>;
63    fn get_opt_decimal(&self, idx: usize) -> Option<rust_decimal::Decimal>;
64    fn get_opt_json(&self, idx: usize) -> Option<serde_json::Value>;
65
66    // Array variants
67    fn get_vec_i16(&self, idx: usize) -> Vec<i16>;
68    fn get_vec_i32(&self, idx: usize) -> Vec<i32>;
69    fn get_vec_i64(&self, idx: usize) -> Vec<i64>;
70    fn get_vec_f32(&self, idx: usize) -> Vec<f32>;
71    fn get_vec_f64(&self, idx: usize) -> Vec<f64>;
72    fn get_vec_string(&self, idx: usize) -> Vec<String>;
73    fn get_vec_bool(&self, idx: usize) -> Vec<bool>;
74    fn get_vec_uuid(&self, idx: usize) -> Vec<uuid::Uuid>;
75}
76
77/// Trait for executing SQL queries against a database.
78/// Uses `impl Future` instead of async_trait to avoid proc macro compile overhead.
79///
80/// This trait is backend-agnostic: implementations exist for PostgreSQL (tokio-postgres),
81/// and can be added for MySQL, SQLite, etc.
82pub trait Client: Send + Sync {
83    type Error: std::error::Error + Send + Sync + 'static;
84    type Row: Row;
85    type Transaction<'a>: Transaction<Error = Self::Error, Row = Self::Row> + 'a where Self: 'a;
86
87    /// Execute a query that returns rows.
88    fn query(
89        &self,
90        sql: &str,
91        params: &[&dyn ToSqlParam],
92    ) -> impl Future<Output = Result<Vec<Self::Row>, Self::Error>> + Send;
93
94    /// Execute a statement that returns the number of affected rows.
95    fn execute(
96        &self,
97        sql: &str,
98        params: &[&dyn ToSqlParam],
99    ) -> impl Future<Output = Result<u64, Self::Error>> + Send;
100
101    /// Begin a new transaction.
102    fn transaction(&mut self) -> impl Future<Output = Result<Self::Transaction<'_>, Self::Error>> + Send;
103}
104
105/// Trait for a database transaction.
106/// Supports the same query/execute operations as `Client`, plus commit/rollback.
107/// Dropping without calling `commit()` should rollback the transaction.
108pub trait Transaction: Send {
109    type Error: std::error::Error + Send + Sync + 'static;
110    type Row: Row;
111    type Nested<'a>: Transaction<Error = Self::Error, Row = Self::Row> + 'a where Self: 'a;
112
113    /// Execute a query that returns rows.
114    fn query(
115        &self,
116        sql: &str,
117        params: &[&dyn ToSqlParam],
118    ) -> impl Future<Output = Result<Vec<Self::Row>, Self::Error>> + Send;
119
120    /// Execute a statement that returns the number of affected rows.
121    fn execute(
122        &self,
123        sql: &str,
124        params: &[&dyn ToSqlParam],
125    ) -> impl Future<Output = Result<u64, Self::Error>> + Send;
126
127    /// Begin a nested transaction (savepoint).
128    fn transaction(&mut self) -> impl Future<Output = Result<Self::Nested<'_>, Self::Error>> + Send;
129
130    /// Commit this transaction.
131    fn commit(self) -> impl Future<Output = Result<(), Self::Error>> + Send;
132
133    /// Rollback this transaction.
134    fn rollback(self) -> impl Future<Output = Result<(), Self::Error>> + Send;
135}
136
137// ---- ToSqlParam implementations for primitive types ----
138
139macro_rules! impl_to_sql_param {
140    ($($ty:ty),*) => {
141        $(
142            impl ToSqlParam for $ty {
143                fn as_any(&self) -> &(dyn std::any::Any + Send + Sync) {
144                    self
145                }
146            }
147        )*
148    };
149}
150
151impl_to_sql_param!(
152    i16, i32, i64, f32, f64, bool, String,
153    uuid::Uuid, chrono::NaiveDateTime, chrono::NaiveDate,
154    rust_decimal::Decimal
155);
156
157impl ToSqlParam for chrono::DateTime<chrono::Utc> {
158    fn as_any(&self) -> &(dyn std::any::Any + Send + Sync) {
159        self
160    }
161}
162
163impl ToSqlParam for serde_json::Value {
164    fn as_any(&self) -> &(dyn std::any::Any + Send + Sync) {
165        self
166    }
167}
168
169impl ToSqlParam for &'static str {
170    fn as_any(&self) -> &(dyn std::any::Any + Send + Sync) {
171        self
172    }
173}
174
175impl<T: ToSqlParam + 'static> ToSqlParam for Option<T> {
176    fn as_any(&self) -> &(dyn std::any::Any + Send + Sync) {
177        self
178    }
179}
180
181impl<T: ToSqlParam + 'static> ToSqlParam for Vec<T> {
182    fn as_any(&self) -> &(dyn std::any::Any + Send + Sync) {
183        self
184    }
185}
186
187impl<T: ToSqlParam + 'static> ToSqlParam for UpdateField<T> {
188    fn as_any(&self) -> &(dyn std::any::Any + Send + Sync) {
189        match self {
190            UpdateField::Set(v) => v.as_any(),
191            UpdateField::Unchanged => panic!("Unchanged fields should not be passed as SQL params"),
192        }
193    }
194}
195
196#[cfg(feature = "backend-tokio-postgres")]
197pub mod tokio_postgres_impl;