Merge branch 'master' into primary-key-type

This commit is contained in:
Billy Chan 2021-09-01 20:13:25 +08:00 committed by GitHub
commit ab7e7f3401
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 244 additions and 223 deletions

View File

@ -11,12 +11,11 @@ env:
jobs:
compile:
name: Compile
compile-sqlite:
name: Compile SQLite
runs-on: ubuntu-20.04
strategy:
matrix:
database: [sqlite, mysql, postgres]
runtime: [async-std, actix, tokio]
tls: [native-tls, rustls]
steps:
@ -35,13 +34,77 @@ jobs:
~/.cargo/git
Cargo.lock
target
key: ${{ github.sha }}-${{ github.run_id }}-${{ runner.os }}-${{ matrix.database }}-${{ matrix.runtime }}-${{ matrix.tls }}
key: ${{ github.sha }}-${{ github.run_id }}-${{ runner.os }}-sqlite-${{ matrix.runtime }}-${{ matrix.tls }}
- uses: actions-rs/cargo@v1
with:
command: test
args: >
--features default,sqlx-${{ matrix.database }},runtime-${{ matrix.runtime }}-${{ matrix.tls }}
--features default,sqlx-sqlite,runtime-${{ matrix.runtime }}-${{ matrix.tls }}
--no-run
compile-mysql:
name: Compile MySQL
runs-on: ubuntu-20.04
strategy:
matrix:
runtime: [async-std, actix, tokio]
tls: [native-tls, rustls]
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: actions/cache@v2
with:
path: |
~/.cargo/registry
~/.cargo/git
Cargo.lock
target
key: ${{ github.sha }}-${{ github.run_id }}-${{ runner.os }}-mysql-${{ matrix.runtime }}-${{ matrix.tls }}
- uses: actions-rs/cargo@v1
with:
command: test
args: >
--features default,sqlx-mysql,runtime-${{ matrix.runtime }}-${{ matrix.tls }}
--no-run
compile-postgres:
name: Compile PostgreSQL
runs-on: ubuntu-20.04
strategy:
matrix:
runtime: [async-std, actix, tokio]
tls: [native-tls, rustls]
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: actions/cache@v2
with:
path: |
~/.cargo/registry
~/.cargo/git
Cargo.lock
target
key: ${{ github.sha }}-${{ github.run_id }}-${{ runner.os }}-postgres-${{ matrix.runtime }}-${{ matrix.tls }}
- uses: actions-rs/cargo@v1
with:
command: test
args: >
--features default,sqlx-postgres,runtime-${{ matrix.runtime }}-${{ matrix.tls }}
--no-run
test:
@ -89,6 +152,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
path: [async-std, tokio]
steps:
- uses: actions/checkout@v2
@ -102,18 +166,12 @@ jobs:
with:
command: build
args: >
--manifest-path examples/async-std/Cargo.toml
- uses: actions-rs/cargo@v1
with:
command: build
args: >
--manifest-path examples/tokio/Cargo.toml
--manifest-path examples/${{ matrix.path }}/Cargo.toml
sqlite:
name: SQLite
runs-on: ubuntu-20.04
needs: compile
needs: compile-sqlite
env:
DATABASE_URL: "sqlite::memory:"
strategy:
@ -147,7 +205,7 @@ jobs:
mysql:
name: MySQL
runs-on: ubuntu-20.04
needs: compile
needs: compile-mysql
env:
DATABASE_URL: "mysql://root:@localhost"
strategy:
@ -199,7 +257,7 @@ jobs:
mariadb:
name: MariaDB
runs-on: ubuntu-20.04
needs: compile
needs: compile-mysql
env:
DATABASE_URL: "mysql://root:@localhost"
strategy:
@ -251,7 +309,7 @@ jobs:
postgres:
name: Postgres
runs-on: ubuntu-20.04
needs: compile
needs: compile-postgres
env:
DATABASE_URL: "postgres://root:root@localhost"
strategy:

39
ARCHITECTURE.md Normal file
View File

