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