Skip to main content
Constraints enforce data integrity rules at the schema level. ChameleonDB validates constraints both at compile time and runtime.

Primary Keys

Every entity must have exactly one primary key:
primary
constraint
Designates a field as the entity’s primary key.Syntax:
fieldName: type primary
Example:
entity User {
    id: uuid primary,  // Primary key
    email: string,
}
Rules:
  • Each entity must have exactly one primary field
  • Primary keys are automatically unique and non-null
  • Primary keys cannot be modified after creation
SQL Mapping: PRIMARY KEY

Primary Key Best Practices

// ✓ Good - UUID primary key
entity User {
    id: uuid primary,
}

// ✓ Also valid - String primary key
entity Country {
    code: string primary,  // e.g., "US", "UK"
}

// ✗ Bad - No primary key
entity User {
    email: string unique,  // Not enough - needs primary key
}

// ✗ Bad - Multiple primary keys
entity User {
    id: uuid primary,
    email: string primary,  // Error: only one primary key allowed
}

Unique Constraints

Enforce uniqueness across all rows:
unique
constraint
Ensures field values are unique across all entities.Syntax:
fieldName: type unique
Example:
entity User {
    id: uuid primary,
    email: string unique,     // No two users can have the same email
    username: string unique,  // No two users can have the same username
}
Rules:
  • Unique fields cannot have duplicate values
  • NULL values are considered distinct (multiple NULLs allowed if nullable)
  • Primary keys are implicitly unique
SQL Mapping: UNIQUE

Unique Constraint Examples

entity Product {
    id: uuid primary,
    sku: string unique,        // Product SKU must be unique
    name: string,              // Name can be duplicated
}

entity Email {
    id: uuid primary,
    address: string unique,    // Email addresses must be unique
    user_id: uuid,
}

Nullable Fields

Allow fields to have NULL values:
nullable
constraint
Allows a field to be NULL (empty/missing).Syntax:
fieldName: type nullable
Example:
entity User {
    id: uuid primary,
    email: string,
    bio: string nullable,        // Bio is optional
    age: int nullable,           // Age is optional
    deleted_at: timestamp nullable,  // NULL until deleted
}
Rules:
  • Fields are NOT NULL by default
  • Primary keys cannot be nullable
  • Foreign keys can be nullable (optional relations)
SQL Mapping: NULL (default is NOT NULL)

Nullable Best Practices

// ✓ Good - Explicit nullable for optional fields
entity User {
    id: uuid primary,
    email: string,              // Required
    bio: string nullable,       // Optional
    middle_name: string nullable,  // Optional
}

// ✓ Good - Nullable foreign key (optional relation)
entity Post {
    id: uuid primary,
    author_id: uuid nullable,   // Post can exist without author
    author: User nullable,
}

// ✗ Avoid - Don't make everything nullable
entity User {
    id: uuid primary,
    email: string nullable,     // Bad: email should be required
    name: string nullable,      // Bad: name should be required
}
Use nullable sparingly. Non-nullable fields make your domain model clearer and prevent many runtime errors.

Default Values

Specify default values for fields:
default
constraint
Provides a default value when the field is not specified.Syntax:
fieldName: type default value
Available defaults:
  • now() - Current timestamp (for timestamp fields)
  • Literal values (strings, numbers, booleans)
Example:
entity User {
    id: uuid primary,
    email: string,
    created_at: timestamp default now(),  // Auto-set to current time
    active: bool default true,            // Default to true (future)
    role: string default "user",          // Default role (future)
}
SQL Mapping: DEFAULT value

Current Support (v1.0)

Currently, only default now() is fully supported for timestamp fields. Additional default value types are planned for future releases.
// ✓ Supported now
entity Order {
    id: uuid primary,
    created_at: timestamp default now(),
    updated_at: timestamp default now(),
}

// 🔮 Coming in future versions
entity User {
    active: bool default true,
    role: string default "user",
    score: int default 0,
}

Combining Constraints

Constraints can be combined on a single field:
entity User {
    id: uuid primary,                      // primary only
    email: string unique,                  // unique only
    nickname: string unique nullable,      // unique + nullable
    bio: string nullable,                  // nullable only
    created_at: timestamp default now(),   // default only
    deleted_at: timestamp nullable,        // nullable only
}

Constraint Order

Constraints can appear in any order:
// All equivalent:
email: string unique nullable
email: string nullable unique
email: nullable unique string  // ✗ Type must come first
The field type must always come immediately after the colon (:). Constraints follow the type.

Complete Example

Here’s a complete schema demonstrating all constraints:
entity User {
    // Primary key (required, unique, non-null)
    id: uuid primary,
    
    // Unique fields
    email: string unique,
    username: string unique,
    
    // Required fields (non-null by default)
    name: string,
    
    // Optional fields
    bio: string nullable,
    age: int nullable,
    avatar_url: string nullable,
    
    // Timestamps with defaults
    created_at: timestamp default now(),
    updated_at: timestamp default now(),
    deleted_at: timestamp nullable,
    
    // Relations
    orders: [Order] via user_id,
}

entity Order {
    id: uuid primary,
    total: decimal,
    status: string,
    
    // Required foreign key
    user_id: uuid,
    user: User,
    
    // Optional foreign key
    coupon_id: uuid nullable,
    coupon: Coupon nullable,
    
    created_at: timestamp default now(),
    items: [OrderItem] via order_id,
}

entity Coupon {
    // String primary key
    code: string primary,
    discount: decimal,
    expires_at: timestamp,
    
    orders: [Order] via coupon_id,
}

entity OrderItem {
    id: uuid primary,
    quantity: int,
    price: decimal,
    order_id: uuid,
    
    order: Order,
}

Validation Rules

ChameleonDB enforces these validation rules:
Exactly One Primary Key - Each entity must have exactly one primary field
Primary Keys Non-Nullable - Primary keys cannot be nullable
Primary Keys Are Unique - Primary keys are implicitly unique
Type Compatibility - Default values must match the field type
Constraint Conflicts - Constraints must not conflict (e.g., primary nullable is invalid)

Runtime Enforcement

Constraints are enforced at runtime:
// Unique constraint violation
result, err := db.Insert("User").
    Set("email", "ana@mail.com").
    Execute(ctx)

// If email already exists:
// ❌ UniqueConstraintError: Field 'email' must be unique
//    Value: ana@mail.com already exists
// NOT NULL constraint violation
result, err := db.Insert("User").
    Set("id", uuid.New()).
    // Missing required 'email' field
    Execute(ctx)

// ❌ NotNullError: Field 'email' cannot be NULL

Best Practices

Use UUIDs for primary keys - UUIDs prevent conflicts and are globally unique
Mark optional fields as nullable - Be explicit about which fields can be empty
Use unique constraints for natural keys - Email addresses, usernames, SKUs, etc.
Use default now() for timestamps - Automatically track creation and update times
Avoid nullable on business-critical fields - Required fields make your domain model clearer and prevent bugs.

Common Patterns

Soft Deletes

entity User {
    id: uuid primary,
    email: string unique,
    deleted_at: timestamp nullable,  // NULL = not deleted
}

Audit Timestamps

entity Post {
    id: uuid primary,
    title: string,
    created_at: timestamp default now(),
    updated_at: timestamp default now(),
}

Optional Relations

entity Order {
    id: uuid primary,
    user_id: uuid nullable,     // Order can exist without user
    user: User nullable,
}

Natural Keys

entity Country {
    code: string primary,       // "US", "UK" as primary key
    name: string,
}

entity Product {
    id: uuid primary,
    sku: string unique,         // Business identifier
    name: string,
}

Next Steps