sea-orm
sea-orm copied to clipboard
sea-orm-cli does not generate has_many relation if there is another one-to-one relation
Description
I have two tables in my schema which depend on each other. One of them is site
, a "main" table which has many foreign keys pointing to site.site_id
. Another is site_domain
, which has a one-to-many relationship to sites, that is, one site could have no custom domains, or many. However, site
s can (optionally) choose one domain as its "preferred" one, and there is a foreign key constraint from this column in site
to site_domain
.
The entity file generated for site
lacked a Relation
case for doing a JOIN where I want the site associated with a particular domain, instead it utilized a different foreign key, which was causing my query to not produce results as expected.
See "background" for further information.
Steps to Reproduce
- Create a postgres database.
- Apply a schema where, for tables which have a one-to-many relation, the one table has a foreign key constraint to one of the many.
- Generate entity files. I used
sea-orm-cli generate entity --verbose --database-url [url] --output-dir [dir]
. - Inspect the generated entity file and observe that there is no result for the many table in
Relation
which has#[sea_orm(has_many)]
.
Reproducible Example
A Postgres database with the given schema will produce the following entity files:
CREATE TABLE site (
site_id BIGSERIAL PRIMARY KEY,
custom_domain TEXT
);
CREATE TABLE site_domain (
domain TEXT PRIMARY KEY,
site_id BIGINT NOT NULL REFERENCES site(site_id)
);
ALTER TABLE site
ADD CONSTRAINT site_custom_domain_fk
FOREIGN KEY (custom_domain) REFERENCES site_domain(domain);
Entity file: site.rs
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "site")]
pub struct Model {
#[sea_orm(primary_key)]
pub site_id: i64,
#[sea_orm(column_type = "Text", nullable)]
pub custom_domain: Option<String>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::site_domain::Entity",
from = "Column::CustomDomain",
to = "super::site_domain::Column::Domain",
on_update = "NoAction",
on_delete = "NoAction"
)]
SiteDomain,
}
impl Related<super::site_domain::Entity> for Entity {
fn to() -> RelationDef {
Relation::SiteDomain.def()
}
}
impl ActiveModelBehavior for ActiveModel {}
Entity file: site_domain.rs
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "site_domain")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false, column_type = "Text")]
pub domain: String,
pub site_id: i64,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::site::Entity",
from = "Column::SiteId",
to = "super::site::Column::SiteId",
on_update = "NoAction",
on_delete = "NoAction"
)]
Site,
}
impl Related<super::site::Entity> for Entity {
fn to() -> RelationDef {
Relation::Site.def()
}
}
impl ActiveModelBehavior for ActiveModel {}
But, if I remove the second foreign key constraint, then it generates the has_many
relation for finding the associated Site
for a SiteDomain
:
CREATE TABLE site (
site_id BIGSERIAL PRIMARY KEY,
custom_domain TEXT -- does NOT have a foreign key relation!
);
CREATE TABLE site_domain (
domain TEXT PRIMARY KEY,
site_id BIGINT NOT NULL REFERENCES site(site_id)
);
Entity file: site.rs
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "site")]
pub struct Model {
#[sea_orm(primary_key)]
pub site_id: i64,
#[sea_orm(column_type = "Text", nullable)]
pub custom_domain: Option<String>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::site_domain::Entity")]
SiteDomain,
}
impl Related<super::site_domain::Entity> for Entity {
fn to() -> RelationDef {
Relation::SiteDomain.def()
}
}
impl ActiveModelBehavior for ActiveModel {}
Entity file: site_domain.rs
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "site_domain")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false, column_type = "Text")]
pub domain: String,
pub site_id: i64,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::site::Entity",
from = "Column::SiteId",
to = "super::site::Column::SiteId",
on_update = "NoAction",
on_delete = "NoAction"
)]
Site,
}
impl Related<super::site::Entity> for Entity {
fn to() -> RelationDef {
Relation::Site.def()
}
}
impl ActiveModelBehavior for ActiveModel {}
Expected Behavior
There should be multiple cases generated, one of which should be the simple has_many
case.
Actual Behavior
Only one Relation::SiteDomain
case is generated, which seems to be "overwriting" the has_many
case with the belongs_to
case.
Perhaps the naming should be numerical, e.g. SiteDomain1
, SiteDomain2
, or alternatively suffix-based, like SiteDomainCustomDomain
for the custom_domain
relation because it sees plain SiteDomain
(just the has_many
) already exists, or having that be named SiteDomainSite
or similar.
Reproduces How Often
The above minimal example consistently generates the entity files as described.
Background
This issue was discovered while attempting to do a JOIN between the site
and site_domain
tables. It was failing to find results, and on inspection, the query that was being generated was incorrect. After debugging, I realized that the wrong JOIN condition was used, per the entity file.
I was attempting to write code that would execute this SQL query:
SELECT *
FROM site
JOIN site_domain
ON site.site_id = site_domain.site_id
WHERE site_domain.domain = $1
To do this, I wrote the following SeaORM code:
let model = Site::find()
.join(JoinType::Join, site::Relation::SiteDomain.def())
.filter(site_domain::Column::Domain.eq(domain))
.one(txn)
.await?;
However the Relation::SiteDomain
that was generated was not has_many
:
#[sea_orm(
belongs_to = "super::site_domain::Entity",
from = "Column::CustomDomain",
to = "super::site_domain::Column::Domain",
on_update = "NoAction",
on_delete = "NoAction"
)]
SiteDomain,
I was expecting a simple #[sea_orm(has_many = "super::site_domain::Entity")]
like the other Relation
cases.
Workarounds
Presently, my workaround for this bug is to manually edit the entity file and add the case to the Relation
enum. However, I would like to have sea-orm-cli
wholly autogenerate my entity files to reduce maintenance burden.
See https://github.com/scpwiki/wikijump/pull/2084
Versions
Tested on sea-orm-cli versions 1.0.0 and 1.0.1