Skip to main content
Relations define how entities are connected to each other. ChameleonDB supports one-to-many and many-to-one relationships using the via keyword.

Relation Types

ChameleonDB supports two primary relation patterns:

One-to-Many (Has Many)

Defines a collection relationship where one entity has multiple related entities:
entity User {
    id: uuid primary,
    orders: [Order] via user_id,  // User has many Orders
}
Syntax:
fieldName: [TargetEntity] via foreign_key_field

Many-to-One (Belongs To)

Defines a single relationship where one entity belongs to another:
entity Order {
    id: uuid primary,
    user_id: uuid,
    user: User,  // Order belongs to User
}
Syntax:
fieldName: TargetEntity

The via Keyword

The via keyword specifies the foreign key field in the related entity:
entity User {
    id: uuid primary,
    email: string unique,
    
    // "via user_id" means: find Orders where order.user_id = user.id
    orders: [Order] via user_id,
}

entity Order {
    id: uuid primary,
    total: decimal,
    user_id: uuid,  // Foreign key field
    
    user: User,  // Reverse relation (no "via" needed)
}
The via keyword is only used for one-to-many relations (arrays). Many-to-one relations infer the foreign key automatically.

Bidirectional Relations

Most relationships are bidirectional - defined from both sides:
entity User {
    id: uuid primary,
    email: string unique,
    
    orders: [Order] via user_id,    // Forward: User → Orders
    posts: [Post] via author_id,     // Forward: User → Posts
}

entity Order {
    id: uuid primary,
    total: decimal,
    user_id: uuid,
    
    user: User,                      // Reverse: Order → User
}

entity Post {
    id: uuid primary,
    title: string,
    author_id: uuid,
    
    author: User,                    // Reverse: Post → User
}

Foreign Key Fields

Foreign key fields must be explicitly declared:
entity Order {
    id: uuid primary,
    total: decimal,
    user_id: uuid,     // ✓ Foreign key field explicitly declared
    
    user: User,        // Relation uses user_id implicitly
}
The foreign key field (user_id) must match the type of the target entity’s primary key.

Nested Relations

Relations can be traversed through multiple levels:
entity User {
    id: uuid primary,
    email: string unique,
    orders: [Order] via user_id,
}

entity Order {
    id: uuid primary,
    total: decimal,
    user_id: uuid,
    
    user: User,
    items: [OrderItem] via order_id,  // Nested relation
}

entity OrderItem {
    id: uuid primary,
    quantity: int,
    price: decimal,
    order_id: uuid,
    
    order: Order,
}
Querying nested relations:
// Eager load multiple levels
users := db.Query("User").
    Include("orders").
    Include("orders.items").  // Nested include
    Execute(ctx)

Self-Referential Relations

Entities can reference themselves:
entity Category {
    id: uuid primary,
    name: string,
    parent_id: uuid nullable,
    
    parent: Category nullable,
    children: [Category] via parent_id,
}

Complete Example

Here’s a complete e-commerce schema demonstrating all relation patterns:
entity User {
    id: uuid primary,
    email: string unique,
    name: string,
    created_at: timestamp default now(),
    
    // One-to-many relations
    orders: [Order] via user_id,
    posts: [Post] via author_id,
}

entity Order {
    id: uuid primary,
    total: decimal,
    status: string,
    created_at: timestamp default now(),
    user_id: uuid,
    
    // Many-to-one relation
    user: User,
    
    // One-to-many relation
    items: [OrderItem] via order_id,
}

entity OrderItem {
    id: uuid primary,
    quantity: int,
    price: decimal,
    order_id: uuid,
    product_id: uuid,
    
    // Many-to-one relations
    order: Order,
    product: Product,
}

entity Product {
    id: uuid primary,
    name: string,
    price: decimal,
    stock: int,
    
    // One-to-many relation
    order_items: [OrderItem] via product_id,
}

entity Post {
    id: uuid primary,
    title: string,
    content: string,
    author_id: uuid,
    created_at: timestamp default now(),
    
    // Many-to-one relation
    author: User,
}

Relation Validation

ChameleonDB validates relations at compile time:
Target Exists - The target entity must be defined in the schema
Foreign Key Type Match - Foreign key type must match the target’s primary key type
Foreign Key Exists - For via relations, the foreign key field must exist in the target entity
No Circular Ownership - Entities cannot form circular ownership dependencies

Querying Relations

Relations are traversed using the query API:

Eager Loading

Load related entities to avoid N+1 queries:
// Load users with their orders
users := db.Query("User").
    Include("orders").  // Eager load orders
    Execute(ctx)

for _, user := range users {
    fmt.Printf("%s has %d orders\n", user.Email, len(user.Orders))
}

Nested Includes

// Load users → orders → items (3 levels)
users := db.Query("User").
    Include("orders").
    Include("orders.items").  // Nested include
    Execute(ctx)

Multiple Relations

// Load multiple relations
users := db.Query("User").
    Include("orders").
    Include("posts").  // Load both orders and posts
    Execute(ctx)

Best Practices

Name foreign keys consistently - Use {entity}_id pattern (e.g., user_id, order_id)
Define bidirectional relations - Define both sides for easier querying
Use meaningful relation names - Choose names that reflect the domain relationship
// Good - clear domain meaning
entity Order {
    user_id: uuid,
    user: User,  // "user" is clear
}

// Better - more specific
entity Post {
    author_id: uuid,
    author: User,  // "author" is more specific than "user"
}
Use eager loading - Always use Include() to avoid N+1 query problems

Common Patterns

Parent-Child

entity Category {
    id: uuid primary,
    name: string,
    parent_id: uuid nullable,
    
    parent: Category nullable,
    children: [Category] via parent_id,
}

Author-Content

entity User {
    id: uuid primary,
    posts: [Post] via author_id,
    comments: [Comment] via author_id,
}

entity Post {
    id: uuid primary,
    author_id: uuid,
    author: User,
}

Order-Line Items

entity Order {
    id: uuid primary,
    items: [OrderItem] via order_id,
}

entity OrderItem {
    id: uuid primary,
    order_id: uuid,
    order: Order,
}

Limitations

Many-to-many not yet supported - Use a join entity pattern instead:
// Instead of direct many-to-many:
// tags: [Tag] many_to_many  // ✗ Not supported

// Use a join entity:
entity PostTag {
    id: uuid primary,
    post_id: uuid,
    tag_id: uuid,
    post: Post,
    tag: Tag,
}

Next Steps