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:
Designates a field as the entity’s primary key. Syntax: 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:
Ensures field values are unique across all entities. Syntax: 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:
Allows a field to be NULL (empty/missing). Syntax: 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:
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" , "[email protected] " ).
Execute ( ctx )
// If email already exists:
// ❌ UniqueConstraintError: Field 'email' must be unique
// Value: [email protected] 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
Field Types Learn about available field types
Annotations Optimize with backend annotations