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::prelude::*;
65//!
66//! #[tokio::main]
67//! async fn main() -> Result<(), prax::SchemaError> {
68//!     // Initialize the client (in real usage, generated from schema)
69//!     let client = PraxClient::new("postgresql://localhost/mydb").await?;
70//!
71//!     // Create a new user
72//!     let user = client
73//!         .user()
74//!         .create(CreateData::new()
75//!             .set("email", "alice@example.com")
76//!             .set("name", "Alice"))
77//!         .exec()
78//!         .await?;
79//!
80//!     // Find users with filtering
81//!     let users = client
82//!         .user()
83//!         .find_many()
84//!         .r#where(user::email::contains("@example.com"))
85//!         .order_by(user::createdAt::desc())
86//!         .take(10)
87//!         .exec()
88//!         .await?;
89//!
90//!     // Update a user
91//!     let updated = client
92//!         .user()
93//!         .update()
94//!         .r#where(user::id::equals(1))
95//!         .data(UpdateData::new().set("name", "Alice Smith"))
96//!         .exec()
97//!         .await?;
98//!
99//!     // Delete a user
100//!     client
101//!         .user()
102//!         .delete()
103//!         .r#where(user::id::equals(1))
104//!         .exec()
105//!         .await?;
106//!
107//!     Ok(())
108//! }
109//! ```
110//!
111//! ## Schema Language
112//!
113//! The Prax schema language provides a powerful way to define your data models:
114//!
115//! ### Models
116//!
117//! Models represent database tables:
118//!
119//! ```text
120//! model User {
121//!     id    Int    @id @auto     // Primary key with auto-increment
122//!     email String @unique       // Unique constraint
123//!     name  String?              // Optional (nullable) field
124//! }
125//! ```
126//!
127//! ### Field Types
128//!
129//! | Type       | Description                    | Database Mapping  |
130//! |------------|--------------------------------|-------------------|
131//! | `Int`      | Integer                        | INTEGER/INT       |
132//! | `BigInt`   | 64-bit integer                 | BIGINT            |
133//! | `Float`    | Floating point                 | FLOAT/DOUBLE      |
134//! | `Decimal`  | Exact decimal                  | DECIMAL/NUMERIC   |
135//! | `String`   | Text                           | VARCHAR/TEXT      |
136//! | `Boolean`  | True/false                     | BOOLEAN           |
137//! | `DateTime` | Date and time                  | TIMESTAMP         |
138//! | `Date`     | Date only                      | DATE              |
139//! | `Time`     | Time only                      | TIME              |
140//! | `Json`     | JSON data                      | JSON/JSONB        |
141//! | `Bytes`    | Binary data                    | BYTEA/BLOB        |
142//! | `Uuid`     | UUID                           | UUID              |
143//!
144//! ### Field Attributes
145//!
146//! | Attribute              | Description                              |
147//! |------------------------|------------------------------------------|
148//! | `@id`                  | Primary key                              |
149//! | `@auto`                | Auto-increment/generate                  |
150//! | `@unique`              | Unique constraint                        |
151//! | `@default(value)`      | Default value                            |
152//! | `@map("name")`         | Custom column name                       |
153//! | `@db.Type`             | Database-specific type                   |
154//! | `@index`               | Create index on field                    |
155//! | `@updated_at`          | Auto-update timestamp                    |
156//! | `@relation(...)`       | Define relation                          |
157//!
158//! ### Relations
159//!
160//! Define relationships between models:
161//!
162//! ```text
163//! model User {
164//!     id    Int    @id @auto
165//!     posts Post[]                    // One-to-many
166//! }
167//!
168//! model Post {
169//!     id       Int  @id @auto
170//!     authorId Int
171//!     author   User @relation(fields: [authorId], references: [id])
172//! }
173//! ```
174//!
175//! ### Enums
176//!
177//! Define enumerated types:
178//!
179//! ```text
180//! enum Role {
181//!     User
182//!     Admin
183//!     Moderator
184//! }
185//!
186//! model User {
187//!     id   Int  @id @auto
188//!     role Role @default(User)
189//! }
190//! ```
191//!
192//! ## Query API
193//!
194//! ### Finding Records
195//!
196//! ```rust,ignore
197//! // Find many with filters
198//! let users = client
199//!     .user()
200//!     .find_many()
201//!     .r#where(user::email::contains("@example.com"))
202//!     .r#where(user::createdAt::gt(some_date))
203//!     .order_by(user::name::asc())
204//!     .skip(10)
205//!     .take(20)
206//!     .exec()
207//!     .await?;
208//!
209//! // Find unique record
210//! let user = client
211//!     .user()
212//!     .find_unique()
213//!     .r#where(user::id::equals(1))
214//!     .exec()
215//!     .await?;
216//!
217//! // Find first matching
218//! let user = client
219//!     .user()
220//!     .find_first()
221//!     .r#where(user::email::ends_with("@company.com"))
222//!     .exec()
223//!     .await?;
224//! ```
225//!
226//! ### Filter Operations
227//!
228//! ```rust,ignore
229//! // Equality
230//! user::email::equals("test@example.com")
231//!
232//! // Comparison
233//! user::age::gt(18)
234//! user::age::gte(18)
235//! user::age::lt(65)
236//! user::age::lte(65)
237//!
238//! // String operations
239//! user::name::contains("john")
240//! user::name::starts_with("Dr.")
241//! user::name::ends_with("Smith")
242//!
243//! // List operations
244//! user::status::in_list(vec!["active", "pending"])
245//! user::status::not_in(vec!["banned"])
246//!
247//! // Null checks
248//! user::deleted_at::is_null()
249//! user::verified_at::is_not_null()
250//!
251//! // Combining filters
252//! Filter::and(vec![
253//!     user::active::equals(true),
254//!     user::email::contains("@company.com"),
255//! ])
256//!
257//! Filter::or(vec![
258//!     user::role::equals(Role::Admin),
259//!     user::role::equals(Role::Moderator),
260//! ])
261//! ```
262//!
263//! ### Creating Records
264//!
265//! ```rust,ignore
266//! // Single create
267//! let user = client
268//!     .user()
269//!     .create(CreateData::new()
270//!         .set("email", "new@example.com")
271//!         .set("name", "New User"))
272//!     .exec()
273//!     .await?;
274//!
275//! // Create many
276//! let count = client
277//!     .user()
278//!     .create_many(vec![
279//!         CreateData::new().set("email", "user1@example.com"),
280//!         CreateData::new().set("email", "user2@example.com"),
281//!     ])
282//!     .exec()
283//!     .await?;
284//!
285//! // Create with nested relation
286//! let user = client
287//!     .user()
288//!     .create(CreateData::new()
289//!         .set("email", "author@example.com")
290//!         .connect("posts", post::id::equals(1)))
291//!     .exec()
292//!     .await?;
293//! ```
294//!
295//! ### Updating Records
296//!
297//! ```rust,ignore
298//! // Update single
299//! let user = client
300//!     .user()
301//!     .update()
302//!     .r#where(user::id::equals(1))
303//!     .data(UpdateData::new()
304//!         .set("name", "Updated Name")
305//!         .increment("login_count", 1))
306//!     .exec()
307//!     .await?;
308//!
309//! // Update many
310//! let count = client
311//!     .user()
312//!     .update_many()
313//!     .r#where(user::active::equals(false))
314//!     .data(UpdateData::new().set("active", true))
315//!     .exec()
316//!     .await?;
317//!
318//! // Upsert (create or update)
319//! let user = client
320//!     .user()
321//!     .upsert()
322//!     .r#where(user::email::equals("test@example.com"))
323//!     .create(CreateData::new().set("email", "test@example.com"))
324//!     .update(UpdateData::new().set("login_count", 1))
325//!     .exec()
326//!     .await?;
327//! ```
328//!
329//! ### Deleting Records
330//!
331//! ```rust,ignore
332//! // Delete single
333//! client
334//!     .user()
335//!     .delete()
336//!     .r#where(user::id::equals(1))
337//!     .exec()
338//!     .await?;
339//!
340//! // Delete many
341//! let count = client
342//!     .user()
343//!     .delete_many()
344//!     .r#where(user::active::equals(false))
345//!     .exec()
346//!     .await?;
347//! ```
348//!
349//! ### Including Relations
350//!
351//! ```rust,ignore
352//! // Include related records
353//! let user = client
354//!     .user()
355//!     .find_unique()
356//!     .r#where(user::id::equals(1))
357//!     .include(user::posts::include())
358//!     .exec()
359//!     .await?;
360//!
361//! // Nested includes
362//! let post = client
363//!     .post()
364//!     .find_unique()
365//!     .r#where(post::id::equals(1))
366//!     .include(post::author::include(
367//!         user::posts::include()
368//!     ))
369//!     .exec()
370//!     .await?;
371//!
372//! // Select specific fields
373//! let user = client
374//!     .user()
375//!     .find_unique()
376//!     .r#where(user::id::equals(1))
377//!     .select(user::select!(id, email, name))
378//!     .exec()
379//!     .await?;
380//! ```
381//!
382//! ## Transactions
383//!
384//! ```rust,ignore
385//! // Basic transaction
386//! let result = client.transaction(|tx| async move {
387//!     let user = tx.user()
388//!         .create(CreateData::new().set("email", "tx@example.com"))
389//!         .exec()
390//!         .await?;
391//!
392//!     tx.post()
393//!         .create(CreateData::new()
394//!             .set("title", "My First Post")
395//!             .set("authorId", user.id))
396//!         .exec()
397//!         .await?;
398//!
399//!     Ok(user)
400//! }).await?;
401//!
402//! // Transaction with options
403//! let result = client
404//!     .transaction_with_config(TransactionConfig::new()
405//!         .isolation(IsolationLevel::Serializable)
406//!         .timeout(Duration::from_secs(30)))
407//!     .run(|tx| async move {
408//!         // ... operations
409//!         Ok(())
410//!     })
411//!     .await?;
412//! ```
413//!
414//! ## Raw SQL
415//!
416//! When you need to execute raw SQL:
417//!
418//! ```rust,ignore
419//! use prax::raw_query;
420//!
421//! // Type-safe raw query
422//! let users: Vec<User> = client
423//!     .query_raw(raw_query!(
424//!         "SELECT * FROM users WHERE email = {}",
425//!         "test@example.com"
426//!     ))
427//!     .await?;
428//!
429//! // Execute raw SQL
430//! let affected = client
431//!     .execute_raw(raw_query!(
432//!         "UPDATE users SET verified = true WHERE id = {}",
433//!         user_id
434//!     ))
435//!     .await?;
436//! ```
437//!
438//! ## Aggregations
439//!
440//! ```rust,ignore
441//! // Count
442//! let count = client
443//!     .user()
444//!     .count()
445//!     .r#where(user::active::equals(true))
446//!     .exec()
447//!     .await?;
448//!
449//! // Aggregate functions
450//! let stats = client
451//!     .post()
452//!     .aggregate()
453//!     .avg(post::views)
454//!     .sum(post::views)
455//!     .min(post::created_at)
456//!     .max(post::created_at)
457//!     .exec()
458//!     .await?;
459//!
460//! // Group by
461//! let by_status = client
462//!     .post()
463//!     .group_by(post::status)
464//!     .count()
465//!     .having(count::gt(10))
466//!     .exec()
467//!     .await?;
468//! ```
469//!
470//! ## Multi-Tenant Applications
471//!
472//! Prax provides built-in support for multi-tenant applications:
473//!
474//! ```rust,ignore
475//! use prax::tenant::{TenantConfig, TenantMiddleware, IsolationStrategy};
476//!
477//! // Configure tenant isolation
478//! let config = TenantConfig::builder()
479//!     .strategy(IsolationStrategy::RowLevel {
480//!         tenant_column: "tenant_id".into(),
481//!     })
482//!     .build();
483//!
484//! // Add tenant middleware
485//! let client = client.with_middleware(TenantMiddleware::new(config));
486//!
487//! // Set tenant context for requests
488//! let client = client.with_tenant("tenant-123");
489//!
490//! // All queries are automatically scoped to this tenant
491//! let users = client.user().find_many().exec().await?;
492//! ```
493//!
494//! ## Middleware
495//!
496//! Extend Prax with custom middleware:
497//!
498//! ```rust,ignore
499//! use prax::middleware::{LoggingMiddleware, MetricsMiddleware, MiddlewareBuilder};
500//!
501//! let client = client.with_middleware(
502//!     MiddlewareBuilder::new()
503//!         .add(LoggingMiddleware::new())
504//!         .add(MetricsMiddleware::new())
505//!         .build()
506//! );
507//! ```
508//!
509//! ## CLI Commands
510//!
511//! Prax provides a CLI for common operations:
512//!
513//! ```bash
514//! # Initialize a new project
515//! prax init
516//!
517//! # Generate client code from schema
518//! prax generate
519//!
520//! # Create a new migration
521//! prax migrate create "add_users_table"
522//!
523//! # Apply pending migrations
524//! prax migrate deploy
525//!
526//! # Reset database
527//! prax migrate reset
528//!
529//! # Validate schema
530//! prax validate
531//!
532//! # Format schema files
533//! prax format
534//! ```
535//!
536//! ## Crate Features
537//!
538//! Enable features in your `Cargo.toml`:
539//!
540//! ```toml
541//! [dependencies]
542//! prax = { version = "0.1", features = ["postgres", "mysql", "sqlite"] }
543//! ```
544//!
545//! | Feature    | Description                                    |
546//! |------------|------------------------------------------------|
547//! | `postgres` | PostgreSQL support via `tokio-postgres`        |
548//! | `mysql`    | MySQL support via `mysql_async`                |
549//! | `sqlite`   | SQLite support via `tokio-rusqlite`            |
550//! | `mongodb`  | MongoDB support                                |
551//! | `sqlx`     | Alternative backend with compile-time checks   |
552//! | `tracing`  | Integration with the `tracing` crate           |
553//! | `serde`    | Serialization support for schema types         |
554//!
555//! ## Error Handling
556//!
557//! Prax provides detailed error types with actionable messages:
558//!
559//! ```rust,ignore
560//! use prax::error::{QueryError, ErrorCode};
561//!
562//! match client.user().find_unique().r#where(user::id::equals(1)).exec().await {
563//!     Ok(user) => println!("Found: {:?}", user),
564//!     Err(e) => {
565//!         eprintln!("Error: {}", e);
566//!         eprintln!("Code: {:?}", e.code());
567//!         if let Some(suggestion) = e.suggestion() {
568//!             eprintln!("Suggestion: {}", suggestion);
569//!         }
570//!     }
571//! }
572//! ```
573//!
574//! ## Performance
575//!
576//! Prax is designed for high performance:
577//!
578//! - **Connection Pooling**: Built-in connection pool with configurable limits
579//! - **Prepared Statement Caching**: Automatic caching of prepared statements
580//! - **Batch Operations**: Efficient bulk create, update, and delete
581//! - **Lazy Loading**: Relations are loaded only when requested
582//!
583//! ## Further Reading
584//!
585//! - [Schema Documentation](schema/index.html)
586//! - [Query API](query/index.html)
587//! - [Configuration](config/index.html)
588//! - [Migration Guide](migrate/index.html)
589//! - [Examples](https://github.com/pegasusheavy/prax/tree/main/examples)
590
591#![cfg_attr(docsrs, feature(doc_cfg))]
592#![deny(missing_docs)]
593#![deny(rustdoc::broken_intra_doc_links)]
594
595/// Schema parsing and AST types.
596///
597/// This module provides everything needed to work with Prax schema files:
598///
599/// - [`parse_schema`](schema::parse_schema) - Parse a schema string
600/// - [`parse_schema_file`](schema::parse_schema_file) - Parse a schema from a file
601/// - [`validate_schema`](schema::validate_schema) - Parse and validate a schema
602/// - [`PraxConfig`](schema::PraxConfig) - Configuration from `prax.toml`
603/// - [`Schema`](schema::Schema) - The parsed schema representation
604///
605/// ## Example
606///
607/// ```rust,ignore
608/// use prax::schema::{parse_schema, validate_schema, PraxConfig};
609///
610/// // Parse a schema
611/// let schema = parse_schema(r#"
612///     model User {
613///         id    Int    @id @auto
614///         email String @unique
615///     }
616/// "#)?;
617///
618/// // Access schema information
619/// println!("Models: {:?}", schema.model_names().collect::<Vec<_>>());
620///
621/// // Load configuration
622/// let config = PraxConfig::from_file("prax.toml")?;
623/// println!("Database: {}", config.database.provider);
624/// ```
625pub mod schema {
626    pub use prax_schema::*;
627}
628
629// Re-export proc macros
630pub use prax_codegen::Model;
631pub use prax_codegen::prax_schema;
632
633/// Prelude module for convenient imports.
634///
635/// Import everything you need with a single line:
636///
637/// ```rust,ignore
638/// use prax::prelude::*;
639/// ```
640///
641/// This includes:
642/// - Schema parsing functions
643/// - Configuration types
644/// - Common traits and types
645pub mod prelude {
646    pub use crate::schema::{PraxConfig, Schema, parse_schema, parse_schema_file};
647    pub use crate::{Model, prax_schema};
648}
649
650// Re-export key types at the crate root
651pub use schema::{Schema, SchemaError};
652
653/// Error types for the Prax ORM.
654///
655/// The main error type is [`SchemaError`] which covers all schema-related
656/// errors including parsing, validation, and file I/O.
657pub mod error {
658    pub use prax_schema::SchemaError;
659}