Несколько внешних ключей ссылаются на одну и ту же таблицу в Diesel

Я пытаюсь создать структуру, которая дважды ссылается на одну и ту же таблицу. Цель этого - создать своего рода иерархию категорий. Вот что я пытаюсь сделать для следующих таблиц:

create table product_category_rollup(
    id serial primary key,
    upper_category_id integer not null,
    lower_category_id integer not null,
    foreign key (upper_category_id) references product_category(id),
    foreign key (lower_category_id) references product_category(id)
);

create table product_category(
    id serial primary key,
    name varchar unique not null
);

Я пытаюсь создать соответствующие структуры, как в:

#[derive(Identifiable, Queryable)]
#[table_name = "product_category"]
pub struct ProductCategory {
    id: i32,
    name: String,
}

#[derive(Queryable, Identifiable, Associations)]
#[belongs_to(ProductCategory, foreign_key="upper_category_id")]
#[belongs_to(ProductCategory, foreign_key="lower_category_id")]
#[table_name = "product_category_rollup"]
pub struct ProductCategoryRollup {
    id: i32,
    upper_category_id: i32,
    lower_category_id: i32,
}

Я получаю сообщение об ошибке:

error[E0119]: conflicting implementations of trait `diesel::associations::BelongsTo<entities::ProductCategory>` for type `entities::ProductCategoryRollup`:
  --> src/entities.rs:29:35
   |
29 | #[derive(Queryable, Identifiable, Associations)]
   |                                   ^^^^^^^^^^^^
   |                                   |
   |                                   first implementation here
   |                                   conflicting implementation for `entities::ProductCategoryRollup`
   |
   = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

Как правильно использовать несколько внешних ключей, ссылающихся на одну и ту же таблицу? Это какое-то внутреннее ограничение Diesel, которое еще не было устранено?


person TheCoolDrop    schedule 30.12.2020    source источник


Ответы (2)


Определение признака BelongsTo:

pub trait BelongsTo<Parent> {
    type ForeignKey: Hash + Eq;
    type ForeignKeyColumn: Column;
    fn foreign_key(&self) -> Option<&Self::ForeignKey>;
    fn foreign_key_column() -> Self::ForeignKeyColumn;
}

В результате того, что ForeignKeyForeignKeyColumn) являются связанными типами, а не универсальными параметрами, данный Child может иметь только одну реализацию BelongsTo<Parent>.


В целом кажется, что BelongsTo довольно ограничен; обратите внимание, что он также ограничен одним столбцом.

person Matthieu M.    schedule 31.12.2020
comment
Для меня это, конечно, ясно, но это не ответ на вопрос, как я могу достичь того, чего хочу достичь. - person TheCoolDrop; 31.12.2020
comment
Ах, извините; Я увлекся исследованием и забыл эту часть вашего вопроса. Если честно, я не уверен, возможно ли это. Разработчики Diesel болтаются на r / rust (Reddit), поэтому я бы посоветовал вам задать свой вопрос там. Если вы это сделаете, сделайте перекрестную ссылку на вопрос о переполнении стека, чтобы, если у кого-то есть обходной путь, они также могли ответить здесь. - person Matthieu M.; 31.12.2020
comment
Я отправил проблему в Github со ссылкой сюда, поэтому нам придется подождать и посмотреть. - person TheCoolDrop; 31.12.2020
comment
@TheCoolDrop: Желаю вам удачи и (возможно, раннего) счастливого Нового года. - person Matthieu M.; 31.12.2020

Итак, я изучал и изучал Diesel, и, как уже отмечалось в ответе выше, эта проблема возникает из-за того, как определена черта BelongsTo<Parent>.

Способ обойти это - сделать что-то вроде следующего:

// This trait contains the behavior common to all types
// representing the product category
trait ProductCategory{
    fn new(id: i32, name: String) -> Self;
}

#[derive(Identifiable)]
#[table_name = "product_category"]
pub struct RawProductCategory {
    id: i32,
    name: String,
}

#[derive(Identifiable)]
#[table_name = "product_category"]
pub struct UpperProductCategory {
    pub id: i32,
    pub name: String,
}

#[derive(Identifiable)]
#[table_name = "product_category"]
pub struct LowerProductCategory {
    pub id: i32,
    pub name: String
}

impl ProductCategory for RawProductCategory {
    fn new(id: i32, name: String) -> Self {
        RawProductCategory {
            id,
            name
        }
    }
}

impl ProductCategory for UpperProductCategory {
    fn new(id: i32, name: String) -> Self {
        UpperProductCategory {
            id,
            name
        }
    }
}

impl ProductCategory for LowerProductCategory {
    fn new(id: i32, name: String) -> Self {
        LowerProductCategory {
            id,
            name
        }
    }
}

impl Queryable<product_category::SqlType, diesel::pg::Pg> for RawProductCategory {
    type Row = (i32, String);
    fn build(row: Self::Row) -> Self {
        ProductCategory::new(row.0, row.1)
    }
}

impl Queryable<product_category::SqlType, diesel::pg::Pg> for UpperProductCategory {
    type Row = (i32, String);
    fn build(row: Self::Row) -> Self {
        ProductCategory::new(row.0, row.1)
    }
}

impl Queryable<product_category::SqlType, diesel::pg::Pg> for LowerProductCategory {
    type Row = (i32, String);
    fn build(row: Self::Row) -> Self {
        ProductCategory::new(row.0, row.1)
    }
}

Теперь я заметил, что у меня довольно большое дублирование кода в отношении реализаций Queryable, но у меня не было желания сокращать его, вводя еще одну структуру, которая содержит одно поле, реализующее черту ProductCategory.

А теперь самое интересное. Я заметил, почему это возникает, и сделал открытую проблему в дизельном репозитории Github. Если эта проблема будет решена, я соответствующим образом обновлю этот ответ, чтобы показать более приятный способ достижения того же.

person TheCoolDrop    schedule 03.01.2021