Merge branch 'master' into linked-api

This commit is contained in:
Billy Chan 2021-09-02 15:26:37 +08:00
commit 3cf8866620
No known key found for this signature in database
GPG Key ID: A2D690CAC7DF3CC7
8 changed files with 245 additions and 224 deletions

View File

@ -11,12 +11,11 @@ env:
jobs: jobs:
compile: compile-sqlite:
name: Compile name: Compile SQLite
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
strategy: strategy:
matrix: matrix:
database: [sqlite, mysql, postgres]
runtime: [async-std, actix, tokio] runtime: [async-std, actix, tokio]
tls: [native-tls, rustls] tls: [native-tls, rustls]
steps: steps:
@ -35,13 +34,77 @@ jobs:
~/.cargo/git ~/.cargo/git
Cargo.lock Cargo.lock
target 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 - uses: actions-rs/cargo@v1
with: with:
command: test command: test
args: > 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 --no-run
test: test:
@ -89,6 +152,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest] os: [ubuntu-latest]
path: [async-std, tokio]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -102,18 +166,12 @@ jobs:
with: with:
command: build command: build
args: > args: >
--manifest-path examples/async-std/Cargo.toml --manifest-path examples/${{ matrix.path }}/Cargo.toml
- uses: actions-rs/cargo@v1
with:
command: build
args: >
--manifest-path examples/tokio/Cargo.toml
sqlite: sqlite:
name: SQLite name: SQLite
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
needs: compile needs: compile-sqlite
env: env:
DATABASE_URL: "sqlite::memory:" DATABASE_URL: "sqlite::memory:"
strategy: strategy:
@ -147,7 +205,7 @@ jobs:
mysql: mysql:
name: MySQL name: MySQL
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
needs: compile needs: compile-mysql
env: env:
DATABASE_URL: "mysql://root:@localhost" DATABASE_URL: "mysql://root:@localhost"
strategy: strategy:
@ -199,7 +257,7 @@ jobs:
mariadb: mariadb:
name: MariaDB name: MariaDB
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
needs: compile needs: compile-mysql
env: env:
DATABASE_URL: "mysql://root:@localhost" DATABASE_URL: "mysql://root:@localhost"
strategy: strategy:
@ -251,7 +309,7 @@ jobs:
postgres: postgres:
name: Postgres name: Postgres
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
needs: compile needs: compile-postgres
env: env:
DATABASE_URL: "postgres://root:root@localhost" DATABASE_URL: "postgres://root:root@localhost"
strategy: 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/) The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/). 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 ## 0.1.2 - 2021-08-23
- [[#68]] Added `DateTimeWithTimeZone` as supported attribute type - [[#68]] Added `DateTimeWithTimeZone` as supported attribute type

View File

@ -7,7 +7,7 @@ members = [
[package] [package]
name = "sea-orm" name = "sea-orm"
version = "0.1.2" version = "0.1.3"
authors = ["Chris Tsang <tyt2y7@gmail.com>"] authors = ["Chris Tsang <tyt2y7@gmail.com>"]
edition = "2018" edition = "2018"
description = "🐚 An async & dynamic ORM for Rust" 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. 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 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. uncover problems. These checks will be removed at production so there will be no run time penalty.
## Readability ## API style
### Turbofish and inference ### 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: we'd prefer having a builder and stating the params explicitly:
```rust ```rust
has_many(cake::Entity).from(cake::Column::Id).to(fruit::Column::CakeId) 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) [![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) [![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) [![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%' // update many: UPDATE "fruit" SET "cake_id" = NULL WHERE "fruit"."name" LIKE '%Apple%'
Fruit::update_many() 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")) .filter(fruit::Column::Name.contains("Apple"))
.exec(db) .exec(db)
.await?; .await?;
@ -148,6 +148,12 @@ fruit::Entity::delete_many()
.await?; .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 ## License
Licensed under either of Licensed under either of

View File

@ -17,8 +17,22 @@ pub(crate) enum QueryResultRow {
Mock(crate::MockRow), Mock(crate::MockRow),
} }
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()),
}
}
}
pub trait TryGetable { pub trait TryGetable {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, DbErr> fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError>
where where
Self: Sized; Self: Sized;
} }
@ -30,7 +44,7 @@ impl QueryResult {
where where
T: TryGetable, T: TryGetable,
{ {
T::try_get(self, pre, col) Ok(T::try_get(self, pre, col)?)
} }
} }
@ -51,66 +65,48 @@ impl fmt::Debug for QueryResultRow {
// TryGetable // // 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 { macro_rules! try_getable_all {
( $type: ty ) => { ( $type: ty ) => {
impl TryGetable for $type { impl TryGetable for $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(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> {
let column = format!("{}{}", pre, col); let column = format!("{}{}", pre, col);
match &res.row { match &res.row {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => { QueryResultRow::SqlxMySql(row) => {
use sqlx::Row; use sqlx::Row;
row.try_get::<Option<$type>, _>(column.as_str()) 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")] #[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => { QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row; use sqlx::Row;
row.try_get::<Option<$type>, _>(column.as_str()) 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")] #[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => { QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row; use sqlx::Row;
row.try_get::<Option<$type>, _>(column.as_str()) 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")] #[cfg(feature = "mock")]
QueryResultRow::Mock(row) => match row.try_get(column.as_str()) { QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| {
Ok(v) => Ok(Some(v)), debug_print!("{:#?}", e.to_string());
Err(e) => { TryGetError::Null
debug_print!("{:#?}", e.to_string()); }),
Ok(None)
}
},
} }
} }
} }
@ -120,40 +116,15 @@ macro_rules! try_getable_all {
macro_rules! try_getable_unsigned { macro_rules! try_getable_unsigned {
( $type: ty ) => { ( $type: ty ) => {
impl TryGetable for $type { impl TryGetable for $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(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> {
let column = format!("{}{}", pre, col); let column = format!("{}{}", pre, col);
match &res.row { match &res.row {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => { QueryResultRow::SqlxMySql(row) => {
use sqlx::Row; use sqlx::Row;
row.try_get::<Option<$type>, _>(column.as_str()) 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")] #[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(_) => { QueryResultRow::SqlxPostgres(_) => {
@ -163,16 +134,14 @@ macro_rules! try_getable_unsigned {
QueryResultRow::SqlxSqlite(row) => { QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row; use sqlx::Row;
row.try_get::<Option<$type>, _>(column.as_str()) 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")] #[cfg(feature = "mock")]
QueryResultRow::Mock(row) => match row.try_get(column.as_str()) { QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| {
Ok(v) => Ok(Some(v)), debug_print!("{:#?}", e.to_string());
Err(e) => { TryGetError::Null
debug_print!("{:#?}", e.to_string()); }),
Ok(None)
}
},
} }
} }
} }
@ -182,38 +151,15 @@ macro_rules! try_getable_unsigned {
macro_rules! try_getable_mysql { macro_rules! try_getable_mysql {
( $type: ty ) => { ( $type: ty ) => {
impl TryGetable for $type { impl TryGetable for $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(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> {
let column = format!("{}{}", pre, col); let column = format!("{}{}", pre, col);
match &res.row { match &res.row {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => { QueryResultRow::SqlxMySql(row) => {
use sqlx::Row; use sqlx::Row;
row.try_get::<Option<$type>, _>(column.as_str()) 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")] #[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(_) => { QueryResultRow::SqlxPostgres(_) => {
@ -224,13 +170,10 @@ macro_rules! try_getable_mysql {
panic!("{} unsupported by sqlx-sqlite", stringify!($type)) panic!("{} unsupported by sqlx-sqlite", stringify!($type))
} }
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
QueryResultRow::Mock(row) => match row.try_get(column.as_str()) { QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| {
Ok(v) => Ok(Some(v)), debug_print!("{:#?}", e.to_string());
Err(e) => { TryGetError::Null
debug_print!("{:#?}", e.to_string()); }),
Ok(None)
}
},
} }
} }
} }
@ -240,31 +183,7 @@ macro_rules! try_getable_mysql {
macro_rules! try_getable_postgres { macro_rules! try_getable_postgres {
( $type: ty ) => { ( $type: ty ) => {
impl TryGetable for $type { impl TryGetable for $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(_) => {
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> {
let column = format!("{}{}", pre, col); let column = format!("{}{}", pre, col);
match &res.row { match &res.row {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
@ -275,20 +194,18 @@ macro_rules! try_getable_postgres {
QueryResultRow::SqlxPostgres(row) => { QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row; use sqlx::Row;
row.try_get::<Option<$type>, _>(column.as_str()) 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")] #[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(_) => { QueryResultRow::SqlxSqlite(_) => {
panic!("{} unsupported by sqlx-sqlite", stringify!($type)) panic!("{} unsupported by sqlx-sqlite", stringify!($type))
} }
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
QueryResultRow::Mock(row) => match row.try_get(column.as_str()) { QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| {
Ok(v) => Ok(Some(v)), debug_print!("{:#?}", e.to_string());
Err(e) => { TryGetError::Null
debug_print!("{:#?}", e.to_string()); }),
Ok(None)
}
},
} }
} }
} }
@ -322,83 +239,41 @@ use rust_decimal::Decimal;
#[cfg(feature = "with-rust_decimal")] #[cfg(feature = "with-rust_decimal")]
impl TryGetable for 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); let column = format!("{}{}", pre, col);
match &res.row { match &res.row {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => { QueryResultRow::SqlxMySql(row) => {
use sqlx::Row; use sqlx::Row;
row.try_get(column.as_str()) row.try_get::<Option<Decimal>, _>(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")] #[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => { QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row; use sqlx::Row;
row.try_get(column.as_str()) row.try_get::<Option<Decimal>, _>(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")] #[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => { QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row; use sqlx::Row;
let val: f64 = row let val: Option<f64> = row
.try_get(column.as_str()) .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; use rust_decimal::prelude::FromPrimitive;
Decimal::from_f64(val) match val {
.ok_or_else(|| DbErr::Query("Failed to convert f64 into Decimal".to_owned())) Some(v) => Decimal::from_f64(v)
} .ok_or_else(|| TryGetError::DbErr(DbErr::Query("Failed to convert f64 into Decimal".to_owned()))),
#[cfg(feature = "mock")] None => Err(TryGetError::Null)
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)
}
} }
} }
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
QueryResultRow::Mock(row) => match row.try_get(column.as_str()) { QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| {
Ok(v) => Ok(Some(v)), debug_print!("{:#?}", e.to_string());
Err(e) => { TryGetError::Null
debug_print!("{:#?}", e.to_string()); }),
Ok(None)
}
},
} }
} }
} }

View File

@ -25,7 +25,7 @@
//! ``` //! ```
//! //!
//! [![Getting Started](https://img.shields.io/badge/Getting%20Started-blue)](https://www.sea-ql.org/SeaORM/docs/index) //! [![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) //! [![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) //! [![Discord](https://img.shields.io/discord/873880840487206962?label=Discord)](https://discord.com/invite/uCPdDXzbdv)
//! //!
@ -180,6 +180,12 @@
//! # Ok(()) //! # 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 //! ## License
//! //!
//! Licensed under either of //! Licensed under either of