@ -0,0 +1,39 @@
# Architecture
To understand the architecture of SeaORM, let's discuss what is an ORM. ORM exists to provide abstractions over common operations you would do against a database and hide the implementation details like the actual SQL queries.
With a good ORM, you shouldn't bother to look under the API surface. Until you do. I hear you say *'abstraction leaks'*, and yes, it does.
The approach SeaORM takes is **'layered abstraction'**, where you'd dig one layer beneath if you want to. That's why we made SeaQuery into a standalone repository. It's useful on its own, and with a public API surface and a separate namespace, it's far more difficult to create confusing internal APIs than a monolithic approach.
The central idea in SeaORM is nearly everything is runtime configurable. At compile time, it does not know what the underlying database is.
What benefits does database-agnostic bring? For example, you can:
1. Make your app work on any database, depending on runtime configuration
1. Use the same models and transfer them across different databases
1. Share entities across different projects by creating a 'data structure crate', where the database is chosen by downstream 'behaviour crates'
The API of SeaORM is not a thin shell, but consist of layers, with each layer underneath being less abstract.
There are different stages when the API is being utilized.
So there are two dimensions to navigate the SeaORM code base, **'stage'** and **'abstractness'**.
First is the declaration stage. Entities and relations among them are being defined with the `EntityTrait`, `ColumnTrait` & `RelationTrait` etc.
Second is the query building stage.
The top most layer is `Entity`'s `find*`, `insert`, `update` & `delete` methods, where you can intuitively perform basic CRUD operations.
One layer down, is the `Select`, `Insert`, `Update` & `Delete` structs, where they each have their own API for more advanced operations.
One layer down, is the SeaQuery `SelectStatement`, `InsertStatement`, `UpdateStatement` & `DeleteStatement`, where they have a rich API for you to fiddle with the SQL syntax tree.
Third is the execution stage. A separate set of structs, `Selector`, `Inserter`, `Updater` & `Deleter`, are responsible for executing the statements against a database connection.
Finally is the resolution stage, when query results are converted into Rust structs for consumption.
Because only the execution and resolution stages are database specific, we have the possibility to use a different driver by replacing those.
I imagine some day, we will support a number of databases, with a matrix of different syntaxes, protocols and form-factors.

