Initial Commit
This commit is contained in:
commit
af4fb2ab7d
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
target
|
||||
Cargo.lock
|
||||
*.sublime*
|
||||
.vscode
|
31
Cargo.toml
Normal file
31
Cargo.toml
Normal file
@ -0,0 +1,31 @@
|
||||
[workspace]
|
||||
members = [
|
||||
".",
|
||||
]
|
||||
|
||||
[package]
|
||||
name = "sea-orm"
|
||||
version = "0.1.0"
|
||||
authors = [ "Chris Tsang <tyt2y7@gmail.com>" ]
|
||||
edition = "2018"
|
||||
description = "A database agnostic ORM for Rust"
|
||||
license = "MIT OR Apache-2.0"
|
||||
documentation = "https://docs.rs/sea-orm"
|
||||
repository = "https://github.com/SeaQL/sea-orm"
|
||||
categories = [ "database" ]
|
||||
keywords = [ "orm", "database", "sql", "mysql", "postgres", "sqlite" ]
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "sea_orm"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
async-trait = "^0.1"
|
||||
async-std = { version = "^1.9", features = [ "attributes" ] }
|
||||
futures = { version = "^0.3" }
|
||||
sea-query = { version = "^0.10", features = [ "sqlx-mysql" ] }
|
||||
# sea-schema = { path = "../sea-schema" }
|
||||
serde = { version = "^1.0", features = [ "derive" ] }
|
||||
sqlx = { version = "^0.5", features = [ "runtime-async-std-native-tls", "mysql", "any", "chrono", "time", "bigdecimal", "decimal", "uuid", "json" ] }
|
||||
strum = { version = "^0.20", features = [ "derive" ] }
|
30
README.md
Normal file
30
README.md
Normal file
@ -0,0 +1,30 @@
|
||||
<div align="center">
|
||||
|
||||
<h1>SeaORM</h1>
|
||||
|
||||
<p>
|
||||
<strong>An [adjective] ORM for Rust</strong>
|
||||
</p>
|
||||
|
||||
<sub>Built with ❤️ by 🌊🦀🐠</sub>
|
||||
|
||||
</div>
|
||||
|
||||
# SeaORM - The Document ORM for Rust
|
||||
|
||||
## Features
|
||||
|
||||
1. Async
|
||||
SeaORM is new to the party and rely on SQLx, so async support is not an afterthought.
|
||||
|
||||
|
||||
2. Progressive
|
||||
Built upon SeaQuery (a dynamic query builder), SeaORM allows you to build complex queries without 'fighting the ORM'.
|
||||
|
||||
|
||||
3. Testable
|
||||
Use mock connection to write unit tests for your logic.
|
||||
|
||||
|
||||
4. API oriented
|
||||
Quickly build search models that help you filter, sort and paginate data in APIs.
|
7
bakery.sql
Normal file
7
bakery.sql
Normal file
@ -0,0 +1,7 @@
|
||||
DROP TABLE IF EXISTS `cake`;
|
||||
|
||||
CREATE TABLE `cake` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
1
src/driver/mod.rs
Normal file
1
src/driver/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod sqlx_mysql;
|
50
src/driver/sqlx_mysql.rs
Normal file
50
src/driver/sqlx_mysql.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use async_trait::async_trait;
|
||||
use sqlx::{mysql::MySqlRow, MySqlPool};
|
||||
|
||||
sea_query::sea_query_driver_mysql!();
|
||||
use sea_query_driver_mysql::bind_query;
|
||||
|
||||
use crate::{debug_print, executor::*, query::*};
|
||||
|
||||
pub struct SqlxMySqlExecutor {
|
||||
pool: MySqlPool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Executor for SqlxMySqlExecutor {
|
||||
async fn query_one(&self, stmt: Statement) -> Result<QueryResult, ExecErr> {
|
||||
debug_print!("{}, {:?}", sql, values);
|
||||
|
||||
let query = bind_query(sqlx::query(&stmt.sql), &stmt.values);
|
||||
if let Ok(row) = query
|
||||
.fetch_one(&mut self.pool.acquire().await.unwrap())
|
||||
.await
|
||||
{
|
||||
Ok(row.into())
|
||||
} else {
|
||||
Err(ExecErr)
|
||||
}
|
||||
}
|
||||
|
||||
async fn query_all(&self, stmt: Statement) -> Result<Vec<QueryResult>, ExecErr> {
|
||||
debug_print!("{}, {:?}", sql, values);
|
||||
|
||||
let query = bind_query(sqlx::query(&stmt.sql), &stmt.values);
|
||||
if let Ok(rows) = query
|
||||
.fetch_all(&mut self.pool.acquire().await.unwrap())
|
||||
.await
|
||||
{
|
||||
Ok(rows.into_iter().map(|r| r.into()).collect())
|
||||
} else {
|
||||
Err(ExecErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MySqlRow> for QueryResult {
|
||||
fn from(row: MySqlRow) -> QueryResult {
|
||||
QueryResult {
|
||||
row: QueryResultRow::SqlxMySql(row),
|
||||
}
|
||||
}
|
||||
}
|
20
src/entity/base.rs
Normal file
20
src/entity/base.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use super::{Column, Identity, Relation};
|
||||
use sea_query::Iden;
|
||||
use std::fmt::Debug;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
pub trait Entity: Iden + Default + Debug {
|
||||
type Model;
|
||||
|
||||
type Column: Column + IntoEnumIterator;
|
||||
|
||||
type Relation: Relation + IntoEnumIterator;
|
||||
|
||||
fn table_name() -> Self;
|
||||
|
||||
fn primary_key() -> Identity;
|
||||
|
||||
fn auto_increment() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
6
src/entity/column.rs
Normal file
6
src/entity/column.rs
Normal file
@ -0,0 +1,6 @@
|
||||
pub use sea_query::ColumnType;
|
||||
use sea_query::Iden;
|
||||
|
||||
pub trait Column: Iden {
|
||||
fn col_type(&self) -> ColumnType;
|
||||
}
|
22
src/entity/identity.rs
Normal file
22
src/entity/identity.rs
Normal file
@ -0,0 +1,22 @@
|
||||
use sea_query::{Iden, IntoIden};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Identity {
|
||||
Unary(Rc<dyn Iden>),
|
||||
// Binary((Rc<dyn Iden>, Rc<dyn Iden>)),
|
||||
// Ternary((Rc<dyn Iden>, Rc<dyn Iden>, Rc<dyn Iden>)),
|
||||
}
|
||||
|
||||
pub trait IntoIdentity {
|
||||
fn into_identity(self) -> Identity;
|
||||
}
|
||||
|
||||
impl<T> IntoIdentity for T
|
||||
where
|
||||
T: IntoIden,
|
||||
{
|
||||
fn into_identity(self) -> Identity {
|
||||
Identity::Unary(self.into_iden())
|
||||
}
|
||||
}
|
9
src/entity/mod.rs
Normal file
9
src/entity/mod.rs
Normal file
@ -0,0 +1,9 @@
|
||||
mod base;
|
||||
mod column;
|
||||
mod identity;
|
||||
mod relation;
|
||||
|
||||
pub use base::*;
|
||||
pub use column::*;
|
||||
pub use identity::*;
|
||||
pub use relation::*;
|
91
src/entity/relation.rs
Normal file
91
src/entity/relation.rs
Normal file
@ -0,0 +1,91 @@
|
||||
use super::{Identity, IntoIdentity};
|
||||
use crate::Entity;
|
||||
use sea_query::{Iden, IntoIden};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RelationType {
|
||||
HasOne,
|
||||
HasMany,
|
||||
BelongsTo,
|
||||
}
|
||||
|
||||
pub trait Relation {
|
||||
fn rel_def() -> RelationDef;
|
||||
}
|
||||
|
||||
pub struct RelationDef {
|
||||
rel_type: RelationType,
|
||||
from_tbl: Rc<dyn Iden>,
|
||||
from_col: Identity,
|
||||
to_col: Identity,
|
||||
}
|
||||
|
||||
pub struct RelationBuilder {
|
||||
rel_type: RelationType,
|
||||
from_tbl: Rc<dyn Iden>,
|
||||
from_col: Option<Identity>,
|
||||
to_col: Option<Identity>,
|
||||
}
|
||||
|
||||
impl RelationBuilder {
|
||||
pub fn has_one<E: 'static>(entity: E) -> Self
|
||||
where
|
||||
E: Entity,
|
||||
{
|
||||
Self::new(RelationType::HasOne, entity)
|
||||
}
|
||||
|
||||
pub fn has_many<E: 'static>(entity: E) -> Self
|
||||
where
|
||||
E: Entity,
|
||||
{
|
||||
Self::new(RelationType::HasMany, entity)
|
||||
}
|
||||
|
||||
pub fn belongs_to<E: 'static>(entity: E) -> Self
|
||||
where
|
||||
E: Entity,
|
||||
{
|
||||
Self::new(RelationType::BelongsTo, entity)
|
||||
}
|
||||
|
||||
fn new<E: 'static>(rel_type: RelationType, entity: E) -> Self
|
||||
where
|
||||
E: Entity,
|
||||
{
|
||||
Self {
|
||||
rel_type,
|
||||
from_tbl: entity.into_iden(),
|
||||
from_col: None,
|
||||
to_col: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from<T>(mut self, identifier: T) -> Self
|
||||
where
|
||||
T: IntoIdentity,
|
||||
{
|
||||
self.from_col = Some(identifier.into_identity());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn to<T>(mut self, identifier: T) -> Self
|
||||
where
|
||||
T: IntoIdentity,
|
||||
{
|
||||
self.to_col = Some(identifier.into_identity());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RelationBuilder> for RelationDef {
|
||||
fn from(b: RelationBuilder) -> Self {
|
||||
RelationDef {
|
||||
rel_type: b.rel_type,
|
||||
from_tbl: b.from_tbl,
|
||||
from_col: b.from_col.unwrap(),
|
||||
to_col: b.to_col.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
38
src/executor.rs
Normal file
38
src/executor.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use crate::QueryResult;
|
||||
use async_trait::async_trait;
|
||||
use sea_query::Values;
|
||||
use std::{error::Error, fmt};
|
||||
|
||||
pub struct Statement {
|
||||
pub sql: String,
|
||||
pub values: Values,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Executor {
|
||||
async fn query_one(&self, stmt: Statement) -> Result<QueryResult, ExecErr>;
|
||||
|
||||
async fn query_all(&self, stmt: Statement) -> Result<Vec<QueryResult>, ExecErr>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExecErr;
|
||||
|
||||
// ----- //
|
||||
|
||||
impl From<(String, Values)> for Statement {
|
||||
fn from(stmt: (String, Values)) -> Statement {
|
||||
Statement {
|
||||
sql: stmt.0,
|
||||
values: stmt.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ExecErr {}
|
||||
|
||||
impl fmt::Display for ExecErr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
10
src/lib.rs
Normal file
10
src/lib.rs
Normal file
@ -0,0 +1,10 @@
|
||||
mod driver;
|
||||
mod entity;
|
||||
mod executor;
|
||||
mod query;
|
||||
mod util;
|
||||
|
||||
pub use driver::*;
|
||||
pub use entity::*;
|
||||
pub use executor::*;
|
||||
pub use query::*;
|
9
src/query.rs
Normal file
9
src/query.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use sqlx::mysql::MySqlRow;
|
||||
|
||||
pub struct QueryResult {
|
||||
pub(crate) row: QueryResultRow,
|
||||
}
|
||||
|
||||
pub(crate) enum QueryResultRow {
|
||||
SqlxMySql(MySqlRow),
|
||||
}
|
14
src/util.rs
Normal file
14
src/util.rs
Normal file
@ -0,0 +1,14 @@
|
||||
#[macro_export]
|
||||
#[cfg(feature = "debug-print")]
|
||||
macro_rules! debug_print {
|
||||
($( $args:expr ),*) => { println!( $( $args ),* ); }
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
// Non-debug version
|
||||
#[cfg(not(feature = "debug-print"))]
|
||||
macro_rules! debug_print {
|
||||
($( $args:expr ),*) => {
|
||||
true;
|
||||
};
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user