Skip to main content

sqlmodel/
lib.rs

1//! SQLModel Rust - SQL databases in Rust, designed to be intuitive and type-safe.
2//!
3//! `sqlmodel` is the **facade crate** for the entire SQLModel Rust ecosystem. It re-exports
4//! the core traits, macros, query builders, schema/migration tooling, session layer, pooling,
5//! and optional console integration so most applications only need a single dependency.
6//!
7//! # Role In The Architecture
8//!
9//! - **One-stop import**: `use sqlmodel::prelude::*;` gives you `Model`, `Connection`,
10//!   `Expr`, and the query macros.
11//! - **Facade over sub-crates**: wraps `sqlmodel-core`, `sqlmodel-macros`, `sqlmodel-query`,
12//!   `sqlmodel-schema`, `sqlmodel-session`, and `sqlmodel-pool`.
13//! - **Optional console**: feature-gated integration with `sqlmodel-console` for rich output.
14//!
15//! # When To Use This Crate
16//!
17//! Use `sqlmodel` for nearly all application code. Reach for the sub-crates directly only
18//! if you're extending internals or building an alternative facade.
19//!
20//! # Quick Start
21//!
22//! ```ignore
23//! use sqlmodel::prelude::*;
24//!
25//! #[derive(Model, Debug)]
26//! #[sqlmodel(table = "heroes")]
27//! struct Hero {
28//!     #[sqlmodel(primary_key, auto_increment)]
29//!     id: Option<i64>,
30//!     name: String,
31//!     secret_name: String,
32//!     age: Option<i32>,
33//! }
34//!
35//! async fn main_example(cx: &Cx, conn: &impl Connection) -> Outcome<(), Error> {
36//!     let hero = Hero {
37//!         id: None,
38//!         name: "Spider-Man".to_string(),
39//!         secret_name: "Peter Parker".to_string(),
40//!         age: Some(25),
41//!     };
42//!
43//!     let _id = match insert!(hero).execute(cx, conn).await {
44//!         Outcome::Ok(v) => v,
45//!         Outcome::Err(e) => return Outcome::Err(e),
46//!         Outcome::Cancelled(r) => return Outcome::Cancelled(r),
47//!         Outcome::Panicked(p) => return Outcome::Panicked(p),
48//!     };
49//!
50//!     let heroes = match select!(Hero)
51//!         .filter(Expr::col("age").gt(18))
52//!         .all(cx, conn)
53//!         .await
54//!     {
55//!         Outcome::Ok(v) => v,
56//!         Outcome::Err(e) => return Outcome::Err(e),
57//!         Outcome::Cancelled(r) => return Outcome::Cancelled(r),
58//!         Outcome::Panicked(p) => return Outcome::Panicked(p),
59//!     };
60//!
61//!     let Some(mut hero) = heroes.into_iter().next() else {
62//!         return Outcome::Ok(());
63//!     };
64//!     hero.age = Some(26);
65//!
66//!     match update!(hero).execute(cx, conn).await {
67//!         Outcome::Ok(_) => {}
68//!         Outcome::Err(e) => return Outcome::Err(e),
69//!         Outcome::Cancelled(r) => return Outcome::Cancelled(r),
70//!         Outcome::Panicked(p) => return Outcome::Panicked(p),
71//!     };
72//!
73//!     match delete!(Hero)
74//!         .filter(Expr::col("name").eq("Spider-Man"))
75//!         .execute(cx, conn)
76//!         .await
77//!
78//!     {
79//!         Outcome::Ok(_) => Outcome::Ok(()),
80//!         Outcome::Err(e) => Outcome::Err(e),
81//!         Outcome::Cancelled(r) => Outcome::Cancelled(r),
82//!         Outcome::Panicked(p) => Outcome::Panicked(p),
83//!     }
84//! }
85//! ```
86//!
87//! # Features
88//!
89//! - **Zero-cost abstractions**: Compile-time code generation, no runtime reflection
90//! - **Structured concurrency**: Built on asupersync for cancel-correct operations
91//! - **Type safety**: SQL types mapped to Rust types with compile-time checks
92//! - **Fluent API**: Chainable query builder methods
93//! - **Connection pooling**: Efficient connection reuse
94//! - **Migrations**: Version-controlled schema changes
95//! - **`console` feature**: Enable rich terminal output via `sqlmodel-console`
96
97// Re-export all public types from sub-crates
98pub use sqlmodel_core::connection::{ConnectionConfig, SslMode, Transaction};
99pub use sqlmodel_core::{
100    // asupersync re-exports
101    Budget,
102    // Core types
103    Connection,
104    Cx,
105    DumpMode,
106    DumpOptions,
107    DumpResult,
108    Error,
109    Field,
110    FieldInfo,
111    FieldsSet,
112    Hybrid,
113    // Inheritance types
114    InheritanceInfo,
115    InheritanceStrategy,
116    Model,
117    ModelDump,
118    Outcome,
119    RegionId,
120    Result,
121    Row,
122    SqlEnum,
123    SqlModelDump,
124    SqlModelValidate,
125    SqlType,
126    TaskId,
127    TrackedModel,
128    TypeInfo,
129    ValidateInput,
130    ValidateOptions,
131    ValidateResult,
132    Value,
133};
134
135pub use sqlmodel_macros::{Model, SqlEnum, Validate};
136
137pub use sqlmodel_query::{
138    BinaryOp, Expr, Join, JoinType, Limit, Offset, OrderBy, PolymorphicJoined, PolymorphicJoined2,
139    PolymorphicJoined3, PolymorphicJoinedSelect, PolymorphicJoinedSelect2,
140    PolymorphicJoinedSelect3, QueryBuilder, Select, UnaryOp, Where, delete, insert, raw_execute,
141    raw_query, select, update,
142};
143
144pub use sqlmodel_schema::{
145    CreateTable, Migration, MigrationRunner, MigrationStatus, SchemaBuilder, create_all,
146    create_table, drop_table,
147};
148
149pub use sqlmodel_pool::{
150    Pool, PoolConfig, PoolStats, PooledConnection, ReplicaPool, ReplicaStrategy,
151};
152
153pub use sqlmodel_session::{
154    GetOptions, ObjectKey, ObjectState, Session, SessionConfig, SessionDebugInfo,
155};
156
157/// Wrap a model struct literal and track which fields were explicitly provided.
158///
159/// This is the Rust equivalent of Pydantic's "fields_set" tracking and enables
160/// correct `exclude_unset` behavior for dumps via `TrackedModel::sql_model_dump()`.
161///
162/// Examples:
163/// ```ignore
164/// use sqlmodel::tracked;
165///
166/// let user = tracked!(User {
167///     id: 1,
168///     name: "Alice".to_string(),
169///     ..Default::default()
170/// });
171///
172/// // Omits fields that came from defaults, keeps explicitly provided fields.
173/// let json = user.sql_model_dump(DumpOptions::default().exclude_unset())?;
174/// ```
175#[macro_export]
176macro_rules! tracked {
177    ($ty:ident { $($field:ident : $value:expr),* $(,)? }) => {{
178        let inner = $ty { $($field: $value),* };
179        $crate::TrackedModel::from_explicit_field_names(inner, &[$(stringify!($field)),*])
180    }};
181    ($ty:ident { $($field:ident : $value:expr),* , .. $rest:expr $(,)? }) => {{
182        let inner = $ty { $($field: $value),*, .. $rest };
183        $crate::TrackedModel::from_explicit_field_names(inner, &[$(stringify!($field)),*])
184    }};
185}
186
187// Session management
188pub mod connection_session;
189pub mod session;
190pub use connection_session::{ConnectionSession, ConnectionSessionBuilder};
191
192// Console-enabled session extension trait
193#[cfg(feature = "console")]
194pub use connection_session::ConnectionBuilderExt;
195
196// Global console support (feature-gated)
197#[cfg(feature = "console")]
198mod global_console;
199#[cfg(feature = "console")]
200pub use global_console::{
201    global_console, has_global_console, init_auto_console, set_global_console,
202    set_global_shared_console,
203};
204
205// Console integration (feature-gated)
206#[cfg(feature = "console")]
207pub use sqlmodel_console::{
208    // Core console types
209    ConsoleAware,
210    OutputMode,
211    SqlModelConsole,
212    Theme,
213    // Renderables
214    renderables::{ErrorPanel, ErrorSeverity, PoolHealth, PoolStatsProvider, PoolStatusDisplay},
215};
216
217// ============================================================================
218// Generic Model Support Tests
219// ============================================================================
220//
221// These compile-time tests verify that the Model derive macro correctly handles
222// generic type parameters at the parsing and code generation level.
223//
224// IMPORTANT CONSTRAINTS for Generic Models:
225// When using generic type parameters in Model fields, the type must satisfy:
226// - Send + Sync (Model trait bounds)
227// - Into<Value> / From<Value> conversions (for to_row/from_row)
228//
229// The easiest patterns for generic models are:
230// 1. Use generics only for non-database fields (with #[sqlmodel(skip)])
231// 2. Use concrete types for database fields, generics for metadata
232// 3. Use PhantomData for type markers
233
234#[cfg(test)]
235mod generic_model_tests {
236    use super::*;
237    use serde::{Deserialize, Serialize};
238    use std::marker::PhantomData;
239
240    // Pattern 1: Generic with skipped field
241    // The generic type is not stored in the database
242    #[derive(Model, Debug, Clone, Serialize, Deserialize)]
243    struct TaggedModel<T: Clone + std::fmt::Debug + Send + Sync + Default> {
244        #[sqlmodel(primary_key)]
245        id: i64,
246        name: String,
247        #[sqlmodel(skip)]
248        _marker: PhantomData<T>,
249    }
250
251    // Pattern 2: Concrete model with generic metadata
252    // Database fields are concrete, generic is for compile-time type safety
253    #[derive(Model, Debug, Clone, Serialize, Deserialize)]
254    struct TypedResponse<T: Send + Sync> {
255        #[sqlmodel(primary_key)]
256        id: i64,
257        status_code: i32,
258        body: String,
259        #[sqlmodel(skip)]
260        _type: PhantomData<T>,
261    }
262
263    // Test marker types for TypedResponse
264    #[derive(Debug, Clone, Default)]
265    struct UserData;
266
267    #[derive(Debug, Clone, Default)]
268    struct OrderData;
269
270    #[test]
271    fn test_generic_model_with_phantom_data() {
272        // TaggedModel compiles and works with any marker type
273        let model: TaggedModel<UserData> = TaggedModel {
274            id: 1,
275            name: "test".to_string(),
276            _marker: PhantomData,
277        };
278        assert_eq!(model.id, 1);
279        assert_eq!(model.name, "test");
280    }
281
282    #[test]
283    fn test_generic_model_fields() {
284        // Verify TaggedModel has correct fields (skip fields are excluded from to_row)
285        let fields = <TaggedModel<UserData> as Model>::fields();
286        // _marker is skipped, so only id and name
287        assert_eq!(fields.len(), 2);
288        assert!(fields.iter().any(|f| f.name == "id"));
289        assert!(fields.iter().any(|f| f.name == "name"));
290    }
291
292    #[test]
293    fn test_generic_model_table_name() {
294        // Table name should be derived from struct name (pluralized by default)
295        assert_eq!(
296            <TaggedModel<UserData> as Model>::TABLE_NAME,
297            "tagged_models"
298        );
299        assert_eq!(
300            <TypedResponse<UserData> as Model>::TABLE_NAME,
301            "typed_responses"
302        );
303    }
304
305    #[test]
306    fn test_generic_model_primary_key() {
307        assert_eq!(<TaggedModel<UserData> as Model>::PRIMARY_KEY, &["id"]);
308    }
309
310    #[test]
311    fn test_generic_model_type_safety() {
312        // Different type parameters create distinct types at compile time
313        let user_response: TypedResponse<UserData> = TypedResponse {
314            id: 1,
315            status_code: 200,
316            body: r#"{"name": "Alice"}"#.to_string(),
317            _type: PhantomData,
318        };
319
320        let order_response: TypedResponse<OrderData> = TypedResponse {
321            id: 2,
322            status_code: 201,
323            body: r#"{"order_id": 123}"#.to_string(),
324            _type: PhantomData,
325        };
326
327        // These are different types - can't accidentally mix them
328        assert_eq!(user_response.id, 1);
329        assert_eq!(order_response.id, 2);
330    }
331
332    #[test]
333    fn test_generic_model_to_row() {
334        let model: TaggedModel<UserData> = TaggedModel {
335            id: 1,
336            name: "test".to_string(),
337            _marker: PhantomData,
338        };
339        let row = model.to_row();
340        // Only non-skipped fields
341        assert_eq!(row.len(), 2);
342        assert!(row.iter().any(|(name, _)| *name == "id"));
343        assert!(row.iter().any(|(name, _)| *name == "name"));
344    }
345
346    #[test]
347    fn test_generic_model_primary_key_value() {
348        let model: TaggedModel<UserData> = TaggedModel {
349            id: 42,
350            name: "test".to_string(),
351            _marker: PhantomData,
352        };
353        let pk = model.primary_key_value();
354        assert_eq!(pk.len(), 1);
355        assert_eq!(pk[0], Value::BigInt(42));
356    }
357
358    #[test]
359    fn test_generic_model_is_new() {
360        let new_model: TaggedModel<UserData> = TaggedModel {
361            id: 0,
362            name: "new".to_string(),
363            _marker: PhantomData,
364        };
365        // Note: is_new() depends on the implementation - typically checks if pk is default
366        let _ = new_model.is_new(); // Just verify it compiles
367    }
368}
369
370// ============================================================================
371// Table Inheritance Integration Tests
372// ============================================================================
373//
374// These tests verify that the Model derive macro correctly handles table
375// inheritance attributes and generates the appropriate inheritance() method.
376
377#[cfg(test)]
378mod inheritance_tests {
379    use super::*;
380    use serde::{Deserialize, Serialize};
381    // InheritanceStrategy is re-exported from crate root
382    use crate::InheritanceStrategy;
383    use sqlmodel_core::Value;
384
385    // Single table inheritance base model with discriminator column
386    #[derive(Model, Debug, Clone, Serialize, Deserialize)]
387    #[sqlmodel(table, inheritance = "single", discriminator = "type_")]
388    struct Employee {
389        #[sqlmodel(primary_key)]
390        id: i64,
391        name: String,
392        type_: String,
393    }
394
395    // Single table inheritance child model
396    #[derive(Model, Debug, Clone, Serialize, Deserialize)]
397    #[sqlmodel(inherits = "Employee", discriminator_value = "manager")]
398    struct Manager {
399        #[sqlmodel(primary_key)]
400        id: i64,
401        department: String,
402    }
403
404    // Joined table inheritance base model
405    #[derive(Model, Debug, Clone, Serialize, Deserialize)]
406    #[sqlmodel(table, inheritance = "joined")]
407    struct Person {
408        #[sqlmodel(primary_key)]
409        id: i64,
410        name: String,
411    }
412
413    // Joined table inheritance child model
414    #[derive(Model, Debug, Clone, Serialize, Deserialize)]
415    #[sqlmodel(table, inherits = "Person")]
416    struct Student {
417        #[sqlmodel(parent)]
418        person: Person,
419        #[sqlmodel(primary_key)]
420        id: i64,
421        grade: String,
422    }
423
424    // Concrete table inheritance base model
425    #[derive(Model, Debug, Clone, Serialize, Deserialize)]
426    #[sqlmodel(table, inheritance = "concrete")]
427    struct BaseEntity {
428        #[sqlmodel(primary_key)]
429        id: i64,
430        created_at: i64,
431    }
432
433    // Normal model without inheritance
434    #[derive(Model, Debug, Clone, Serialize, Deserialize)]
435    #[sqlmodel(table)]
436    struct NormalModel {
437        #[sqlmodel(primary_key)]
438        id: i64,
439        data: String,
440    }
441
442    #[test]
443    fn test_single_table_inheritance_base() {
444        let info = <Employee as Model>::inheritance();
445        assert_eq!(info.strategy, InheritanceStrategy::Single);
446        assert!(info.parent.is_none());
447        assert_eq!(info.discriminator_column, Some("type_"));
448        assert!(info.discriminator_value.is_none());
449        assert!(info.is_base());
450        assert!(!info.is_child());
451    }
452
453    #[test]
454    fn test_single_table_inheritance_child() {
455        let info = <Manager as Model>::inheritance();
456        assert_eq!(info.parent, Some(<Employee as Model>::TABLE_NAME));
457        assert_eq!(info.discriminator_column, Some("type_"));
458        assert_eq!(info.discriminator_value, Some("manager"));
459        assert!(info.is_child());
460        assert!(!info.is_base());
461    }
462
463    #[test]
464    fn test_single_table_inheritance_child_to_row_includes_discriminator() {
465        let m = Manager {
466            id: 1,
467            department: "ops".to_string(),
468        };
469        let row = m.to_row();
470
471        assert!(
472            row.iter()
473                .any(|(k, v)| *k == "type_" && *v == Value::Text("manager".to_string())),
474            "STI child to_row() must include discriminator value"
475        );
476    }
477
478    #[test]
479    fn test_joined_table_inheritance_base() {
480        let info = <Person as Model>::inheritance();
481        assert_eq!(info.strategy, InheritanceStrategy::Joined);
482        assert!(info.parent.is_none());
483        assert!(info.is_base());
484    }
485
486    #[test]
487    fn test_joined_table_inheritance_child() {
488        let info = <Student as Model>::inheritance();
489        assert_eq!(info.strategy, InheritanceStrategy::Joined);
490        assert_eq!(info.parent, Some(<Person as Model>::TABLE_NAME));
491        assert!(info.is_child());
492    }
493
494    #[test]
495    fn test_concrete_table_inheritance_base() {
496        let info = <BaseEntity as Model>::inheritance();
497        assert_eq!(info.strategy, InheritanceStrategy::Concrete);
498        assert!(info.parent.is_none());
499        assert!(info.is_base());
500    }
501
502    #[test]
503    fn test_no_inheritance() {
504        let info = <NormalModel as Model>::inheritance();
505        assert_eq!(info.strategy, InheritanceStrategy::None);
506        assert!(info.parent.is_none());
507        assert!(info.discriminator_value.is_none());
508        assert!(!info.is_base());
509        assert!(!info.is_child());
510    }
511
512    #[test]
513    fn test_inheritance_strategy_methods() {
514        // Single table uses discriminator
515        let single = <Employee as Model>::inheritance();
516        assert!(single.strategy.uses_discriminator());
517        assert!(!single.strategy.requires_join());
518
519        // Joined table requires join
520        let joined = <Person as Model>::inheritance();
521        assert!(!joined.strategy.uses_discriminator());
522        assert!(joined.strategy.requires_join());
523
524        // Concrete table neither
525        let concrete = <BaseEntity as Model>::inheritance();
526        assert!(!concrete.strategy.uses_discriminator());
527        assert!(!concrete.strategy.requires_join());
528    }
529}
530
531// ============================================================================
532// Horizontal Sharding Tests
533// ============================================================================
534//
535// These tests verify that the Model derive macro correctly handles shard_key
536// attributes and generates the appropriate SHARD_KEY constant and shard_key_value() method.
537
538#[cfg(test)]
539mod shard_key_tests {
540    use super::*;
541    use serde::{Deserialize, Serialize};
542
543    // Model with shard_key on tenant_id
544    #[derive(Model, Debug, Clone, Serialize, Deserialize)]
545    #[sqlmodel(table, shard_key = "tenant_id")]
546    struct TenantData {
547        #[sqlmodel(primary_key)]
548        id: i64,
549        tenant_id: i64,
550        data: String,
551    }
552
553    // Model with shard_key on an optional field
554    #[derive(Model, Debug, Clone, Serialize, Deserialize)]
555    #[sqlmodel(table, shard_key = "region")]
556    struct RegionalData {
557        #[sqlmodel(primary_key)]
558        id: i64,
559        region: Option<String>,
560        value: i32,
561    }
562
563    // Model without shard_key (default)
564    #[derive(Model, Debug, Clone, Serialize, Deserialize)]
565    #[sqlmodel(table)]
566    struct UnshardedData {
567        #[sqlmodel(primary_key)]
568        id: i64,
569        data: String,
570    }
571
572    #[test]
573    fn test_shard_key_constant() {
574        assert_eq!(<TenantData as Model>::SHARD_KEY, Some("tenant_id"));
575        assert_eq!(<RegionalData as Model>::SHARD_KEY, Some("region"));
576        assert_eq!(<UnshardedData as Model>::SHARD_KEY, None);
577    }
578
579    #[test]
580    fn test_shard_key_value_non_optional() {
581        let data = TenantData {
582            id: 1,
583            tenant_id: 42,
584            data: "test".to_string(),
585        };
586        let shard_value = data.shard_key_value();
587        assert!(shard_value.is_some());
588        assert_eq!(shard_value.unwrap(), Value::BigInt(42));
589    }
590
591    #[test]
592    fn test_shard_key_value_optional_some() {
593        let data = RegionalData {
594            id: 1,
595            region: Some("us-west".to_string()),
596            value: 100,
597        };
598        let shard_value = data.shard_key_value();
599        assert!(shard_value.is_some());
600        assert_eq!(shard_value.unwrap(), Value::Text("us-west".to_string()));
601    }
602
603    #[test]
604    fn test_shard_key_value_optional_none() {
605        let data = RegionalData {
606            id: 1,
607            region: None,
608            value: 100,
609        };
610        let shard_value = data.shard_key_value();
611        assert!(shard_value.is_none());
612    }
613
614    #[test]
615    fn test_shard_key_value_unsharded() {
616        let data = UnshardedData {
617            id: 1,
618            data: "test".to_string(),
619        };
620        let shard_value = data.shard_key_value();
621        assert!(shard_value.is_none());
622    }
623}
624
625/// Prelude module for convenient imports.
626///
627/// ```ignore
628/// use sqlmodel::prelude::*;
629/// ```
630pub mod prelude {
631    pub use crate::{
632        // asupersync
633        Budget,
634        // Core traits and types (Model is the trait)
635        Connection,
636        // Connection session (connection + optional console)
637        ConnectionSession,
638        ConnectionSessionBuilder,
639        Cx,
640        DumpMode,
641        DumpOptions,
642        Error,
643        // Query building
644        Expr,
645        FieldsSet,
646        GetOptions,
647        Hybrid,
648        Join,
649        JoinType,
650        Migration,
651        MigrationRunner,
652        Model,
653        ModelDump,
654        ObjectKey,
655        ObjectState,
656        OrderBy,
657        Outcome,
658        PolymorphicJoined,
659        PolymorphicJoined2,
660        PolymorphicJoined3,
661        PolymorphicJoinedSelect,
662        PolymorphicJoinedSelect2,
663        PolymorphicJoinedSelect3,
664        // Pool
665        Pool,
666        PoolConfig,
667        RegionId,
668        Result,
669        Row,
670        Select,
671        // ORM Session (unit of work / identity map)
672        Session,
673        SessionConfig,
674        SqlModelDump,
675        SqlModelValidate,
676        TaskId,
677        TrackedModel,
678        ValidateInput,
679        ValidateOptions,
680        ValidateResult,
681        Value,
682        // Schema
683        create_table,
684        // Macros
685        delete,
686        insert,
687        select,
688        update,
689    };
690    // Derive macros (re-export only Validate/SqlEnum since Model trait conflicts)
691    pub use sqlmodel_macros::{SqlEnum, Validate};
692
693    // Console types when feature enabled
694    #[cfg(feature = "console")]
695    pub use crate::{
696        // Types and traits
697        ConnectionBuilderExt,
698        ConsoleAware,
699        ErrorPanel,
700        ErrorSeverity,
701        OutputMode,
702        PoolHealth,
703        PoolStatsProvider,
704        PoolStatusDisplay,
705        SqlModelConsole,
706        Theme,
707        // Global console functions
708        global_console,
709        has_global_console,
710        init_auto_console,
711        set_global_console,
712        set_global_shared_console,
713    };
714}