Relationships

A player belongs to a team: the players table holds team_id, which points at a team. Canyon can generate lookups in both directions from that relationship. The database still needs its own foreign-key constraint if you want it enforced.

Suppose Player.team_id refers to Team.id:


#![allow(unused)]
fn main() {
use canyon_sql::macros::{canyon_entity, CanyonMapper, Crud, Fields};

#[derive(Debug, Fields, Crud, CanyonMapper)]
#[canyon_entity(table_name = "teams")]
pub struct Team {
    #[primary_key]
    pub id: i64,
    pub name: String,
}

#[derive(Debug, Fields, Crud, CanyonMapper)]
#[canyon_entity(table_name = "players")]
pub struct Player {
    #[primary_key]
    pub id: i64,
    #[foreign_key(references = Team::id)]
    pub team_id: i64,
    pub name: String,
}
}

The references path names a Rust type and field, not a SQL table string. Player needs Read (included in Crud), and Team needs mapping metadata. Canyon derives the name team from team_id and gives Player four methods:


#![allow(unused)]
fn main() {
let parent: Option<Team> = player.find_team().await?;
let children: Vec<Player> = Player::find_all_by_team(&team).await?;

let parent_on_other_db = player.find_team_with("reporting").await?;
let children_on_other_db =
    Player::find_all_by_team_with(&team, "reporting").await?;
}

The return type follows the direction of the lookup:

  • player.find_team() looks for one parent: Ok(None) means the lookup found none.
  • Player::find_all_by_team(&team) looks for children: Ok(vec![]) means there are none.

A query or mapping failure is an error in either direction. The _with variants also accept a compatible connection.

The referenced field can be something other than the parent's primary key. In that case, make sure your schema identifies a parent unambiguously—usually with a unique constraint. A fully qualified Rust path also works:


#![allow(unused)]
fn main() {
#[foreign_key(references = crate::models::Team::external_id)]
pub team_external_id: i64,
}

Canyon reads the parent's physical table name and schema from its entity metadata. TournamentDetails maps to tournament_details by default, but an explicit table_name works too. Keep that metadata, the Rust relationship, and the database constraint in agreement. The relationship tests exercise default and custom names on all three backends.