Skip to main content

prax_orm/
lib.rs

1//! # Prax - A Next-Generation ORM for Rust
2//!
3//! Prax is a type-safe, async-first Object-Relational Mapper (ORM) for Rust,
4//! inspired by Prisma. It provides a delightful developer experience with
5//! compile-time guarantees and a powerful schema definition language.
6//!
7//! ## Features
8//!
9//! - **Schema Definition Language**: Define your data models in a clear, readable `.prax` file
10//! - **Type-Safe Queries**: Compile-time checked queries with a fluent builder API
11//! - **Async-First**: Built on Tokio for high-performance async database operations
12//! - **Multi-Database Support**: PostgreSQL, MySQL, SQLite, and MongoDB
13//! - **Automatic Migrations**: Generate and apply database migrations from your schema
14//! - **Relations**: Define and query relationships between models with ease
15//! - **Transactions**: Full transaction support with savepoints and isolation levels
16//! - **Middleware System**: Extensible query interception for logging, metrics, and more
17//! - **Multi-Tenant Support**: Built-in support for multi-tenant applications
18//!
19//! ## Quick Start
20//!
21//! ### 1. Define Your Schema
22//!
23//! Create a `prax/schema.prax` file in your project:
24//!
25//! ```text
26//! // Models define your database tables
27//! model User {
28//!     id        Int      @id @auto
29//!     email     String   @unique
30//!     name      String?
31//!     posts     Post[]
32//!     createdAt DateTime @default(now())
33//! }
34//!
35//! model Post {
36//!     id        Int      @id @auto
37//!     title     String
38//!     content   String?  @db.Text
39//!     published Boolean  @default(false)
40//!     authorId  Int
41//!     author    User     @relation(fields: [authorId], references: [id])
42//! }
43//! ```
44//!
45//! ### 2. Configure Your Database
46//!
47//! Create a `prax.toml` configuration file in your project root:
48//!
49//! ```toml
50//! [database]
51//! provider = "postgresql"
52//! url = "${DATABASE_URL}"
53//!
54//! [database.pool]
55//! max_connections = 10
56//!
57//! [generator.client]
58//! output = "./src/generated"
59//! ```
60//!
61//! ### 3. Use in Your Application
62//!
63//! ```rust,ignore
64//! use prax_orm::postgres::{PgEngine, PgPool, PgPoolBuilder};
65//! use prax_orm::prelude::*;
66//!
67//! #[tokio::main]
68//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
69//!     // Build a connection pool and wrap it in an engine
70//!     let pool: PgPool = PgPoolBuilder::new()
71//!         .url("postgresql://localhost/mydb")
72//!         .build()
73//!         .await?;
74//!     let client = PraxClient::new(PgEngine::new(pool));
75//!
76//!     // Create a new user
77//!     let user = client
78//!         .user()
79//!         .create(CreateData::new()
80//!             .set("email", "alice@example.com")
81//!             .set("name", "Alice"))
82//!         .exec()
83//!         .await?;
84//!
85//!     // Find users with filtering
86//!     let users = client
87//!         .user()
88//!         .find_many()
89//!         .r#where(user::email::contains("@example.com"))
90//!         .order_by(user::createdAt::desc())
91//!         .take(10)
92//!         .exec()
93//!         .await?;
94//!
95//!     // Update a user
96//!     let updated = client
97//!         .user()
98//!         .update()
99//!         .r#where(user::id::equals(1))
100//!         .data(UpdateData::new().set("name", "Alice Smith"))
101//!         .exec()
102//!         .await?;
103//!
104//!     // Delete a user
105//!     client
106//!         .user()
107//!         .delete()
108//!         .r#where(user::id::equals(1))
109//!         .exec()
110//!         .await?;
111//!
112//!     Ok(())
113//! }
114//! ```
115//!
116//! ## Schema Language
117//!
118//! The Prax schema language provides a powerful way to define your data models:
119//!
120//! ### Models
121//!
122//! Models represent database tables:
123//!
124//! ```text
125//! model User {
126//!     id    Int    @id @auto     // Primary key with auto-increment
127//!     email String @unique       // Unique constraint
128//!     name  String?              // Optional (nullable) field
129//! }
130//! ```
131//!
132//! ### Field Types
133//!
134//! | Type       | Description                    | Database Mapping  |
135//! |------------|--------------------------------|-------------------|
136//! | `Int`      | Integer                        | INTEGER/INT       |
137//! | `BigInt`   | 64-bit integer                 | BIGINT            |
138//! | `Float`    | Floating point                 | FLOAT/DOUBLE      |
139//! | `Decimal`  | Exact decimal                  | DECIMAL/NUMERIC   |
140//! | `String`   | Text                           | VARCHAR/TEXT      |
141//! | `Boolean`  | True/false                     | BOOLEAN           |
142//! | `DateTime` | Date and time                  | TIMESTAMP         |
143//! | `Date`     | Date only                      | DATE              |
144//! | `Time`     | Time only                      | TIME              |
145//! | `Json`     | JSON data                      | JSON/JSONB        |
146//! | `Bytes`    | Binary data                    | BYTEA/BLOB        |
147//! | `Uuid`     | UUID                           | UUID              |
148//!
149//! ### Field Attributes
150//!
151//! | Attribute              | Description                              |
152//! |------------------------|------------------------------------------|
153//! | `@id`                  | Primary key                              |
154//! | `@auto`                | Auto-increment/generate                  |
155//! | `@unique`              | Unique constraint                        |
156//! | `@default(value)`      | Default value                            |
157//! | `@map("name")`         | Custom column name                       |
158//! | `@db.Type`             | Database-specific type                   |
159//! | `@index`               | Create index on field                    |
160//! | `@updated_at`          | Auto-update timestamp                    |
161//! | `@relation(...)`       | Define relation                          |
162//!
163//! ### Relations
164//!
165//! Define relationships between models:
166//!
167//! ```text
168//! model User {
169//!     id    Int    @id @auto
170//!     posts Post[]                    // One-to-many
171//! }
172//!
173//! model Post {
174//!     id       Int  @id @auto
175//!     authorId Int
176//!     author   User @relation(fields: [authorId], references: [id])
177//! }
178//! ```
179//!
180//! ### Enums
181//!
182//! Define enumerated types:
183//!
184//! ```text
185//! enum Role {
186//!     User
187//!     Admin
188//!     Moderator
189//! }
190//!
191//! model User {
192//!     id   Int  @id @auto
193//!     role Role @default(User)
194//! }
195//! ```
196//!
197//! ## Query API
198//!
199//! ### Finding Records
200//!
201//! ```rust,ignore
202//! // Find many with filters
203//! let users = client
204//!     .user()
205//!     .find_many()
206//!     .r#where(user::email::contains("@example.com"))
207//!     .r#where(user::createdAt::gt(some_date))
208//!     .order_by(user::name::asc())
209//!     .skip(10)
210//!     .take(20)
211//!     .exec()
212//!     .await?;
213//!
214//! // Find unique record
215//! let user = client
216//!     .user()
217//!     .find_unique()
218//!     .r#where(user::id::equals(1))
219//!     .exec()
220//!     .await?;
221//!
222//! // Find first matching
223//! let user = client
224//!     .user()
225//!     .find_first()
226//!     .r#where(user::email::ends_with("@company.com"))
227//!     .exec()
228//!     .await?;
229//! ```
230//!
231//! ### Filter Operations
232//!
233//! ```text
234//! // Equality
235//! user::email::equals("test@example.com")
236//!
237//! // Comparison
238//! user::age::gt(18)
239//! user::age::gte(18)
240//! user::age::lt(65)
241//! user::age::lte(65)
242//!
243//! // String operations
244//! user::name::contains("john")
245//! user::name::starts_with("Dr.")
246//! user::name::ends_with("Smith")
247//!
248//! // List operations
249//! user::status::in_list(vec!["active", "pending"])
250//! user::status::not_in(vec!["banned"])
251//!
252//! // Null checks
253//! user::deleted_at::is_null()
254//! user::verified_at::is_not_null()
255//!
256//! // Combining filters
257//! Filter::and(vec![
258//!     user::active::equals(true),
259//!     user::email::contains("@company.com"),
260//! ])
261//!
262//! Filter::or(vec![
263//!     user::role::equals(Role::Admin),
264//!     user::role::equals(Role::Moderator),
265//! ])
266//! ```
267//!
268//! ### Creating Records
269//!
270//! ```rust,ignore
271//! // Single create
272//! let user = client
273//!     .user()
274//!     .create(CreateData::new()
275//!         .set("email", "new@example.com")
276//!         .set("name", "New User"))
277//!     .exec()
278//!     .await?;
279//!
280//! // Create many
281//! let count = client
282//!     .user()
283//!     .create_many(vec![
284//!         CreateData::new().set("email", "user1@example.com"),
285//!         CreateData::new().set("email", "user2@example.com"),
286//!     ])
287//!     .exec()
288//!     .await?;
289//!
290//! // Create with nested relation
291//! let user = client
292//!     .user()
293//!     .create(CreateData::new()
294//!         .set("email", "author@example.com")
295//!         .connect("posts", post::id::equals(1)))
296//!     .exec()
297//!     .await?;
298//! ```
299//!
300//! ### Updating Records
301//!
302//! ```rust,ignore
303//! // Update single
304//! let user = client
305//!     .user()
306//!     .update()
307//!     .r#where(user::id::equals(1))
308//!     .data(UpdateData::new()
309//!         .set("name", "Updated Name")
310//!         .increment("login_count", 1))
311//!     .exec()
312//!     .await?;
313//!
314//! // Update many
315//! let count = client
316//!     .user()
317//!     .update_many()
318//!     .r#where(user::active::equals(false))
319//!     .data(UpdateData::new().set("active", true))
320//!     .exec()
321//!     .await?;
322//!
323//! // Upsert (create or update)
324//! let user = client
325//!     .user()
326//!     .upsert()
327//!     .r#where(user::email::equals("test@example.com"))
328//!     .create(CreateData::new().set("email", "test@example.com"))
329//!     .update(UpdateData::new().set("login_count", 1))
330//!     .exec()
331//!     .await?;
332//! ```
333//!
334//! ### Deleting Records
335//!
336//! ```rust,ignore
337//! // Delete single
338//! client
339//!     .user()
340//!     .delete()
341//!     .r#where(user::id::equals(1))
342//!     .exec()
343//!     .await?;
344//!
345//! // Delete many
346//! let count = client
347//!     .user()
348//!     .delete_many()
349//!     .r#where(user::active::equals(false))
350//!     .exec()
351//!     .await?;
352//! ```
353//!
354//! ### Including Relations
355//!
356//! ```rust,ignore
357//! // Include related records
358//! let user = client
359//!     .user()
360//!     .find_unique()
361//!     .r#where(user::id::equals(1))
362//!     .include(user::posts::include())
363//!     .exec()
364//!     .await?;
365//!
366//! // Nested includes
367//! let post = client
368//!     .post()
369//!     .find_unique()
370//!     .r#where(post::id::equals(1))
371//!     .include(post::author::include(
372//!         user::posts::include()
373//!     ))
374//!     .exec()
375//!     .await?;
376//!
377//! // Select specific fields
378//! let user = client
379//!     .user()
380//!     .find_unique()
381//!     .r#where(user::id::equals(1))
382//!     .select(user::select!(id, email, name))
383//!     .exec()
384//!     .await?;
385//! ```
386//!
387//! ## Transactions
388//!
389//! ```rust,ignore
390//! // Basic transaction
391//! let result = client.transaction(|tx| async move {
392//!     let user = tx.user()
393//!         .create(CreateData::new().set("email", "tx@example.com"))
394//!         .exec()
395//!         .await?;
396//!
397//!     tx.post()
398//!         .create(CreateData::new()
399//!             .set("title", "My First Post")
400//!             .set("authorId", user.id))
401//!         .exec()
402//!         .await?;
403//!
404//!     Ok(user)
405//! }).await?;
406//!
407//! // Transaction with options
408//! let result = client
409//!     .transaction_with_config(TransactionConfig::new()
410//!         .isolation(IsolationLevel::Serializable)
411//!         .timeout(Duration::from_secs(30)))
412//!     .run(|tx| async move {
413//!         // ... operations
414//!         Ok(())
415//!     })
416//!     .await?;
417//! ```
418//!
419//! ## Raw SQL
420//!
421//! When you need to execute raw SQL:
422//!
423//! ```rust,ignore
424//! use prax_query::raw_query;
425//!
426//! // Type-safe raw query
427//! let users: Vec<User> = client
428//!     .query_raw(raw_query!(
429//!         "SELECT * FROM users WHERE email = {}",
430//!         "test@example.com"
431//!     ))
432//!     .await?;
433//!
434//! // Execute raw SQL
435//! let affected = client
436//!     .execute_raw(raw_query!(
437//!         "UPDATE users SET verified = true WHERE id = {}",
438//!         user_id
439//!     ))
440//!     .await?;
441//! ```
442//!
443//! ## Aggregations
444//!
445//! ```rust,ignore
446//! // Count
447//! let count = client
448//!     .user()
449//!     .count()
450//!     .r#where(user::active::equals(true))
451//!     .exec()
452//!     .await?;
453//!
454//! // Aggregate functions
455//! let stats = client
456//!     .post()
457//!     .aggregate()
458//!     .avg(post::views)
459//!     .sum(post::views)
460//!     .min(post::created_at)
461//!     .max(post::created_at)
462//!     .exec()
463//!     .await?;
464//!
465//! // Group by
466//! let by_status = client
467//!     .post()
468//!     .group_by(post::status)
469//!     .count()
470//!     .having(count::gt(10))
471//!     .exec()
472//!     .await?;
473//! ```
474//!
475//! ## Multi-Tenant Applications
476//!
477//! Prax provides built-in support for multi-tenant applications:
478//!
479//! ```rust,ignore
480//! use prax::tenant::{TenantConfig, TenantMiddleware, IsolationStrategy};
481//!
482//! // Configure tenant isolation
483//! let config = TenantConfig::builder()
484//!     .strategy(IsolationStrategy::RowLevel {
485//!         tenant_column: "tenant_id".into(),
486//!     })
487//!     .build();
488//!
489//! // Add tenant middleware
490//! let client = client.with_middleware(TenantMiddleware::new(config));
491//!
492//! // Set tenant context for requests
493//! let client = client.with_tenant("tenant-123");
494//!
495//! // All queries are automatically scoped to this tenant
496//! let users = client.user().find_many().exec().await?;
497//! ```
498//!
499//! ## Middleware
500//!
501//! Extend Prax with custom middleware:
502//!
503//! ```rust,ignore
504//! use prax::middleware::{LoggingMiddleware, MetricsMiddleware, MiddlewareBuilder};
505//!
506//! let client = client.with_middleware(
507//!     MiddlewareBuilder::new()
508//!         .add(LoggingMiddleware::new())
509//!         .add(MetricsMiddleware::new())
510//!         .build()
511//! );
512//! ```
513//!
514//! ## CLI Commands
515//!
516//! Prax provides a CLI for common operations:
517//!
518//! ```bash
519//! # Initialize a new project
520//! prax init
521//!
522//! # Generate client code from schema
523//! prax generate
524//!
525//! # Create a new migration
526//! prax migrate create "add_users_table"
527//!
528//! # Apply pending migrations
529//! prax migrate deploy
530//!
531//! # Reset database
532//! prax migrate reset
533//!
534//! # Validate schema
535//! prax validate
536//!
537//! # Format schema files
538//! prax format
539//! ```
540//!
541//! ## Crate Features
542//!
543//! Enable features in your `Cargo.toml`:
544//!
545//! ```toml
546//! [dependencies]
547//! prax-orm = { version = "0.10", features = ["postgres", "mysql", "sqlite"] }
548//! ```
549//!
550//! The `postgres` feature is enabled by default.
551//!
552//! | Feature     | Description                                  |
553//! |-------------|----------------------------------------------|
554//! | `postgres`  | PostgreSQL support via `tokio-postgres`      |
555//! | `mysql`     | MySQL support via `mysql_async`              |
556//! | `sqlite`    | SQLite support via `tokio-rusqlite`          |
557//! | `mssql`     | MSSQL support via `tiberius`                 |
558//! | `mongodb`   | MongoDB support                              |
559//! | `duckdb`    | DuckDB analytics support                     |
560//! | `scylladb`  | ScyllaDB support                             |
561//! | `cassandra` | Apache Cassandra support                     |
562//! | `sqlx`      | Alternative backend with compile-time checks |
563//! | `pgvector`  | Vector similarity search for PostgreSQL      |
564//!
565//! ## Error Handling
566//!
567//! Prax provides detailed error types with actionable messages:
568//!
569//! ```rust,ignore
570//! use prax::error::{QueryError, ErrorCode};
571//!
572//! match client.user().find_unique().r#where(user::id::equals(1)).exec().await {
573//!     Ok(user) => println!("Found: {:?}", user),
574//!     Err(e) => {
575//!         eprintln!("Error: {}", e);
576//!         eprintln!("Code: {:?}", e.code());
577//!         if let Some(suggestion) = e.suggestion() {
578//!             eprintln!("Suggestion: {}", suggestion);
579//!         }
580//!     }
581//! }
582//! ```
583//!
584//! ## Performance
585//!
586//! Prax is designed for high performance:
587//!
588//! - **Connection Pooling**: Built-in connection pool with configurable limits
589//! - **Prepared Statement Caching**: Automatic caching of prepared statements
590//! - **Batch Operations**: Efficient bulk create, update, and delete
591//! - **Lazy Loading**: Relations are loaded only when requested
592//!
593//! ## Further Reading
594//!
595//! - [Schema Documentation](schema/index.html)
596//! - [Client API](client/index.html)
597//! - [Error Types](error/index.html)
598//! - [Prelude](prelude/index.html)
599//! - [Examples](https://github.com/quinnjr/prax/tree/main/examples)
600
601#![cfg_attr(docsrs, feature(doc_cfg))]
602#![deny(missing_docs)]
603#![deny(rustdoc::broken_intra_doc_links)]
604
605/// Schema parsing and AST types.
606///
607/// This module provides everything needed to work with Prax schema files:
608///
609/// - [`schema::parse_schema`] - Parse a schema string
610/// - [`schema::parse_schema_file`] - Parse a schema from a file
611/// - [`schema::validate_schema`] - Parse and validate a schema
612/// - [`schema::PraxConfig`] - Configuration from `prax.toml`
613/// - [`schema::Schema`] - The parsed schema representation
614///
615/// ## Example
616///
617/// ```rust,ignore
618/// use prax::schema::{parse_schema, validate_schema, PraxConfig};
619///
620/// // Parse a schema
621/// let schema = parse_schema(r#"
622///     model User {
623///         id    Int    @id @auto
624///         email String @unique
625///     }
626/// "#)?;
627///
628/// // Access schema information
629/// println!("Models: {:?}", schema.model_names().collect::<Vec<_>>());
630///
631/// // Load configuration
632/// let config = PraxConfig::from_file("prax.toml")?;
633/// println!("Database: {}", config.database.provider);
634/// ```
635pub mod schema {
636    pub use prax_schema::*;
637}
638
639// Re-export proc macros
640pub use prax_codegen::Model;
641pub use prax_codegen::prax_schema;
642
643// Read-operation macros (phase 3). See `prax-codegen/src/macros/`.
644pub use prax_codegen::{count, delete, delete_many, find_first, find_many, find_unique};
645
646// Aggregate macros (phase 6). See `prax-codegen/src/macros/ops/aggregate.rs`.
647pub use prax_codegen::aggregate;
648pub use prax_codegen::group_by;
649
650// Write-operation macros (phase 5a). See `prax-codegen/src/macros/ops/`.
651pub use prax_codegen::{create, create_many, update, update_many, upsert};
652
653// Shape macros (phase 4) — typed input struct values that compose with
654// the read macros via spread. See `prax-codegen/src/macros/ops/shape.rs`.
655pub use prax_codegen::r#where;
656pub use prax_codegen::{cursor, include, order_by, select};
657
658// Database engine backends. Each is gated behind the cargo feature of the
659// same name (`postgres` is on by default) and re-exports the corresponding
660// `prax-<engine>` crate.
661#[cfg(feature = "cassandra")]
662pub use prax_cassandra as cassandra;
663#[cfg(feature = "duckdb")]
664pub use prax_duckdb as duckdb;
665#[cfg(feature = "mongodb")]
666pub use prax_mongodb as mongodb;
667#[cfg(feature = "mssql")]
668pub use prax_mssql as mssql;
669#[cfg(feature = "mysql")]
670pub use prax_mysql as mysql;
671#[cfg(feature = "pgvector")]
672pub use prax_pgvector as pgvector;
673#[cfg(feature = "postgres")]
674pub use prax_postgres as postgres;
675#[cfg(feature = "scylladb")]
676pub use prax_scylladb as scylladb;
677#[cfg(feature = "sqlite")]
678pub use prax_sqlite as sqlite;
679#[cfg(feature = "sqlx")]
680pub use prax_sqlx as sqlx;
681
682/// Top-level `PraxClient<E>` and the `prax::client!` macro. See the
683/// [`client`](mod@client) module docs for usage.
684pub mod client;
685pub use client::PraxClient;
686// The `client!` macro is re-exported automatically by `#[macro_export]`.
687
688// Macro plumbing: the expansion of `client!` references `$crate::__paste`
689// and `$crate::__prelude`. Re-export them at the crate root so callers do
690// not have to think about where the symbols live.
691#[doc(hidden)]
692pub use client::__paste;
693#[doc(hidden)]
694pub use client::__prelude;
695
696/// Prelude module for convenient imports.
697///
698/// Import everything you need with a single line:
699///
700/// ```rust,ignore
701/// use prax::prelude::*;
702/// ```
703///
704/// This includes:
705/// - Schema parsing functions
706/// - Configuration types
707/// - Common traits and types
708pub mod prelude {
709    pub use crate::client::PraxClient;
710    pub use crate::schema::{PraxConfig, Schema, parse_schema, parse_schema_file};
711    pub use crate::{Model, prax_schema};
712}
713
714// Re-export key types at the crate root
715pub use schema::{Schema, SchemaError};
716
717/// Error types for the Prax ORM.
718///
719/// The main error type is [`SchemaError`] which covers all schema-related
720/// errors including parsing, validation, and file I/O.
721pub mod error {
722    pub use prax_schema::SchemaError;
723}
724
725/// Common types used by generated Prax models.
726///
727/// This module is referenced by the `#[derive(Model)]` proc-macro.
728#[doc(hidden)]
729pub mod _prax_prelude {
730    /// Marker trait for Prax models.
731    pub trait PraxModel {
732        /// The table name in the database.
733        const TABLE_NAME: &'static str;
734
735        /// The primary key column(s).
736        const PRIMARY_KEY: &'static [&'static str];
737    }
738
739    /// Sort direction for order by clauses.
740    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
741    pub enum SortOrder {
742        /// Ascending order (A-Z, 0-9).
743        Asc,
744        /// Descending order (Z-A, 9-0).
745        Desc,
746    }
747}