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