mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-25 14:25:02 +00:00
11 KiB
11 KiB
Entity Relationship Schema Specification
Version: 2.0 Date: 2026-02-04
Relationship Types
MetaBuilder entity schemas support all standard relationship patterns using explicit relations declarations.
1. One-to-One (1:1)
Pattern: User has one Profile, Profile belongs to one User
entity: User
fields:
id: {type: uuid, primary: true}
username: {type: string, required: true}
relations:
profile:
type: one-to-one
entity: Profile
foreign_key: userId # Field in Profile that points to User
entity: Profile
fields:
id: {type: uuid, primary: true}
userId: {type: uuid, required: true, unique: true}
bio: {type: text}
relations:
user:
type: belongs-to
entity: User
foreign_key: userId # This field
2. One-to-Many (1:N)
Pattern: User has many Posts, Post belongs to one User
entity: User
fields:
id: {type: uuid, primary: true}
username: {type: string}
relations:
posts:
type: has-many
entity: Post
foreign_key: authorId # Field in Post
entity: Post
fields:
id: {type: uuid, primary: true}
title: {type: string}
authorId: {type: uuid, required: true}
relations:
author:
type: belongs-to
entity: User
foreign_key: authorId # This field
3. Many-to-Many (M:N)
Pattern: User has many Roles, Role has many Users (through UserRole join table)
entity: User
fields:
id: {type: uuid, primary: true}
username: {type: string}
relations:
roles:
type: many-to-many
entity: Role
through: UserRole # Join table name
foreign_key: userId # FK in UserRole
target_key: roleId # Other FK in UserRole
entity: Role
fields:
id: {type: uuid, primary: true}
name: {type: string}
relations:
users:
type: many-to-many
entity: User
through: UserRole
foreign_key: roleId
target_key: userId
entity: UserRole
fields:
id: {type: uuid, primary: true}
userId: {type: uuid, required: true}
roleId: {type: uuid, required: true}
assignedAt: {type: bigint}
indexes:
- fields: [userId, roleId]
unique: true
relations:
user:
type: belongs-to
entity: User
foreign_key: userId
role:
type: belongs-to
entity: Role
foreign_key: roleId
4. Self-Referential (Tree/Graph)
Pattern: Category has parent Category (recursive hierarchy)
entity: Category
fields:
id: {type: uuid, primary: true}
name: {type: string}
parentId: {type: uuid, nullable: true}
relations:
parent:
type: belongs-to
entity: Category # Self-reference
foreign_key: parentId
nullable: true
children:
type: has-many
entity: Category # Self-reference
foreign_key: parentId
5. Polymorphic (Generic Relations)
Pattern: Comment can belong to Post OR Video OR Product
entity: Comment
fields:
id: {type: uuid, primary: true}
content: {type: text}
commentableId: {type: uuid, required: true}
commentableType: {type: string, required: true} # "Post", "Video", "Product"
indexes:
- fields: [commentableId, commentableType]
relations:
commentable:
type: polymorphic
foreign_key: commentableId
type_key: commentableType
entities:
- Post
- Video
- Product
entity: Post
fields:
id: {type: uuid, primary: true}
title: {type: string}
relations:
comments:
type: has-many
entity: Comment
polymorphic: true
as: commentable # Matches relation name in Comment
6. Through Relationships (Has-Many-Through)
Pattern: Country has many Users through Cities
entity: Country
fields:
id: {type: uuid, primary: true}
name: {type: string}
relations:
cities:
type: has-many
entity: City
foreign_key: countryId
users:
type: has-many-through
entity: User
through: City # Intermediate entity
source_key: countryId # FK in City → Country
target_key: cityId # FK in User → City
entity: City
fields:
id: {type: uuid, primary: true}
name: {type: string}
countryId: {type: uuid, required: true}
relations:
country:
type: belongs-to
entity: Country
foreign_key: countryId
users:
type: has-many
entity: User
foreign_key: cityId
entity: User
fields:
id: {type: uuid, primary: true}
name: {type: string}
cityId: {type: uuid, required: true}
relations:
city:
type: belongs-to
entity: City
foreign_key: cityId
country:
type: has-one-through
entity: Country
through: City
source_key: cityId # User.cityId
target_key: countryId # City.countryId
Relation Field Specification
Required Fields
relations:
<relation_name>:
type: <relationship_type> # Required
entity: <target_entity> # Required
foreign_key: <field_name> # Required (except polymorphic)
Optional Fields
relations:
<relation_name>:
# Optional configuration
nullable: true|false # Can relation be null? (default: false)
cascade_delete: true|false # Delete related records? (default: false)
cascade_update: true|false # Update related FKs? (default: false)
on_delete: cascade|set_null|restrict|no_action # FK constraint
on_update: cascade|set_null|restrict|no_action # FK constraint
eager_load: true|false # Auto-load in queries? (default: false)
inverse_of: <relation_name> # Name of inverse relation
Polymorphic-Specific Fields
relations:
commentable:
type: polymorphic
foreign_key: commentableId
type_key: commentableType # Required - field storing entity type
entities: [Post, Video] # Required - allowed entity types
Many-to-Many Specific Fields
relations:
roles:
type: many-to-many
entity: Role
through: UserRole # Required - join table
foreign_key: userId # Required - FK in join table
target_key: roleId # Required - other FK in join table
attributes: [assignedAt] # Optional - extra fields from join table
Relationship Types Summary
| Type | Description | Example |
|---|---|---|
belongs-to |
N:1 relationship | Post → User (many posts belong to one user) |
has-one |
1:1 relationship | User → Profile (one user has one profile) |
has-many |
1:N relationship | User → Posts (one user has many posts) |
many-to-many |
M:N via join table | User ↔ Roles (via UserRole) |
one-to-one |
1:1 bidirectional | User ↔ Profile |
polymorphic |
N:1 to multiple types | Comment → Post|Video|Product |
has-many-through |
1:N via intermediate | Country → Users (through Cities) |
has-one-through |
1:1 via intermediate | User → Country (through City) |
FK Constraints
on_delete Behavior
| Value | Behavior |
|---|---|
cascade |
Delete related records when parent deleted |
set_null |
Set FK to NULL when parent deleted |
restrict |
Prevent deletion if related records exist |
no_action |
Database decides (usually same as restrict) |
on_update Behavior
| Value | Behavior |
|---|---|
cascade |
Update FK when parent ID changes |
set_null |
Set FK to NULL when parent ID changes |
restrict |
Prevent update if related records exist |
no_action |
Database decides |
Example: Full Forum Schema with Relations
entity: ForumCategory
fields:
id: {type: cuid, primary: true}
name: {type: string, required: true}
parentId: {type: cuid, nullable: true}
relations:
parent:
type: belongs-to
entity: ForumCategory
foreign_key: parentId
nullable: true
on_delete: set_null
children:
type: has-many
entity: ForumCategory
foreign_key: parentId
cascade_delete: false
threads:
type: has-many
entity: ForumThread
foreign_key: categoryId
cascade_delete: true
---
entity: ForumThread
fields:
id: {type: cuid, primary: true}
categoryId: {type: cuid, required: true}
authorId: {type: uuid, required: true}
title: {type: string, required: true}
relations:
category:
type: belongs-to
entity: ForumCategory
foreign_key: categoryId
on_delete: cascade
author:
type: belongs-to
entity: User
foreign_key: authorId
on_delete: restrict # Can't delete user with threads
posts:
type: has-many
entity: ForumPost
foreign_key: threadId
cascade_delete: true # Delete posts when thread deleted
---
entity: ForumPost
fields:
id: {type: cuid, primary: true}
threadId: {type: cuid, required: true}
authorId: {type: uuid, required: true}
content: {type: text, required: true}
relations:
thread:
type: belongs-to
entity: ForumThread
foreign_key: threadId
on_delete: cascade
author:
type: belongs-to
entity: User
foreign_key: authorId
on_delete: restrict
Implementation in C++ DBAL
EntitySchemaLoader Enhancement
Add RelationDef struct:
struct RelationDef {
std::string name;
std::string type; // belongs-to, has-one, has-many, etc.
std::string entity; // Target entity name
std::string foreignKey; // FK field name
std::optional<std::string> targetKey; // For M:N
std::optional<std::string> through; // Join table for M:N
std::optional<std::string> typeKey; // For polymorphic
std::vector<std::string> entities; // For polymorphic
bool nullable = false;
bool cascadeDelete = false;
bool cascadeUpdate = false;
std::string onDelete = "restrict";
std::string onUpdate = "no_action";
bool eagerLoad = false;
};
struct EntitySchema {
std::string name;
std::vector<EntityField> fields;
std::vector<EntityIndex> indexes;
std::vector<RelationDef> relations; // ← NEW
ACLDefinition acl;
};
Usage in Generic Operations
// Eager loading example
auto result = adapter.read("users", userId);
if (result.isOk()) {
auto user = result.getValue();
// Auto-load relations marked eager_load: true
auto schema = adapter.getEntitySchema("users");
for (const auto& rel : schema.relations) {
if (rel.eagerLoad) {
if (rel.type == "belongs-to") {
auto fkValue = user[rel.foreignKey];
auto related = adapter.read(rel.entity, fkValue.get<std::string>());
user[rel.name] = related.getValue();
}
else if (rel.type == "has-many") {
Json filter = {{rel.foreignKey, user["id"]}};
auto related = adapter.list(rel.entity, {.filter = filter});
user[rel.name] = related.getValue().items;
}
}
}
}
Next Steps
- Update existing entity YAML files to include relations
- Update EntitySchemaLoader to parse relations
- Add relationship helpers to generic adapters (e.g.,
loadRelation(),saveWithRelations()) - Create Prisma schema generator from YAML schemas (see next task)