commit af4fb2ab7df92131ebe140e53de7f15598b4bdf5 Author: Chris Tsang Date: Fri May 7 03:35:46 2021 +0800 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..950c227c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +target +Cargo.lock +*.sublime* +.vscode \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..ed529945 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,31 @@ +[workspace] +members = [ + ".", +] + +[package] +name = "sea-orm" +version = "0.1.0" +authors = [ "Chris Tsang " ] +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" ] } diff --git a/README.md b/README.md new file mode 100644 index 00000000..2d28b139 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +
+ +

SeaORM

+ +

+ An [adjective] ORM for Rust +

+ + Built with ❤️ by 🌊🦀🐠 + +
+ +# 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. diff --git a/bakery.sql b/bakery.sql new file mode 100644 index 00000000..97988223 --- /dev/null +++ b/bakery.sql @@ -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; \ No newline at end of file diff --git a/src/driver/mod.rs b/src/driver/mod.rs new file mode 100644 index 00000000..3944f04f --- /dev/null +++ b/src/driver/mod.rs @@ -0,0 +1 @@ +pub mod sqlx_mysql; diff --git a/src/driver/sqlx_mysql.rs b/src/driver/sqlx_mysql.rs new file mode 100644 index 00000000..c4de4d5e --- /dev/null +++ b/src/driver/sqlx_mysql.rs @@ -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 { + 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, 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 for QueryResult { + fn from(row: MySqlRow) -> QueryResult { + QueryResult { + row: QueryResultRow::SqlxMySql(row), + } + } +} diff --git a/src/entity/base.rs b/src/entity/base.rs new file mode 100644 index 00000000..6fdf8720 --- /dev/null +++ b/src/entity/base.rs @@ -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 + } +} diff --git a/src/entity/column.rs b/src/entity/column.rs new file mode 100644 index 00000000..54ad6f29 --- /dev/null +++ b/src/entity/column.rs @@ -0,0 +1,6 @@ +pub use sea_query::ColumnType; +use sea_query::Iden; + +pub trait Column: Iden { + fn col_type(&self) -> ColumnType; +} diff --git a/src/entity/identity.rs b/src/entity/identity.rs new file mode 100644 index 00000000..72848cd9 --- /dev/null +++ b/src/entity/identity.rs @@ -0,0 +1,22 @@ +use sea_query::{Iden, IntoIden}; +use std::rc::Rc; + +#[derive(Debug, Clone)] +pub enum Identity { + Unary(Rc), + // Binary((Rc, Rc)), + // Ternary((Rc, Rc, Rc)), +} + +pub trait IntoIdentity { + fn into_identity(self) -> Identity; +} + +impl IntoIdentity for T +where + T: IntoIden, +{ + fn into_identity(self) -> Identity { + Identity::Unary(self.into_iden()) + } +} diff --git a/src/entity/mod.rs b/src/entity/mod.rs new file mode 100644 index 00000000..21477aa2 --- /dev/null +++ b/src/entity/mod.rs @@ -0,0 +1,9 @@ +mod base; +mod column; +mod identity; +mod relation; + +pub use base::*; +pub use column::*; +pub use identity::*; +pub use relation::*; diff --git a/src/entity/relation.rs b/src/entity/relation.rs new file mode 100644 index 00000000..d1268919 --- /dev/null +++ b/src/entity/relation.rs @@ -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, + from_col: Identity, + to_col: Identity, +} + +pub struct RelationBuilder { + rel_type: RelationType, + from_tbl: Rc, + from_col: Option, + to_col: Option, +} + +impl RelationBuilder { + pub fn has_one(entity: E) -> Self + where + E: Entity, + { + Self::new(RelationType::HasOne, entity) + } + + pub fn has_many(entity: E) -> Self + where + E: Entity, + { + Self::new(RelationType::HasMany, entity) + } + + pub fn belongs_to(entity: E) -> Self + where + E: Entity, + { + Self::new(RelationType::BelongsTo, entity) + } + + fn new(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(mut self, identifier: T) -> Self + where + T: IntoIdentity, + { + self.from_col = Some(identifier.into_identity()); + self + } + + pub fn to(mut self, identifier: T) -> Self + where + T: IntoIdentity, + { + self.to_col = Some(identifier.into_identity()); + self + } +} + +impl From 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(), + } + } +} diff --git a/src/executor.rs b/src/executor.rs new file mode 100644 index 00000000..49df09b8 --- /dev/null +++ b/src/executor.rs @@ -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; + + async fn query_all(&self, stmt: Statement) -> Result, 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) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..0a3ed2c3 --- /dev/null +++ b/src/lib.rs @@ -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::*; diff --git a/src/query.rs b/src/query.rs new file mode 100644 index 00000000..a66c0de5 --- /dev/null +++ b/src/query.rs @@ -0,0 +1,9 @@ +use sqlx::mysql::MySqlRow; + +pub struct QueryResult { + pub(crate) row: QueryResultRow, +} + +pub(crate) enum QueryResultRow { + SqlxMySql(MySqlRow), +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 00000000..f6ddfd54 --- /dev/null +++ b/src/util.rs @@ -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; + }; +}