libsql_orm/
model.rs

1//! Model trait and core database operations
2//!
3//! This module provides the main Model trait that enables ORM functionality
4//! for structs that derive from it. Features include:
5//!
6//! - **Custom Table Names**: Use `#[table_name("custom")]` to override default naming
7//! - **Boolean Type Safety**: Automatic conversion between SQLite integers (0/1) and Rust booleans
8//! - **Column Attributes**: Customize column properties with `#[orm_column(...)]`
9//! - **Full CRUD Operations**: Create, read, update, delete with type safety
10//!
11//! # Examples
12//!
13//! ```rust
14//! use libsql_orm::Model;
15//! use serde::{Serialize, Deserialize};
16//!
17//! #[derive(Model, Serialize, Deserialize)]
18//! #[table_name("user_accounts")]  // Custom table name
19//! struct User {
20//!     pub id: Option<i64>,
21//!     pub name: String,
22//!     pub is_active: bool,     // ✅ Automatic boolean conversion
23//!     pub is_verified: bool,   // ✅ Type-safe operations
24//! }
25//! ```
26
27use crate::{
28    Aggregate, Database, Error, FilterOperator, PaginatedResult, Pagination, QueryBuilder, Result,
29    SearchFilter, Sort,
30};
31use serde::{de::DeserializeOwned, Serialize};
32use std::collections::HashMap;
33
34/// Core trait for all database models
35#[allow(async_fn_in_trait)]
36pub trait Model: Serialize + DeserializeOwned + Send + Sync + Clone {
37    /// Get the table name for this model
38    fn table_name() -> &'static str;
39
40    /// Get the primary key column name
41    fn primary_key() -> &'static str {
42        "id"
43    }
44
45    /// Get the primary key value
46    fn get_primary_key(&self) -> Option<i64>;
47
48    /// Set the primary key value
49    fn set_primary_key(&mut self, id: i64);
50
51    /// Get all column names for this model
52    fn columns() -> Vec<&'static str>;
53
54    /// Generate SQL for creating the table
55    fn migration_sql() -> String;
56
57    /// Convert the model to a HashMap for database operations
58    fn to_map(&self) -> Result<HashMap<String, crate::Value>>;
59
60    /// Create a model from a HashMap
61    fn from_map(map: HashMap<String, crate::Value>) -> Result<Self>;
62
63    /// Create a new record in the database
64    async fn create(&self, db: &Database) -> Result<Self> {
65        let map = self.to_map()?;
66        let columns: Vec<String> = map.keys().cloned().collect();
67        let values: Vec<String> = map.keys().map(|_| "?".to_string()).collect();
68
69        let sql = format!(
70            "INSERT INTO {} ({}) VALUES ({})",
71            Self::table_name(),
72            columns.join(", "),
73            values.join(", ")
74        );
75
76        Self::log_info(&format!("Creating record in table: {}", Self::table_name()));
77        Self::log_debug(&format!("SQL: {sql}"));
78
79        let params: Vec<libsql::Value> = map
80            .values()
81            .map(|v| Self::value_to_libsql_value(v))
82            .collect();
83
84        db.inner.execute(&sql, params).await?;
85        let id = 1i64; // Placeholder - libsql WASM doesn't support last_insert_rowid
86
87        let mut result = self.clone();
88        result.set_primary_key(id);
89
90        Self::log_info(&format!("Successfully created record with ID: {id}"));
91        Ok(result)
92    }
93
94    /// Create or update a record based on whether it has a primary key
95    async fn create_or_update(&self, db: &Database) -> Result<Self> {
96        if let Some(id) = self.get_primary_key() {
97            Self::log_info(&format!("Updating existing record with ID: {id}"));
98            // Check if record exists
99            match Self::find_by_id(id, db).await? {
100                Some(_) => {
101                    // Record exists, update it
102                    self.update(db).await
103                }
104                None => {
105                    // Record doesn't exist, create it
106                    Self::log_warn(&format!(
107                        "Record with ID {id} not found, creating new record"
108                    ));
109                    self.create(db).await
110                }
111            }
112        } else {
113            // No primary key, create new record
114            Self::log_info("Creating new record (no primary key provided)");
115            self.create(db).await
116        }
117    }
118
119    /// Create or update a record based on unique constraints
120    async fn upsert(&self, unique_columns: &[&str], db: &Database) -> Result<Self> {
121        let map = self.to_map()?;
122
123        // Build WHERE clause for unique columns
124        let mut where_conditions = Vec::new();
125        let mut where_params = Vec::new();
126
127        for &column in unique_columns {
128            if let Some(value) = map.get(column) {
129                where_conditions.push(format!("{column} = ?"));
130                where_params.push(Self::value_to_libsql_value(value));
131            }
132        }
133
134        if where_conditions.is_empty() {
135            return Err(Error::Validation(
136                "No unique columns provided for upsert".to_string(),
137            ));
138        }
139
140        let where_clause = where_conditions.join(" AND ");
141        let sql = format!(
142            "SELECT {} FROM {} WHERE {}",
143            Self::primary_key(),
144            Self::table_name(),
145            where_clause
146        );
147
148        Self::log_info(&format!(
149            "Checking for existing record in table: {}",
150            Self::table_name()
151        ));
152        Self::log_debug(&format!("SQL: {sql}"));
153
154        let mut rows = db.inner.query(&sql, where_params).await?;
155
156        if let Some(row) = rows.next().await? {
157            // Record exists, update it
158            if let Some(existing_id) = row.get_value(0).ok().and_then(|v| match v {
159                libsql::Value::Integer(i) => Some(i),
160                _ => None,
161            }) {
162                Self::log_info(&format!(
163                    "Found existing record with ID: {existing_id}, updating"
164                ));
165                let mut updated_self = self.clone();
166                updated_self.set_primary_key(existing_id);
167                updated_self.update(db).await
168            } else {
169                Err(Error::Query(
170                    "Failed to get primary key from existing record".to_string(),
171                ))
172            }
173        } else {
174            // Record doesn't exist, create it
175            Self::log_info("No existing record found, creating new one");
176            self.create(db).await
177        }
178    }
179
180    /// Create multiple records in the database
181    async fn bulk_create(models: &[Self], db: &Database) -> Result<Vec<Self>> {
182        if models.is_empty() {
183            return Ok(Vec::new());
184        }
185
186        let mut results = Vec::new();
187        // Note: Manual transaction handling for WASM
188        db.inner
189            .execute("BEGIN", vec![libsql::Value::Null; 0])
190            .await?;
191
192        for model in models {
193            let map = model.to_map()?;
194            let columns: Vec<String> = map.keys().cloned().collect();
195            let values: Vec<String> = map.keys().map(|_| "?".to_string()).collect();
196
197            let sql = format!(
198                "INSERT INTO {} ({}) VALUES ({})",
199                Self::table_name(),
200                columns.join(", "),
201                values.join(", ")
202            );
203
204            let params: Vec<libsql::Value> = map
205                .values()
206                .map(|v| Self::value_to_libsql_value(v))
207                .collect();
208
209            db.inner.execute(&sql, params).await?;
210            let id = 1i64; // Placeholder - libsql WASM doesn't support last_insert_rowid
211
212            let mut result = model.clone();
213            result.set_primary_key(id);
214            results.push(result);
215        }
216
217        db.inner
218            .execute("COMMIT", vec![libsql::Value::Null; 0])
219            .await?;
220        Ok(results)
221    }
222
223    /// Find a record by its primary key
224    async fn find_by_id(id: i64, db: &Database) -> Result<Option<Self>> {
225        let sql = format!(
226            "SELECT * FROM {} WHERE {} = ?",
227            Self::table_name(),
228            Self::primary_key()
229        );
230
231        Self::log_debug(&format!("Finding record by ID: {id}"));
232        Self::log_debug(&format!("SQL: {sql}"));
233
234        let mut rows = db
235            .inner
236            .query(&sql, vec![libsql::Value::Integer(id)])
237            .await?;
238
239        if let Some(row) = rows.next().await? {
240            let map = Self::row_to_map(&row)?;
241            Self::log_debug(&format!("Found record with ID: {id}"));
242            Ok(Some(Self::from_map(map)?))
243        } else {
244            Self::log_debug(&format!("No record found with ID: {id}"));
245            Ok(None)
246        }
247    }
248
249    /// Find a single record by a specific condition
250    async fn find_one(filter: FilterOperator, db: &Database) -> Result<Option<Self>> {
251        let builder = QueryBuilder::new(Self::table_name())
252            .r#where(filter)
253            .limit(1);
254
255        let results = builder.execute::<Self>(db).await?;
256        Ok(results.into_iter().next())
257    }
258
259    /// Find all records
260    async fn find_all(db: &Database) -> Result<Vec<Self>> {
261        let builder = QueryBuilder::new(Self::table_name());
262        builder.execute::<Self>(db).await
263    }
264
265    /// Find records with a filter
266    async fn find_where(filter: FilterOperator, db: &Database) -> Result<Vec<Self>> {
267        let builder = QueryBuilder::new(Self::table_name()).r#where(filter);
268        builder.execute::<Self>(db).await
269    }
270
271    /// Find records with pagination
272    async fn find_paginated(
273        pagination: &Pagination,
274        db: &Database,
275    ) -> Result<PaginatedResult<Self>> {
276        let builder = QueryBuilder::new(Self::table_name());
277        builder.execute_paginated::<Self>(db, pagination).await
278    }
279
280    /// Find records with filter and pagination
281    async fn find_where_paginated(
282        filter: FilterOperator,
283        pagination: &Pagination,
284        db: &Database,
285    ) -> Result<PaginatedResult<Self>> {
286        let builder = QueryBuilder::new(Self::table_name()).r#where(filter);
287        builder.execute_paginated::<Self>(db, pagination).await
288    }
289
290    /// Search records with text search
291    async fn search(
292        search_filter: &SearchFilter,
293        pagination: Option<&Pagination>,
294        db: &Database,
295    ) -> Result<PaginatedResult<Self>> {
296        let filter = search_filter.to_filter_operator();
297        let pagination = pagination.unwrap_or(&Pagination::default()).clone();
298
299        Self::find_where_paginated(filter, &pagination, db).await
300    }
301
302    /// Count all records
303    async fn count(db: &Database) -> Result<u64> {
304        let sql = format!("SELECT COUNT(*) FROM {}", Self::table_name());
305        let mut rows = db.inner.query(&sql, vec![libsql::Value::Null; 0]).await?;
306
307        if let Some(row) = rows.next().await? {
308            row.get_value(0)
309                .ok()
310                .and_then(|v| match v {
311                    libsql::Value::Integer(i) => Some(i as u64),
312                    _ => None,
313                })
314                .ok_or_else(|| Error::Query("Failed to get count".to_string()))
315        } else {
316            Err(Error::Query("No count result".to_string()))
317        }
318    }
319
320    /// Count records with a filter
321    async fn count_where(filter: FilterOperator, db: &Database) -> Result<u64> {
322        let builder = QueryBuilder::new(Self::table_name()).r#where(filter);
323
324        let (sql, params) = builder.build_count()?;
325        let mut rows = db.inner.query(&sql, params).await?;
326
327        if let Some(row) = rows.next().await? {
328            row.get_value(0)
329                .ok()
330                .and_then(|v| match v {
331                    libsql::Value::Integer(i) => Some(i as u64),
332                    _ => None,
333                })
334                .ok_or_else(|| Error::Query("Failed to get count".to_string()))
335        } else {
336            Err(Error::Query("No count result".to_string()))
337        }
338    }
339
340    /// Update a record
341    async fn update(&self, db: &Database) -> Result<Self> {
342        let id = self.get_primary_key().ok_or_else(|| {
343            Error::Validation("Cannot update record without primary key".to_string())
344        })?;
345
346        let map = self.to_map()?;
347        let set_clauses: Vec<String> = map
348            .keys()
349            .filter(|&k| k != Self::primary_key())
350            .map(|k| format!("{k} = ?"))
351            .collect();
352
353        let sql = format!(
354            "UPDATE {} SET {} WHERE {} = ?",
355            Self::table_name(),
356            set_clauses.join(", "),
357            Self::primary_key()
358        );
359
360        Self::log_info(&format!("Updating record with ID: {id}"));
361        Self::log_debug(&format!("SQL: {sql}"));
362
363        let mut params: Vec<libsql::Value> = map
364            .iter()
365            .filter(|(k, _)| k != &Self::primary_key())
366            .map(|(_, v)| Self::value_to_libsql_value(v))
367            .collect();
368        params.push(libsql::Value::Integer(id));
369
370        db.inner.execute(&sql, params).await?;
371        Self::log_info(&format!("Successfully updated record with ID: {id}"));
372        Ok(self.clone())
373    }
374
375    /// Update multiple records
376    async fn bulk_update(models: &[Self], db: &Database) -> Result<Vec<Self>> {
377        if models.is_empty() {
378            return Ok(Vec::new());
379        }
380
381        let mut results = Vec::new();
382        // Note: Manual transaction handling for WASM
383        db.inner
384            .execute("BEGIN", vec![libsql::Value::Null; 0])
385            .await?;
386
387        for model in models {
388            let result = model.update(db).await?;
389            results.push(result);
390        }
391
392        db.inner
393            .execute("COMMIT", vec![libsql::Value::Null; 0])
394            .await?;
395        Ok(results)
396    }
397
398    /// Delete a record
399    async fn delete(&self, db: &Database) -> Result<bool> {
400        let id = self.get_primary_key().ok_or_else(|| {
401            Error::Validation("Cannot delete record without primary key".to_string())
402        })?;
403
404        let sql = format!(
405            "DELETE FROM {} WHERE {} = ?",
406            Self::table_name(),
407            Self::primary_key()
408        );
409
410        Self::log_info(&format!("Deleting record with ID: {id}"));
411        Self::log_debug(&format!("SQL: {sql}"));
412
413        db.inner
414            .execute(&sql, vec![libsql::Value::Integer(id)])
415            .await?;
416        Self::log_info(&format!("Successfully deleted record with ID: {id}"));
417        Ok(true)
418    }
419
420    /// Delete multiple records
421    async fn bulk_delete(ids: &[i64], db: &Database) -> Result<u64> {
422        if ids.is_empty() {
423            return Ok(0);
424        }
425
426        let placeholders: Vec<String> = ids.iter().map(|_| "?".to_string()).collect();
427        let sql = format!(
428            "DELETE FROM {} WHERE {} IN ({})",
429            Self::table_name(),
430            Self::primary_key(),
431            placeholders.join(", ")
432        );
433
434        let params: Vec<libsql::Value> = ids.iter().map(|&id| libsql::Value::Integer(id)).collect();
435        db.inner.execute(&sql, params).await?;
436        Ok(ids.len() as u64)
437    }
438
439    /// Delete records with a filter
440    async fn delete_where(filter: FilterOperator, db: &Database) -> Result<u64> {
441        let builder = QueryBuilder::new(Self::table_name()).r#where(filter);
442
443        let (sql, params) = builder.build()?;
444        let delete_sql = sql.replace("SELECT *", "DELETE");
445        db.inner.execute(&delete_sql, params).await?;
446
447        // Note: SQLite doesn't return the number of affected rows directly
448        // This is a simplified implementation
449        Ok(1)
450    }
451
452    /// List records with optional sorting and pagination
453    async fn list(
454        sort: Option<Vec<Sort>>,
455        pagination: Option<&Pagination>,
456        db: &Database,
457    ) -> Result<PaginatedResult<Self>> {
458        let mut builder = QueryBuilder::new(Self::table_name());
459
460        if let Some(sorts) = sort {
461            builder = builder.order_by_multiple(sorts);
462        }
463
464        let pagination = pagination.unwrap_or(&Pagination::default()).clone();
465        builder.execute_paginated::<Self>(db, &pagination).await
466    }
467
468    /// List records with filter, sorting, and pagination
469    async fn list_where(
470        filter: FilterOperator,
471        sort: Option<Vec<Sort>>,
472        pagination: Option<&Pagination>,
473        db: &Database,
474    ) -> Result<PaginatedResult<Self>> {
475        let mut builder = QueryBuilder::new(Self::table_name()).r#where(filter);
476
477        if let Some(sorts) = sort {
478            builder = builder.order_by_multiple(sorts);
479        }
480
481        let pagination = pagination.unwrap_or(&Pagination::default()).clone();
482        builder.execute_paginated::<Self>(db, &pagination).await
483    }
484
485    /// Execute a custom query
486    async fn query(builder: QueryBuilder, db: &Database) -> Result<Vec<Self>> {
487        builder.execute::<Self>(db).await
488    }
489
490    /// Execute a custom query with pagination
491    async fn query_paginated(
492        builder: QueryBuilder,
493        pagination: &Pagination,
494        db: &Database,
495    ) -> Result<PaginatedResult<Self>> {
496        builder.execute_paginated::<Self>(db, pagination).await
497    }
498
499    /// Get aggregate value
500    async fn aggregate(
501        function: Aggregate,
502        column: &str,
503        filter: Option<FilterOperator>,
504        db: &Database,
505    ) -> Result<Option<f64>> {
506        let mut builder =
507            QueryBuilder::new(Self::table_name()).aggregate(function, column, None::<String>);
508
509        if let Some(filter) = filter {
510            builder = builder.r#where(filter);
511        }
512
513        let (sql, params) = builder.build()?;
514        let mut rows = db.inner.query(&sql, params).await?;
515
516        if let Some(row) = rows.next().await? {
517            let value = row
518                .get_value(0)
519                .ok()
520                .and_then(|v| match v {
521                    libsql::Value::Integer(i) => Some(i as f64),
522                    libsql::Value::Real(f) => Some(f),
523                    _ => None,
524                })
525                .ok_or_else(|| Error::Query("Failed to get aggregate value".to_string()))?;
526            Ok(Some(value))
527        } else {
528            Ok(None)
529        }
530    }
531
532    /// Convert a database row to a HashMap
533    fn row_to_map(row: &libsql::Row) -> Result<HashMap<String, crate::Value>> {
534        let mut map = HashMap::new();
535        for i in 0..row.column_count() {
536            if let Some(column_name) = row.column_name(i) {
537                let value = row.get_value(i).unwrap_or(libsql::Value::Null);
538                map.insert(column_name.to_string(), Self::libsql_value_to_value(&value));
539            }
540        }
541        Ok(map)
542    }
543
544    /// Convert our Value type to libsql::Value
545    fn value_to_libsql_value(value: &crate::Value) -> libsql::Value {
546        match value {
547            crate::Value::Null => libsql::Value::Null,
548            crate::Value::Integer(i) => libsql::Value::Integer(*i),
549            crate::Value::Real(f) => libsql::Value::Real(*f),
550            crate::Value::Text(s) => libsql::Value::Text(s.clone()),
551            crate::Value::Blob(b) => libsql::Value::Blob(b.clone()),
552            crate::Value::Boolean(b) => libsql::Value::Integer(if *b { 1 } else { 0 }),
553        }
554    }
555
556    /// Convert libsql::Value to our Value type
557    fn libsql_value_to_value(value: &libsql::Value) -> crate::Value {
558        match value {
559            libsql::Value::Null => crate::Value::Null,
560            libsql::Value::Integer(i) => crate::Value::Integer(*i),
561            libsql::Value::Real(f) => crate::Value::Real(*f),
562            libsql::Value::Text(s) => crate::Value::Text(s.clone()),
563            libsql::Value::Blob(b) => crate::Value::Blob(b.clone()),
564        }
565    }
566
567    /// Log an info message
568    fn log_info(message: &str) {
569        #[cfg(target_arch = "wasm32")]
570        {
571            web_sys::console::log_1(&format!("[INFO] {}: {}", Self::table_name(), message).into());
572        }
573        #[cfg(not(target_arch = "wasm32"))]
574        {
575            log::info!("[{}] {}", Self::table_name(), message);
576        }
577    }
578
579    /// Log a debug message
580    fn log_debug(message: &str) {
581        #[cfg(target_arch = "wasm32")]
582        {
583            web_sys::console::log_1(&format!("[DEBUG] {}: {}", Self::table_name(), message).into());
584        }
585        #[cfg(not(target_arch = "wasm32"))]
586        {
587            log::debug!("[{}] {}", Self::table_name(), message);
588        }
589    }
590
591    /// Log a warning message
592    fn log_warn(message: &str) {
593        #[cfg(target_arch = "wasm32")]
594        {
595            web_sys::console::warn_1(&format!("[WARN] {}: {}", Self::table_name(), message).into());
596        }
597        #[cfg(not(target_arch = "wasm32"))]
598        {
599            log::warn!("[{}] {}", Self::table_name(), message);
600        }
601    }
602
603    /// Log an error message
604    fn log_error(message: &str) {
605        #[cfg(target_arch = "wasm32")]
606        {
607            web_sys::console::error_1(
608                &format!("[ERROR] {}: {}", Self::table_name(), message).into(),
609            );
610        }
611        #[cfg(not(target_arch = "wasm32"))]
612        {
613            log::error!("[{}] {}", Self::table_name(), message);
614        }
615    }
616}