23

我有一个我想通过 Diesel 使用的 SQL 表:

CREATE TABLE records (
    id BIGSERIAL PRIMARY KEY,
    record_type SMALLINT NOT NULL,
    value DECIMAL(10, 10) NOT NULL
)

此表生成以下架构:

table! {
    records (id) {
        id -> Int8,
        record_type -> Int2,
        value -> Numeric,
    }
}

Diesel 将小数导出为bigdecimal::BigDecimal,但我想decimal::d128改为使用。我也想映射record_type到一个枚举,所以我这样声明我的模型:

use decimal::d128;

pub enum RecordType {
    A,
    B,
}

pub struct Record {
    pub id: i64,
    pub record_type: RecordType,
    pub value: d128,
}

#derive(Queryable, Insertable)由于非标准类型映射,我无法使用,所以我尝试自己实现这些特征:

impl Queryable<records::SqlType, Pg> for Record {
    type Row = (i64, i16, BigDecimal);

    fn build(row: Self::Row) -> Self {
        Record {
            id: row.0,
            record_type: match row.1 {
                1 => RecordType::A,
                2 => RecordType::B,
                _ => panic!("Wrong record type"),
            },
            value: d128!(format!("{}", row.2)),
        }
    }
}

我无法弄清楚如何实施Insertable. Values关联类型是什么?Diesel 的文档对此并不十分清楚。

也许有更好的方法来实现我想要做的事情?

Cargo.toml

[dependencies]
bigdecimal = "0.0.10"
decimal = "2.0.4"
diesel = { version = "1.1.1", features = ["postgres", "bigdecimal", "num-bigint", "num-integer", "num-traits"] }
dotenv = "0.9.0"
4

2 回答 2

23

我发现创建实现ToSqlFromSql. 然后,您可以使用这些基本块进行构建,以创建可以派生Queryable/的更大类型Insertable

此示例仅显示如何执行枚举与 a 的映射SmallInt,但小数的情况相同。唯一的区别在于您如何执行转换:

#[macro_use]
extern crate diesel;

mod types {
    use diesel::sql_types::*;
    use diesel::backend::Backend;
    use diesel::deserialize::{self, FromSql};
    use diesel::serialize::{self, ToSql, Output};
    use std::io;

    table! {
        records (id) {
            id -> BigInt,
            record_type -> SmallInt,
        }
    }

    #[derive(Debug, Copy, Clone, AsExpression, FromSqlRow)]
    #[sql_type = "SmallInt"]
    pub enum RecordType {
        A,
        B,
    }

    impl<DB: Backend> ToSql<SmallInt, DB> for RecordType
    where
        i16: ToSql<SmallInt, DB>,
    {
        fn to_sql<W>(&self, out: &mut Output<W, DB>) -> serialize::Result
        where
            W: io::Write,
        {
            let v = match *self {
                RecordType::A => 1,
                RecordType::B => 2,
            };
            v.to_sql(out)
        }
    }

    impl<DB: Backend> FromSql<SmallInt, DB> for RecordType
    where
        i16: FromSql<SmallInt, DB>,
    {
        fn from_sql(bytes: Option<&DB::RawValue>) -> deserialize::Result<Self> {
            let v = i16::from_sql(bytes)?;
            Ok(match v {
                1 => RecordType::A,
                2 => RecordType::B,
                _ => return Err("replace me with a real error".into()),
            })
        }
    }

    #[derive(Insertable, Queryable, Debug)]
    #[table_name = "records"]
    pub struct Record {
        pub id: i64,
        pub record_type: RecordType,
    }
}

有一个草案指南描述了所有派生及其注释,但它还没有提到#[sql_type]整个类型。这让 Diesel 知道数据库内部需要什么样的底层存储。

另请参阅自定义类型的 Diesel 测试

于 2018-03-04T15:38:46.860 回答
9

有时,了解宏的作用(派生只是宏的不同形式)的最简单方法是向编译器询问扩展代码。使用夜间编译器,您可以使用以下命令执行此操作:

cargo rustc -- -Z unstable-options --pretty expanded > expanded.rs

这将输出扩展的代码expanded.rs

我们现在可以查看此文件以查看#[derive(Insertable)]扩展内容。自然,我首先更改了 的定义Record以匹配 Diesel 的类型。经过一番清理,这是生成的代码:

impl<'insert> diesel::insertable::Insertable<records::table> for &'insert Record {
    type Values = <(
        Option<diesel::dsl::Eq<records::id, &'insert i64>>,
        Option<diesel::dsl::Eq<records::record_type, &'insert i16>>,
        Option<diesel::dsl::Eq<records::value, &'insert BigDecimal>>
    ) as diesel::insertable::Insertable<records::table>>::Values;

    #[allow(non_shorthand_field_patterns)]
    fn values(self) -> Self::Values {
        let Record {
            id: ref id,
            record_type: ref record_type,
            value: ref value,
        } = *self;
        diesel::insertable::Insertable::values((
            Some(::ExpressionMethods::eq(records::id, id)),
            Some(::ExpressionMethods::eq(records::record_type, record_type)),
            Some(::ExpressionMethods::eq(records::value, value))))
    }
}

impl diesel::query_builder::UndecoratedInsertRecord<records::table> for Record {
}

我们现在可以Insertable为我们的自定义类型调整实现。请注意,我已将Values关联类型更改为直接返回值而不是对值的引用,因为其中两个的值是在values方法中创建的,因此我们无法返回引用,而另一个则返回参考在性能方面并没有太大的收获。

impl<'insert> diesel::insertable::Insertable<records::table> for &'insert Record {
    type Values = <(
        Option<diesel::dsl::Eq<records::id, i64>>,
        Option<diesel::dsl::Eq<records::record_type, i16>>,
        Option<diesel::dsl::Eq<records::value, BigDecimal>>
    ) as diesel::insertable::Insertable<records::table>>::Values;

    #[allow(non_shorthand_field_patterns)]
    fn values(self) -> Self::Values {
        let Record {
            id: ref id,
            record_type: ref record_type,
            value: ref value,
        } = *self;
        let record_type = match *record_type {
            RecordType::A => 1,
            RecordType::B => 2,
        };
        let value: BigDecimal = value.to_string().parse().unwrap();
        diesel::insertable::Insertable::values((
            Some(::ExpressionMethods::eq(records::id, *id)),
            Some(::ExpressionMethods::eq(records::record_type, record_type)),
            Some(::ExpressionMethods::eq(records::value, value))))
    }
}
于 2018-03-04T14:27:43.090 回答