ormkit 0.3.0

A compile-time safe async ORM for PostgreSQL powered by SQLx
Documentation
# Relationships Guide

This guide covers entity relationships in Ormkit, including BelongsTo, HasMany, and N+1 query prevention.

**Status Note:** Relationship loading is functional but the API is still being refined. BelongsTo and HasMany work well. One-to-one and many-to-many relationships are planned.

---

## Table of Contents

1. [BelongsTo (Many-to-One)]#belongsto-many-to-one
2. [HasMany (One-to-Many)]#hasmany-one-to-many
3. [Eager Loading]#eager-loading
4. [N+1 Query Prevention]#n1-query-prevention
5. [Query Count Examples]#query-count-examples

---

## BelongsTo (Many-to-One)

A `BelongsTo` relationship represents ownership where one entity belongs to another.

### Defining BelongsTo

```rust
#[derive(ormkit::Entity)]
#[ormkit(table = "posts")]
struct Post {
    #[ormkit(id)]
    id: Uuid,
    title: String,
    content: String,

    // Foreign key to users table
    #[ormkit(belongs_to = "User")]
    user_id: Uuid,
}

#[derive(ormkit::Entity)]
#[ormkit(table = "users")]
struct User {
    #[ormkit(id)]
    id: Uuid,
    name: String,
    email: String,
}
```

### Loading BelongsTo Relationships

```rust
use ormkit::relations::RelationBuilder;

// Load posts with their users
let posts = Post::query().fetch_all(&pool).await?;

// Load users for all posts in a single query
let posts_with_users = RelationBuilder::for_entities(posts)
    .load::<User>(&pool)
    .await?;

for loaded in posts_with_users {
    println!("Post: {} by {}", loaded.entity.title, loaded.related.name);
}
```

### Using the .with() API

```rust
use ormkit::WithRelations;

// Load single post with user
let post = Post::query().fetch_one(&pool).await?;
let post_with_user = post.with::<User>().load(&pool).await?;

println!("Post: {} by {}", post_with_user.entity.title, post_with_user.related.unwrap().name);

// Load multiple posts with users
let posts = Post::query().fetch_all(&pool).await?;
let posts_with_users = posts.with::<User>().load(&pool).await?;
```

---

## HasMany (One-to-Many)

A `HasMany` relationship represents an entity that contains multiple related entities.

### Defining HasMany

```rust
#[derive(ormkit::Entity)]
#[ormkit(table = "users")]
struct User {
    #[ormkit(id)]
    id: Uuid,
    name: String,

    // Note: This field tracks the relationship but isn't stored
    #[ormkit(has_many = "Post")]
    posts: Vec<Uuid>, // Stores post IDs for reference
}

#[derive(ormkit::Entity)]
#[ormkit(table = "posts")]
struct Post {
    #[ormkit(id)]
    id: Uuid,
    title: String,

    // Foreign key back to user
    user_id: Uuid,
}
```

### Loading HasMany Relationships

```rust
use ormkit::WithRelations;

// Load users with their posts
let users = User::query().fetch_all(&pool).await?;

let users_with_posts = users.with_many::<Post>().load(&pool).await?;

for (user, posts) in users_with_posts {
    println!("User: {}", user.name);
    for post in posts {
        println!("  - {}", post.title);
    }
}
```

---

## Eager Loading

Eager loading prevents N+1 queries by loading related entities in batch.

### The N+1 Problem

**Without eager loading (N+1 queries):**
```rust
// BAD: N+1 queries
let posts = Post::query().fetch_all(&pool).await?; // 1 query

for post in posts {
    // N additional queries (one per post)
    let user = User::query()
        .filter(User::id().eq(post.user_id))
        .fetch_one(&pool).await?;
    println!("{} by {}", post.title, user.name);
}
```

**With eager loading (2 queries):**
```rust
// GOOD: 2 queries total
let posts = Post::query().fetch_all(&pool).await?; // 1 query

let posts_with_users = posts.with::<User>().load(&pool).await?; // 1 query

for loaded in posts_with_users {
    println!("{} by {}", loaded.entity.title, loaded.related.name);
}
```

### Multiple Relationships

You can chain eager loads for multiple relationships:

```rust
let posts = Post::query().fetch_all(&pool).await?;

// Load users first
let posts_with_users = posts.with::<User>().load(&pool).await?;

// Then load comments for each post
// (This would require nested relationship support - planned feature)
```

---

## N+1 Query Prevention

Ormkit prevents N+1 queries through:

1. **Batch Loading**: Load all related entities in a single query
2. **IN Clauses**: Use `WHERE id IN (...)` instead of individual queries
3. **RelationBuilder**: Centralized loading logic

### How It Works

```rust
// Internally, this generates:
SELECT * FROM posts; // Get all posts

// Then:
SELECT * FROM users
WHERE id IN (
    'uuid1', 'uuid2', 'uuid3', ... // All post.user_id values
);
```

This reduces **N queries** to **2 queries** regardless of entity count.

---

## Query Count Examples

### Example 1: Blog Posts with Authors

**Naive approach (N+1):**
```rust
// 1 query to fetch posts
// N queries to fetch authors (one per post)
// Total: 101 queries for 100 posts
```

**Eager loading:**
```rust
let posts = Post::query().fetch_all(&pool).await?; // 1 query
let posts_with_authors = posts.with::<User>().load(&pool).await?; // 1 query
// Total: 2 queries for 100 posts
```

**Improvement: 50x reduction in queries**

### Example 2: Users with Posts

**Naive approach (N+1):**
```rust
// 1 query to fetch users
// N queries to fetch posts (one per user)
// Total: 51 queries for 50 users
```

**Eager loading:**
```rust
let users = User::query().fetch_all(&pool).await?; // 1 query
let users_with_posts = users.with_many::<Post>().load(&pool).await?; // 1 query
// Total: 2 queries for 50 users
```

**Improvement: 25x reduction in queries**

---

## Planned Features

### One-to-One Relationships

```rust
// Planned syntax (not yet implemented)
#[derive(ormkit::Entity)]
struct User {
    #[ormkit(id)]
    id: Uuid,
    #[ormkit(has_one = "Profile")]
    profile: Option<Uuid>,
}

let user_with_profile = user.with_one::<Profile>().load(&pool).await?;
```

### Many-to-Many Relationships

```rust
// Planned syntax (not yet implemented)
#[ormkit(join_table = "post_tags")]
struct PostTags {
    post_id: Uuid,
    tag_id: Uuid,
}

#[derive(ormkit::Entity)]
struct Post {
    #[ormkit(id)]
    id: Uuid,
    #[ormkit(many_to_many = "Tag")]
    tags: Vec<Tag>,
}

let posts_with_tags = posts.with_many::<Tag>().load(&pool).await?;
```

### Nested Eager Loading

```rust
// Planned: Load posts with users and user comments
let posts_with_all = posts
    .with::<User>()
    .with_many::<Comment>()
    .load(&pool)
    .await?;
```

---

## Best Practices

1. **Always use eager loading** for relationships in loops
2. **Measure query counts** in development
3. **Use .with() API** for cleaner syntax
4. **Avoid relationship loading** for single entities when not needed
5. **Consider join queries** for complex filtering (planned feature)

---

## See Also

- [Core ORM Guide]CORE_GUIDE.md - Basic ORM usage
- [Type-Safe Queries]CORE_GUIDE.md#type-safe-queries - Query DSL
- [Performance Guide]PERFORMANCE_OPTIMIZATIONS.md - Performance optimization

---

**Current Status**: BelongsTo and HasMany relationships are functional. API ergonomics are being refined. One-to-one and many-to-many relationships are planned.