Model relation

This topic introduces the concept of model relation. To learn how to write relation input through frontend clients or raw HTTP requests, head over to relation queries GUIDE.

A relation connects two models and setup their relationship by foreign keys or join table. Relations are categorized into 1-to-1, 1-to-many and many-to-many depending on the nature of how the relationship is implemented. They also fall in two main categories: intrinsic relation and coincidence relation, depending on whether a foreign key is declared.

Fields which has a primary index or unique index can be used as foreign key reference. If a compound unique index is used, multiple keys should be specified in order. Let's see code examples.

One-to-one relation

An object from model A has exact 0 or 1 referenced object from model B, and vice versa.

model Profile {
  @id @autoIncrement @readonly
  id: Int
  @unique
  name: String
  @foreignKey
  userId: Int
  @relation(fields: .userId, references: .id)
  user: User
}
 
model User {
  @id @autoIncrement @readonly
  id: Int
  @unique
  email: String
  @relation(fields: .id, references: .userId)
  profile: Profile
}

In this example, a user has a profile, and a profile has a user. The foreign key is userId on Profile model.

One-to-many relation

An object from model A has any number of referenced objects from model B. While an object from model B has exact 0 or 1 referenced object from model A.

model Dog {
  @id @autoIncrement @readonly
  id: Int
  @unique
  name: String
  @foreignKey
  userId: Int
  @relation(fields: .userId, references: .id)
  user: User
}
 
model User {
  @id @autoIncrement @readonly
  id: Int
  @unique
  email: String
  @relation(fields: .id, references: .userId)
  dogs: Dog[]
}

In this example, a dog has an owner, and a user has many dogs. The foreign key is userId on Dog model.

Many-to-many relation

An object from model A has any number of referenced objects from model B and vice versa. A many-to-many relationship is defined with join table.

model Artist {
  @id @autoIncrement @readonly
  id: Int
  name: String
  @relation(through: ArtistAndSong, local: .artist, foreign: .song)
  songs: Song[]
}
 
model Song {
  @id @autoIncrement @readonly
  id: Int
  name: String
  @relation(through: ArtistAndSong, local: .song, foreign: .artist)
  artists: Artist[]
}
 
@id([.artistId, .songId])
model ArtistAndSong {
  @foreignKey
  artistId: Int
  @foreignKey
  songId: Int
  @relation(fields: .artistId, references: .id)
  artist: Artist
  @relation(fields: .songId, references: .id)
  song: Song
}

In this example, an artist has many songs, and a song is sang by one or more artists. The foreign keys are defined on the join table instead of any of the related models.

Intrinsic relation

A relation is intrinsic when a foreignKey is marked on some field. These relations can trigger actions if one of the objects is being deleted or updated. The objects indeed have relationships. They are tightly coupled. All the examples above have intrinsic relations.

Coincidence relation

A relation is coincidence relation if no foreignKey is marked. These relations won't trigger actions if one of the objects is being deleted or updated. If some value matches, they have a relationship. If some value changes, the relationship is broken. They have a relation due to coincidence. They are not belonging to each other. At some point, their data matches due to some field value.

model City {
  @id @autoIncrement @readonly
  id: Int
  @unique
  name: String
  @relation(fields: .name, references: .cityName)
  views: View[]
}
 
model View {
  @id @autoIncrement @readonly
  id: Int
  cityName: String
  number: Int
  @relation(fields: cityName, references: .name)
  city: City
}

A city has views, but the person who is typing this may type wrongly. If he modify the cityName with an updation, the related city will not be updated.

Update rule

Update rule designates what to happen to this object if the related object is being updated.

To specify an update rule, add a labeled argument onUpdate: to the @relation decorator.

model Profile {
  @id
  id: Int
  @unique
  name: String
  @foreignKey
  userId: Int
  @relation(fields: .userId, references: .id, onUpdate: .update)
  user: User
}
 
model User {
  @id
  id: Int
  @unique
  email: String
  @relation(fields: .id, references: .userId, onUpdate: .update)
  profile: Profile
}

The available update rules are:

  • noAction doesn't take any actions and leave the data in the database in a broken state
  • nullify set the related value to null
  • update update the related value to match the related object
  • delete delete this object
  • deny doesn't allow the value on the field otherside to be updated
  • default reset the related value to the default value

Delete rule

Delete rule designates what to happen to this object if the related object is being deleted.

To specify a delete rule, add a labeled argument onDelete: to the @relation decorator.

model Profile {
  @id
  id: Int
  @unique
  name: String
  @foreignKey
  userId: Int
  @relation(fields: .userId, references: .id, onDelete: .cascade)
  user: User
}
 
model User {
  @id
  id: Int
  @unique
  email: String
  @relation(fields: .id, references: .userId, onDelete: .cascade)
  profile: Profile
}

The available delete rules are:

  • noAction no action is taken and leave the data in the database in a broken state
  • nullify set the related value to null
  • cascade delete this object, too
  • deny prevent related objects from being deleted
  • default set the related value to the field's default value