Support bigdecimal::BigDecimal (#1258)

* Fix: fields with type `Option<T>` are always nullable

* Support BigDecimal
This commit is contained in:
Billy Chan 2022-12-01 12:53:19 +08:00 committed by GitHub
parent e0974a1cf9
commit 08cb44028e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 173 additions and 0 deletions

View File

@ -32,6 +32,7 @@ futures = { version = "^0.3", default-features = false, features = ["std"] }
log = { version = "^0.4", default-features = false }
tracing = { version = "^0.1", default-features = false, features = ["attributes", "log"] }
rust_decimal = { version = "^1", default-features = false, optional = true }
bigdecimal = { version = "^0.3", default-features = false, optional = true }
sea-orm-macros = { version = "^0.10.3", path = "sea-orm-macros", default-features = false, optional = true }
sea-query = { version = "^0.27.2", features = ["thread-safe"] }
sea-query-binder = { version = "^0.2.2", default-features = false, optional = true }
@ -67,6 +68,7 @@ default = [
"with-json",
"with-chrono",
"with-rust_decimal",
"with-bigdecimal",
"with-uuid",
"with-time",
]
@ -75,6 +77,7 @@ mock = []
with-json = ["serde_json", "sea-query/with-json", "chrono?/serde", "time?/serde", "uuid?/serde", "sea-query-binder?/with-json", "sqlx?/json"]
with-chrono = ["chrono", "sea-query/with-chrono", "sea-query-binder?/with-chrono", "sqlx?/chrono"]
with-rust_decimal = ["rust_decimal", "sea-query/with-rust_decimal", "sea-query-binder?/with-rust_decimal", "sqlx?/decimal"]
with-bigdecimal = ["bigdecimal", "sea-query/with-bigdecimal", "sea-query-binder?/with-bigdecimal", "sqlx?/bigdecimal"]
with-uuid = ["uuid", "sea-query/with-uuid", "sea-query-binder?/with-uuid", "sqlx?/uuid"]
with-time = ["time", "sea-query/with-time", "sea-query-binder?/with-time", "sqlx?/time"]
postgres-array = ["sea-query/postgres-array", "sea-query-binder?/postgres-array", "sea-orm-macros?/postgres-array"]

View File

@ -72,5 +72,8 @@ pub use time::OffsetDateTime as TimeDateTimeWithTimeZone;
#[cfg(feature = "with-rust_decimal")]
pub use rust_decimal::Decimal;
#[cfg(feature = "with-bigdecimal")]
pub use bigdecimal::BigDecimal;
#[cfg(feature = "with-uuid")]
pub use uuid::Uuid;

View File

@ -359,6 +359,58 @@ impl TryGetable for Decimal {
}
}
#[cfg(feature = "with-bigdecimal")]
use bigdecimal::BigDecimal;
#[cfg(feature = "with-bigdecimal")]
impl TryGetable for BigDecimal {
#[allow(unused_variables)]
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
let column = format!("{}{}", pre, col);
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
use sqlx::Row;
row.try_get::<Option<BigDecimal>, _>(column.as_str())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
row.try_get::<Option<BigDecimal>, _>(column.as_str())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)))
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row;
let val: Option<f64> = row
.try_get(column.as_str())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))?;
match val {
Some(v) => BigDecimal::try_from(v).map_err(|e| {
TryGetError::DbErr(DbErr::TryIntoErr {
from: "f64",
into: "BigDecimal",
source: Box::new(e),
})
}),
None => Err(TryGetError::Null(column)),
}
}
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| {
debug_print!("{:#?}", e.to_string());
TryGetError::Null(column)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
#[cfg(feature = "with-uuid")]
try_getable_all!(uuid::Uuid);
@ -489,6 +541,9 @@ mod postgres_array {
#[cfg(feature = "with-rust_decimal")]
try_getable_postgres_array!(rust_decimal::Decimal);
#[cfg(feature = "with-bigdecimal")]
try_getable_postgres_array!(bigdecimal::BigDecimal);
#[cfg(feature = "with-uuid")]
try_getable_postgres_array!(uuid::Uuid);

View File

@ -8,6 +8,7 @@ pub mod insert_default;
pub mod json_struct;
pub mod json_vec;
pub mod metadata;
pub mod pi;
pub mod repository;
pub mod satellite;
pub mod schema;
@ -24,6 +25,7 @@ pub use insert_default::Entity as InsertDefault;
pub use json_struct::Entity as JsonStruct;
pub use json_vec::Entity as JsonVec;
pub use metadata::Entity as Metadata;
pub use pi::Entity as Pi;
pub use repository::Entity as Repository;
pub use satellite::Entity as Satellite;
pub use schema::*;

View File

@ -0,0 +1,21 @@
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "pi")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(column_type = "Decimal(Some((11, 10)))")]
pub decimal: Decimal,
#[sea_orm(column_type = "Decimal(Some((11, 10)))")]
pub big_decimal: BigDecimal,
#[sea_orm(column_type = "Decimal(Some((11, 10)))")]
pub decimal_opt: Option<Decimal>,
#[sea_orm(column_type = "Decimal(Some((11, 10)))")]
pub big_decimal_opt: Option<BigDecimal>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -41,6 +41,7 @@ pub async fn create_tables(db: &DatabaseConnection) -> Result<(), DbErr> {
create_active_enum_table(db).await?;
create_active_enum_child_table(db).await?;
create_insert_default_table(db).await?;
create_pi_table(db).await?;
if DbBackend::Postgres == db_backend {
create_collection_table(db).await?;
@ -383,3 +384,30 @@ pub async fn create_collection_table(db: &DbConn) -> Result<ExecResult, DbErr> {
create_table(db, &stmt, Collection).await
}
pub async fn create_pi_table(db: &DbConn) -> Result<ExecResult, DbErr> {
let stmt = sea_query::Table::create()
.table(pi::Entity)
.col(
ColumnDef::new(pi::Column::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(
ColumnDef::new(pi::Column::Decimal)
.decimal_len(11, 10)
.not_null(),
)
.col(
ColumnDef::new(pi::Column::BigDecimal)
.decimal_len(11, 10)
.not_null(),
)
.col(ColumnDef::new(pi::Column::DecimalOpt).decimal_len(11, 10))
.col(ColumnDef::new(pi::Column::BigDecimalOpt).decimal_len(11, 10))
.to_owned();
create_table(db, &stmt, Pi).await
}

61
tests/pi_tests.rs Normal file
View File

@ -0,0 +1,61 @@
pub mod common;
use common::{features::*, TestContext};
use pretty_assertions::assert_eq;
use rust_decimal_macros::dec;
use sea_orm::{entity::prelude::*, entity::*, DatabaseConnection};
use std::str::FromStr;
#[sea_orm_macros::test]
#[cfg(any(
feature = "sqlx-mysql",
feature = "sqlx-sqlite",
feature = "sqlx-postgres"
))]
async fn main() -> Result<(), DbErr> {
let ctx = TestContext::new("pi_tests").await;
create_tables(&ctx.db).await?;
create_and_update_pi(&ctx.db).await?;
ctx.delete().await;
Ok(())
}
pub async fn create_and_update_pi(db: &DatabaseConnection) -> Result<(), DbErr> {
let pi = pi::Model {
id: 1,
decimal: dec!(3.1415926536),
big_decimal: BigDecimal::from_str("3.1415926536").unwrap(),
decimal_opt: None,
big_decimal_opt: None,
};
let res = pi.clone().into_active_model().insert(db).await?;
let model = Pi::find().one(db).await?;
assert_eq!(model, Some(res));
assert_eq!(model, Some(pi.clone()));
let res = pi::ActiveModel {
decimal_opt: Set(Some(dec!(3.1415926536))),
big_decimal_opt: Set(Some(BigDecimal::from_str("3.1415926536").unwrap())),
..pi.clone().into_active_model()
}
.update(db)
.await?;
let model = Pi::find().one(db).await?;
assert_eq!(model, Some(res));
assert_eq!(
model,
Some(pi::Model {
id: 1,
decimal: dec!(3.1415926536),
big_decimal: BigDecimal::from_str("3.1415926536").unwrap(),
decimal_opt: Some(dec!(3.1415926536)),
big_decimal_opt: Some(BigDecimal::from_str("3.1415926536").unwrap()),
})
);
Ok(())
}