Merge branch 'master' into ss/rocket-example
This commit is contained in:
commit
3f6b2e3516
39
ARCHITECTURE.md
Normal file
39
ARCHITECTURE.md
Normal 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.
|
@ -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
|
||||||
|
|
||||||
@ -66,3 +66,34 @@ 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();
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
10
README.md
10
README.md
@ -25,7 +25,7 @@ This is an early release of SeaORM, the API is not stable yet.
|
|||||||
```
|
```
|
||||||
|
|
||||||
[](https://www.sea-ql.org/SeaORM/docs/index)
|
[](https://www.sea-ql.org/SeaORM/docs/index)
|
||||||
[](https://github.com/SeaQL/sea-orm/tree/master/examples/sqlx)
|
[](https://github.com/SeaQL/sea-orm/tree/master/examples)
|
||||||
[](https://github.com/SeaQL/sea-orm/issues/37)
|
[](https://github.com/SeaQL/sea-orm/issues/37)
|
||||||
[](https://discord.com/invite/uCPdDXzbdv)
|
[](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
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! [](https://www.sea-ql.org/SeaORM/docs/index)
|
//! [](https://www.sea-ql.org/SeaORM/docs/index)
|
||||||
//! [](https://github.com/SeaQL/sea-orm/tree/master/examples/sqlx)
|
//! [](https://github.com/SeaQL/sea-orm/tree/master/examples)
|
||||||
//! [](https://github.com/SeaQL/sea-orm/issues/37)
|
//! [](https://github.com/SeaQL/sea-orm/issues/37)
|
||||||
//! [](https://discord.com/invite/uCPdDXzbdv)
|
//! [](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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user