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}