Define a schema
A blog needs users and posts, with each post pointing to its author. Describe
those models in rent/schema.rsl. Rent uses them to generate both the Rust client
and database migrations:
datasource db {
provider = "postgresql"
}
model User {
id BigInt @id @default(autoincrement())
email String @unique @db.VarChar(320) @rent.sensitive
version UInt @rent.version
posts Post[]
}
model Post {
id BigInt @id @default(autoincrement())
author_id BigInt
author User @relation(fields: [author_id], references: [id], onDelete: Cascade)
title String
@@index([author_id])
}User, User?, and User[] express required to-one, optional to-one, and to-many relationships. Rent resolves both sides of a relationship, synthesizes a correctly typed foreign key for compact declarations, and chooses stable join storage for implicit many-to-many relationships.
Naming
Prefer Rust's idiom and the schema needs almost no naming annotations. A model's table and a field's column are
both derived, so the declaration above needs neither @@map nor @map:
model Userstores tousers, the model name lowercased with ans. Use@@map("...")for a name that rule does not produce, including an irregular plural.author_idstores to the columnauthor_id. Use@map("...")only when the column genuinely differs, as in a database you did not create.- An enum variant stores its own snake_case form, so
DRAFTstoresdraftwithout@map.
Generated code is Rust, so identifiers are snake_case whatever the schema writes. A field declared authorId
still generates author_id and stores to author_id, which keeps a schema ported from another tool working
unchanged. Prefer snake_case in new schemas anyway: it is what the generated code will read as, so the schema and
the code agree. Two members that converge on one identifier, such as authorId beside author_id, are rejected
rather than silently merged.
Run these commands while editing:
rent validate
rent format
rent describe
rent generate
rent migrate preflightrent generate reads and validates the schema, then produces Rust models, query
builders, mutation methods, relationship loaders, and the typed client. Import
those generated types in application code instead of writing a second set of
model structs. Rent-specific options use the rent namespace.
For a larger codebase, set schema in rent.toml to a directory. Rent recursively merges .rsl files in lexical path order and resolves relationships across file boundaries.
Repeated fields and model attributes can live in a mixin, applied with
use Name inside each model. Mixins can share creator and workspace relations,
timestamps, or composite keys while each model keeps its own table. They are
optional and independent of permissions. See Model mixins.
Rent-specific database objects remain beside the model they affect:
view ActiveUser {
id BigInt
email String
@@rent.sql("""
SELECT id, email FROM users WHERE active
""")
}
extension vector {
name = "vector"
version = "0.8.1"
schema = "extensions"
}Triple-quoted strings are an RSL extension intended for readable SQL. Dotted native attributes such as @db.Uuid remain valid, while differentiated features use @rent.version, @rent.generated, @rent.join, @@rent.check, @@rent.policy, and @@rent.rowLevelSecurity. Database connection URLs remain in Rent's project configuration and environment rather than the RSL datasource block, keeping the data model separate from runtime configuration.
Native PostgreSQL enums
Enums normally use portable text storage with generated Rust variants. Opt into a managed PostgreSQL enum when you also want the database to enforce its labels and comparison order:
enum PostState {
DRAFT
REVIEW
PUBLISHED
@@rent.native
@@schema("publishing")
}
model Post {
id BigInt @id
state PostState @default(DRAFT)
}The model requires PostgreSQL. Rent creates the namespace and type before its table, binds enum values with
the correct SQL type, and inspects ordered labels for drift. PostState? permits SQL NULL. PostState[] remains
a typed JSON collection rather than a native SQL array. Keep ordinary scalar fields as model identities.
A unique native enum field can be the referenced key of a relationship. Eager loading, traversal, connect
helpers, upserts, and tenant scoping treat it like any other key.
Adding labels is supported without rewriting existing rows. Put enum additions in their own migration and apply it before a migration that uses the new label in a default or data change. PostgreSQL requires that commit boundary. Removing or reordering existing labels requires a reviewed data migration. Rent does not silently replace the type or discard stored values.
