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,
}
}
_
ディーゼルは小数を_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
関連タイプとは何ですか?ディーゼルのドキュメントはこれについてあまり明確ではありません。
たぶん私がやろうとしていることを達成するためのより良い方法がありますか?
_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"
_
ToSql
とFromSql
を実装するnewtypeラッパーを作成する方が便利だと思います。次に、これらの基本ブロックを使用してビルドし、Queryable
/Insertable
を導出できるより大きなタイプを作成できます。
この例は、SmallInt
との間で列挙型のマッピングを実行する方法のみを示していますが、10進数の場合も同じです。唯一の違いは、変換の実行方法にあります。
#[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は、データベース内に必要な基盤となるストレージの種類を知ることができます。
カスタムタイプのディーゼルテスト も参照してください。
マクロが何をするのかを理解する最も簡単な方法(派生はマクロの単なる異なる形式です)は、コンパイラーに拡張コードを要求することである場合があります。夜間コンパイラでは、次のコマンドを使用してこれを行うことができます。
_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
に関連付けられた型を変更して、値への参照ではなく直接値を返すようにしたことに注意してください。2つについては、値がvalues
メソッドで作成されるため、できませんでした。 tは参照を返しますが、もう1つは、参照を返すことはパフォーマンスの点であまり向上しません。
_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))))
}
}
_