View File

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## 0.1.3 - 2021-08-30
- [[#108]] Remove impl TryGetable for Option<T>
[#108]: https://github.com/SeaQL/sea-orm/issues/108
## 0.1.2 - 2021-08-23
- [[#68]] Added `DateTimeWithTimeZone` as supported attribute type

View File

@ -7,7 +7,7 @@ members = [
[package]
name = "sea-orm"
version = "0.1.2"
version = "0.1.3"
authors = ["Chris Tsang <tyt2y7@gmail.com>"]
edition = "2018"
description = "🐚 An async & dynamic ORM for Rust"

View File

@ -1,4 +1,4 @@
# Design Goals
# Design
We are heavily inspired by ActiveRecord, Eloquent and TypeORM.
@ -20,7 +20,7 @@ After some bitterness we realized it is not possible to capture everything compi
want to encounter problems at run time either. The solution is to perform checking at 'test time' to
uncover problems. These checks will be removed at production so there will be no run time penalty.
## Readability
## API style
### Turbofish and inference
@ -65,4 +65,35 @@ has_many(cake::Entity, cake::Column::Id, fruit::Column::CakeId)
we'd prefer having a builder and stating the params explicitly:
```rust
has_many(cake::Entity).from(cake::Column::Id).to(fruit::Column::CakeId)
```
### Method overloading
Consider the following two methods, which accept the same parameter but in different forms:
```rust
fn method_with_model(m: Model) { ... }
fn method_with_active_model(a: ActiveModel) { ... }
```
We would define a trait
```rust
pub trait IntoActiveModel {
fn into_active_model(self) -> ActiveModel;
}
```
Such that `Model` and `ActiveModel` both impl this trait.
In this way, we can overload the two methods:
```rust
pub fn method<A>(a: A)
where
A: IntoActiveModel,
{
let a: ActiveModel = a.into_active_model();
...
}
```

View File

@ -25,7 +25,7 @@ This is an early release of SeaORM, the API is not stable yet.
```
[![Getting Started](https://img.shields.io/badge/Getting%20Started-blue)](https://www.sea-ql.org/SeaORM/docs/index)
[![Examples](https://img.shields.io/badge/Examples-orange)](https://github.com/SeaQL/sea-orm/tree/master/examples/sqlx)
[![Examples](https://img.shields.io/badge/Examples-orange)](https://github.com/SeaQL/sea-orm/tree/master/examples)
[![Starter Kit](https://img.shields.io/badge/Starter%20Kit-green)](https://github.com/SeaQL/sea-orm/issues/37)
[![Discord](https://img.shields.io/discord/873880840487206962?label=Discord)](https://discord.com/invite/uCPdDXzbdv)
@ -108,7 +108,7 @@ let pear: fruit::ActiveModel = Fruit::update(pear).exec(db).await?;
// update many: UPDATE "fruit" SET "cake_id" = NULL WHERE "fruit"."name" LIKE '%Apple%'
Fruit::update_many()
.col_expr(fruit::Column::CakeId, Expr::value(Value::Null))
.col_expr(fruit::Column::CakeId, Expr::value(Value::Int(None)))
.filter(fruit::Column::Name.contains("Apple"))
.exec(db)
.await?;
@ -148,6 +148,12 @@ fruit::Entity::delete_many()
.await?;
```
## Learn More
1. [Design](https://github.com/SeaQL/sea-orm/tree/master/DESIGN.md)
1. [Architecture](https://github.com/SeaQL/sea-orm/tree/master/ARCHITECTURE.md)
## License
Licensed under either of

View File

@ -21,6 +21,20 @@ pub trait TryGetable: Sized {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, DbErr>;
}
pub enum TryGetError {
DbErr(DbErr),
Null,
}
impl From<TryGetError> for DbErr {
fn from(e: TryGetError) -> DbErr {
match e {
TryGetError::DbErr(e) => e,
TryGetError::Null => DbErr::Query("error occurred while decoding: Null".to_owned()),
}
}
}
// QueryResult //
impl QueryResult {
@ -28,7 +42,7 @@ impl QueryResult {
where
T: TryGetable,
{
T::try_get(self, pre, col)
Ok(T::try_get(self, pre, col)?)
}
}
@ -49,66 +63,48 @@ impl fmt::Debug for QueryResultRow {
// TryGetable //
impl<T: TryGetable> TryGetable for Option<T> {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
match T::try_get(res, pre, col) {
Ok(v) => Ok(Some(v)),
Err(TryGetError::Null) => Ok(None),
Err(e) => Err(e),
}
}
}
macro_rules! try_getable_all {
( $type: ty ) => {
impl TryGetable for $type {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, DbErr> {
let column = format!("{}{}", pre, col);
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
use sqlx::Row;
row.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
row.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row;
row.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
}
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => Ok(row.try_get(column.as_str())?),
}
}
}
impl TryGetable for Option<$type> {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, DbErr> {
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<$type>, _>(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
row.try_get::<Option<$type>, _>(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null))
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row;
row.try_get::<Option<$type>, _>(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null))
}
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => match row.try_get(column.as_str()) {
Ok(v) => Ok(Some(v)),
Err(e) => {
debug_print!("{:#?}", e.to_string());
Ok(None)
}
},
QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| {
debug_print!("{:#?}", e.to_string());
TryGetError::Null
}),
}
}
}
@ -118,40 +114,15 @@ macro_rules! try_getable_all {
macro_rules! try_getable_unsigned {
( $type: ty ) => {
impl TryGetable for $type {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, DbErr> {
let column = format!("{}{}", pre, col);
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
use sqlx::Row;
row.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(_) => {
panic!("{} unsupported by sqlx-postgres", stringify!($type))
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row;
row.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
}
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => Ok(row.try_get(column.as_str())?),
}
}
}
impl TryGetable for Option<$type> {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, DbErr> {
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<$type>, _>(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(_) => {
@ -161,16 +132,14 @@ macro_rules! try_getable_unsigned {
QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row;
row.try_get::<Option<$type>, _>(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null))
}
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => match row.try_get(column.as_str()) {
Ok(v) => Ok(Some(v)),
Err(e) => {
debug_print!("{:#?}", e.to_string());
Ok(None)
}
},
QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| {
debug_print!("{:#?}", e.to_string());
TryGetError::Null
}),
}
}
}
@ -180,38 +149,15 @@ macro_rules! try_getable_unsigned {
macro_rules! try_getable_mysql {
( $type: ty ) => {
impl TryGetable for $type {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, DbErr> {
let column = format!("{}{}", pre, col);
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
use sqlx::Row;
row.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(_) => {
panic!("{} unsupported by sqlx-postgres", stringify!($type))
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(_) => {
panic!("{} unsupported by sqlx-sqlite", stringify!($type))
}
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => Ok(row.try_get(column.as_str())?),
}
}
}
impl TryGetable for Option<$type> {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, DbErr> {
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<$type>, _>(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(_) => {
@ -222,13 +168,10 @@ macro_rules! try_getable_mysql {
panic!("{} unsupported by sqlx-sqlite", stringify!($type))
}
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => match row.try_get(column.as_str()) {
Ok(v) => Ok(Some(v)),
Err(e) => {
debug_print!("{:#?}", e.to_string());
Ok(None)
}
},
QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| {
debug_print!("{:#?}", e.to_string());
TryGetError::Null
}),
}
}
}
@ -238,31 +181,7 @@ macro_rules! try_getable_mysql {
macro_rules! try_getable_postgres {
( $type: ty ) => {
impl TryGetable for $type {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, DbErr> {
let column = format!("{}{}", pre, col);
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(_) => {
panic!("{} unsupported by sqlx-mysql", stringify!($type))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
row.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(_) => {
panic!("{} unsupported by sqlx-sqlite", stringify!($type))
}
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => Ok(row.try_get(column.as_str())?),
}
}
}
impl TryGetable for Option<$type> {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, DbErr> {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
let column = format!("{}{}", pre, col);
match &res.row {
#[cfg(feature = "sqlx-mysql")]
@ -273,20 +192,18 @@ macro_rules! try_getable_postgres {
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
row.try_get::<Option<$type>, _>(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null))
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(_) => {
panic!("{} unsupported by sqlx-sqlite", stringify!($type))
}
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => match row.try_get(column.as_str()) {
Ok(v) => Ok(Some(v)),
Err(e) => {
debug_print!("{:#?}", e.to_string());
Ok(None)
}
},
QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| {
debug_print!("{:#?}", e.to_string());
TryGetError::Null
}),
}
}
}
@ -320,83 +237,41 @@ use rust_decimal::Decimal;
#[cfg(feature = "with-rust_decimal")]
impl TryGetable for Decimal {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, DbErr> {
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(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
row.try_get::<Option<Decimal>, _>(column.as_str())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
row.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
row.try_get::<Option<Decimal>, _>(column.as_str())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null))
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row;
let val: f64 = row
let val: Option<f64> = row
.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err)?;
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))?;
use rust_decimal::prelude::FromPrimitive;
Decimal::from_f64(val)
.ok_or_else(|| DbErr::Query("Failed to convert f64 into Decimal".to_owned()))
}
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => Ok(row.try_get(column.as_str())?),
}
}
}
#[cfg(feature = "with-rust_decimal")]
impl TryGetable for Option<Decimal> {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, DbErr> {
let column = format!("{}{}", pre, col);
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
use sqlx::Row;
match row.try_get(column.as_str()) {
Ok(v) => Ok(Some(v)),
Err(e) => {
debug_print!("{:#?}", e.to_string());
Ok(None)
}
}
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
match row.try_get(column.as_str()) {
Ok(v) => Ok(Some(v)),
Err(e) => {
debug_print!("{:#?}", e.to_string());
Ok(None)
}
}
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(_) => {
let result: Result<Decimal, _> = TryGetable::try_get(res, pre, col);
match result {
Ok(v) => Ok(Some(v)),
Err(e) => {
debug_print!("{:#?}", e.to_string());
Ok(None)
}
match val {
Some(v) => Decimal::from_f64(v)
.ok_or_else(|| TryGetError::DbErr(DbErr::Query("Failed to convert f64 into Decimal".to_owned()))),
None => Err(TryGetError::Null)
}
}
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => match row.try_get(column.as_str()) {
Ok(v) => Ok(Some(v)),
Err(e) => {
debug_print!("{:#?}", e.to_string());
Ok(None)
}
},
QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| {
debug_print!("{:#?}", e.to_string());
TryGetError::Null
}),
}
}
}

View File

@ -25,7 +25,7 @@
//! ```
//!
//! [![Getting Started](https://img.shields.io/badge/Getting%20Started-blue)](https://www.sea-ql.org/SeaORM/docs/index)
//! [![Examples](https://img.shields.io/badge/Examples-orange)](https://github.com/SeaQL/sea-orm/tree/master/examples/sqlx)
//! [![Examples](https://img.shields.io/badge/Examples-orange)](https://github.com/SeaQL/sea-orm/tree/master/examples)
//! [![Starter Kit](https://img.shields.io/badge/Starter%20Kit-green)](https://github.com/SeaQL/sea-orm/issues/37)
//! [![Discord](https://img.shields.io/discord/873880840487206962?label=Discord)](https://discord.com/invite/uCPdDXzbdv)
//!
@ -180,6 +180,12 @@
//! # Ok(())
//! # }
//! ```
//!
//! ## Learn More
//!
//! 1. [Design](https://github.com/SeaQL/sea-orm/tree/master/DESIGN.md)
//! 1. [Architecture](https://github.com/SeaQL/sea-orm/tree/master/ARCHITECTURE.md)
//!
//! ## License
//!
//! Licensed under either of