Schema Design¶
Learn how to design your data model with entities, fields, and relations.
Entities¶
An entity represents a type of object in your application (like User, Post, or Order).
Basic Entity¶
use ormdb_core::catalog::{EntityDef, FieldDef, FieldType, ScalarType};
let user = EntityDef::new("User", "id") // name and primary key field
.with_field(FieldDef::new("id", FieldType::Scalar(ScalarType::Uuid)))
.with_field(FieldDef::new("name", FieldType::Scalar(ScalarType::String)))
.with_field(FieldDef::new("email", FieldType::Scalar(ScalarType::String)));
Field Types¶
Scalar Types¶
| Type | Description | Rust | Example |
|---|---|---|---|
uuid |
128-bit UUID | ScalarType::Uuid |
"550e8400-e29b-41d4-a716-446655440000" |
string |
UTF-8 text | ScalarType::String |
"Hello" |
int32 |
32-bit integer | ScalarType::Int32 |
42 |
int64 |
64-bit integer | ScalarType::Int64 |
9223372036854775807 |
float32 |
32-bit float | ScalarType::Float32 |
3.14 |
float64 |
64-bit float | ScalarType::Float64 |
3.141592653589793 |
bool |
Boolean | ScalarType::Bool |
true / false |
bytes |
Binary data | ScalarType::Bytes |
[0x01, 0x02, 0x03] |
timestamp |
Microseconds since epoch | ScalarType::Timestamp |
1704067200000000 |
Field Options¶
// Required field (not nullable)
FieldDef::new("email", FieldType::Scalar(ScalarType::String))
.required()
// With default value
FieldDef::new("active", FieldType::Scalar(ScalarType::Bool))
.with_default(true)
// Auto-generate timestamp
FieldDef::new("created_at", FieldType::Scalar(ScalarType::Timestamp))
.with_default_current_timestamp()
// Auto-generate UUID
FieldDef::new("id", FieldType::Scalar(ScalarType::Uuid))
.with_default_auto_uuid()
// Indexed for fast lookups
FieldDef::new("email", FieldType::Scalar(ScalarType::String))
.indexed()
const fields = [
// Required field
{ name: "email", type: "string", required: true },
// With default value
{ name: "active", type: "bool", default: true },
// Auto-generate timestamp
{ name: "created_at", type: "timestamp", default: "current_timestamp" },
// Auto-generate UUID
{ name: "id", type: "uuid", default: "auto_uuid" },
// Indexed for fast lookups
{ name: "email", type: "string", indexed: true },
];
fields = [
# Required field
{"name": "email", "type": "string", "required": True},
# With default value
{"name": "active", "type": "bool", "default": True},
# Auto-generate timestamp
{"name": "created_at", "type": "timestamp", "default": "current_timestamp"},
# Auto-generate UUID
{"name": "id", "type": "uuid", "default": "auto_uuid"},
# Indexed for fast lookups
{"name": "email", "type": "string", "indexed": True},
]
Relations¶
Relations connect entities together.
One-to-Many¶
A user has many posts:
One-to-One¶
A user has one profile:
Many-to-Many¶
Users belong to many teams, teams have many users:
// Edge entity for many-to-many
let team_member = EntityDef::new("TeamMember", "id")
.with_field(FieldDef::new("id", FieldType::Scalar(ScalarType::Uuid)))
.with_field(FieldDef::new("user_id", FieldType::Scalar(ScalarType::Uuid)))
.with_field(FieldDef::new("team_id", FieldType::Scalar(ScalarType::Uuid)))
.with_field(FieldDef::new("role", FieldType::Scalar(ScalarType::String)));
// Relations
let user_memberships = RelationDef::one_to_many("memberships", "User", "id", "TeamMember", "user_id");
let team_memberships = RelationDef::one_to_many("memberships", "Team", "id", "TeamMember", "team_id");
let membership_user = RelationDef::many_to_one("user", "TeamMember", "user_id", "User", "id");
let membership_team = RelationDef::many_to_one("team", "TeamMember", "team_id", "Team", "id");
// Edge entity for many-to-many
const teamMember = {
name: "TeamMember",
primaryKey: "id",
fields: [
{ name: "id", type: "uuid" },
{ name: "user_id", type: "uuid", required: true },
{ name: "team_id", type: "uuid", required: true },
{ name: "role", type: "string" },
],
};
// Relations
const relations = [
{
name: "memberships",
from: { entity: "User", field: "id" },
to: { entity: "TeamMember", field: "user_id" },
cardinality: "one_to_many",
},
{
name: "memberships",
from: { entity: "Team", field: "id" },
to: { entity: "TeamMember", field: "team_id" },
cardinality: "one_to_many",
},
{
name: "user",
from: { entity: "TeamMember", field: "user_id" },
to: { entity: "User", field: "id" },
cardinality: "many_to_one",
},
{
name: "team",
from: { entity: "TeamMember", field: "team_id" },
to: { entity: "Team", field: "id" },
cardinality: "many_to_one",
},
];
# Edge entity for many-to-many
team_member = {
"name": "TeamMember",
"primaryKey": "id",
"fields": [
{"name": "id", "type": "uuid"},
{"name": "user_id", "type": "uuid", "required": True},
{"name": "team_id", "type": "uuid", "required": True},
{"name": "role", "type": "string"},
],
}
# Relations (same pattern as TypeScript)
Delete Behavior¶
Control what happens to related entities when a parent is deleted:
| Behavior | Description |
|---|---|
Cascade |
Delete related entities |
Restrict |
Prevent deletion if related entities exist |
SetNull |
Set foreign key to null |
use ormdb_core::catalog::DeleteBehavior;
// When a user is deleted, delete their posts
let user_posts = RelationDef::one_to_many("posts", "User", "id", "Post", "author_id")
.with_delete_behavior(DeleteBehavior::Cascade);
// Prevent deleting a team if it has members
let team_members = RelationDef::one_to_many("members", "Team", "id", "TeamMember", "team_id")
.with_delete_behavior(DeleteBehavior::Restrict);
Schema Bundles¶
A schema bundle is a complete, versioned snapshot of your data model:
Best Practices¶
- Use UUIDs for primary keys - They're globally unique and don't leak sequence information
- Add timestamps -
created_atandupdated_atare invaluable for debugging - Index foreign keys - Relations are faster when foreign keys are indexed
- Use meaningful relation names -
postsis clearer thanuser_post_rel - Plan for soft deletes - Consider adding a
deleted_atfield instead of hard deletes
Next Steps¶
- Querying Data - Learn how to query your schema
- Mutations - Insert, update, and delete data
- Migrations - Evolve your schema safely