diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 82d1f6a9..c37cac7c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -177,7 +177,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - path: [async-std, tokio, actix_example, actix4_example, rocket_example] + path: [basic, actix_example, actix4_example, rocket_example] steps: - uses: actions/checkout@v2 @@ -193,6 +193,28 @@ jobs: args: > --manifest-path examples/${{ matrix.path }}/Cargo.toml + issues: + name: Issues + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + path: [86] + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - uses: actions-rs/cargo@v1 + with: + command: build + args: > + --manifest-path issues/${{ matrix.path }}/Cargo.toml + sqlite: name: SQLite runs-on: ubuntu-20.04 diff --git a/CHANGELOG.md b/CHANGELOG.md index 693c1d0e..ee544831 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ 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.2.6 - 2021-10-09 + +- [[#224]] [sea-orm-cli] Date & Time column type mapping +- Escape rust keywords with `r#` raw identifier + +[#224]: https://github.com/SeaQL/sea-orm/pull/224 + ## 0.2.5 - 2021-10-06 - [[#227]] Resolve "Inserting actual none value of Option results in panic" diff --git a/Cargo.toml b/Cargo.toml index 62d180e9..28bc14e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [".", "sea-orm-macros", "sea-orm-codegen"] [package] name = "sea-orm" -version = "0.2.5" +version = "0.2.6" authors = ["Chris Tsang "] edition = "2018" description = "🐚 An async & dynamic ORM for Rust" @@ -29,8 +29,8 @@ futures = { version = "^0.3" } futures-util = { version = "^0.3" } log = { version = "^0.4", optional = true } rust_decimal = { version = "^1", optional = true } -sea-orm-macros = { version = "^0.2.5", path = "sea-orm-macros", optional = true } -sea-query = { version = "^0.17.0", features = ["thread-safe"] } +sea-orm-macros = { version = "^0.2.6", path = "sea-orm-macros", optional = true } +sea-query = { version = "^0.17.1", features = ["thread-safe"] } sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1", optional = true } diff --git a/README.md b/README.md index b4525b31..d6bfcdff 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ SeaORM is a relational ORM to help you build light weight and concurrent web services in Rust. [![Getting Started](https://img.shields.io/badge/Getting%20Started-brightgreen)](https://www.sea-ql.org/SeaORM/docs/index) -[![Usage Example](https://img.shields.io/badge/Usage%20Example-yellow)](https://github.com/SeaQL/sea-orm/tree/master/examples/async-std) +[![Usage Example](https://img.shields.io/badge/Usage%20Example-yellow)](https://github.com/SeaQL/sea-orm/tree/master/examples/basic) [![Actix Example](https://img.shields.io/badge/Actix%20Example-blue)](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example) [![Rocket Example](https://img.shields.io/badge/Rocket%20Example-orange)](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) [![Discord](https://img.shields.io/discord/873880840487206962?label=Discord)](https://discord.com/invite/uCPdDXzbdv) diff --git a/examples/async-std/Cargo.toml b/examples/basic/Cargo.toml similarity index 100% rename from examples/async-std/Cargo.toml rename to examples/basic/Cargo.toml diff --git a/examples/async-std/Readme.md b/examples/basic/Readme.md similarity index 100% rename from examples/async-std/Readme.md rename to examples/basic/Readme.md diff --git a/examples/async-std/bakery.sql b/examples/basic/bakery.sql similarity index 100% rename from examples/async-std/bakery.sql rename to examples/basic/bakery.sql diff --git a/examples/async-std/import.sh b/examples/basic/import.sh similarity index 100% rename from examples/async-std/import.sh rename to examples/basic/import.sh diff --git a/examples/async-std/src/entities.rs b/examples/basic/src/entities.rs similarity index 100% rename from examples/async-std/src/entities.rs rename to examples/basic/src/entities.rs diff --git a/examples/async-std/src/example_cake.rs b/examples/basic/src/example_cake.rs similarity index 100% rename from examples/async-std/src/example_cake.rs rename to examples/basic/src/example_cake.rs diff --git a/examples/async-std/src/example_cake_filling.rs b/examples/basic/src/example_cake_filling.rs similarity index 100% rename from examples/async-std/src/example_cake_filling.rs rename to examples/basic/src/example_cake_filling.rs diff --git a/examples/async-std/src/example_filling.rs b/examples/basic/src/example_filling.rs similarity index 100% rename from examples/async-std/src/example_filling.rs rename to examples/basic/src/example_filling.rs diff --git a/examples/async-std/src/example_fruit.rs b/examples/basic/src/example_fruit.rs similarity index 100% rename from examples/async-std/src/example_fruit.rs rename to examples/basic/src/example_fruit.rs diff --git a/examples/async-std/src/main.rs b/examples/basic/src/main.rs similarity index 100% rename from examples/async-std/src/main.rs rename to examples/basic/src/main.rs diff --git a/examples/async-std/src/operation.rs b/examples/basic/src/operation.rs similarity index 100% rename from examples/async-std/src/operation.rs rename to examples/basic/src/operation.rs diff --git a/examples/async-std/src/select.rs b/examples/basic/src/select.rs similarity index 100% rename from examples/async-std/src/select.rs rename to examples/basic/src/select.rs diff --git a/examples/tokio/Cargo.toml b/issues/86/Cargo.toml similarity index 100% rename from examples/tokio/Cargo.toml rename to issues/86/Cargo.toml diff --git a/examples/tokio/src/cake.rs b/issues/86/src/cake.rs similarity index 100% rename from examples/tokio/src/cake.rs rename to issues/86/src/cake.rs diff --git a/examples/tokio/src/main.rs b/issues/86/src/main.rs similarity index 100% rename from examples/tokio/src/main.rs rename to issues/86/src/main.rs diff --git a/sea-orm-cli/Cargo.toml b/sea-orm-cli/Cargo.toml index 3c05d08b..07db2e4b 100644 --- a/sea-orm-cli/Cargo.toml +++ b/sea-orm-cli/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "sea-orm-cli" -version = "0.2.5" +version = "0.2.6" authors = [ "Billy Chan " ] edition = "2018" description = "Command line utility for SeaORM" @@ -21,7 +21,7 @@ path = "src/main.rs" clap = { version = "^2.33.3" } dotenv = { version = "^0.15" } async-std = { version = "^1.9", features = [ "attributes" ] } -sea-orm-codegen = { version = "^0.2.5", path = "../sea-orm-codegen" } +sea-orm-codegen = { version = "^0.2.6", path = "../sea-orm-codegen" } sea-schema = { version = "^0.2.9", default-features = false, features = [ "debug-print", "sqlx-mysql", diff --git a/sea-orm-codegen/Cargo.toml b/sea-orm-codegen/Cargo.toml index 0e8fa624..9013cea4 100644 --- a/sea-orm-codegen/Cargo.toml +++ b/sea-orm-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sea-orm-codegen" -version = "0.2.5" +version = "0.2.6" authors = ["Billy Chan "] edition = "2018" description = "Code Generator for SeaORM" diff --git a/sea-orm-macros/Cargo.toml b/sea-orm-macros/Cargo.toml index 22b58c01..cde1575c 100644 --- a/sea-orm-macros/Cargo.toml +++ b/sea-orm-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sea-orm-macros" -version = "0.2.5" +version = "0.2.6" authors = [ "Billy Chan " ] edition = "2018" description = "Derive macros for SeaORM" diff --git a/src/driver/sqlx_sqlite.rs b/src/driver/sqlx_sqlite.rs index 0f7548fc..ea2e05e3 100644 --- a/src/driver/sqlx_sqlite.rs +++ b/src/driver/sqlx_sqlite.rs @@ -1,7 +1,7 @@ use std::{future::Future, pin::Pin}; use sqlx::{ - sqlite::{SqliteArguments, SqliteQueryResult, SqliteRow}, + sqlite::{SqliteArguments, SqlitePoolOptions, SqliteQueryResult, SqliteRow}, Sqlite, SqlitePool, }; @@ -29,7 +29,11 @@ impl SqlxSqliteConnector { } pub async fn connect(string: &str) -> Result { - if let Ok(pool) = SqlitePool::connect(string).await { + if let Ok(pool) = SqlitePoolOptions::new() + .max_connections(1) + .connect(string) + .await + { Ok(DatabaseConnection::SqlxSqlitePoolConnection( SqlxSqlitePoolConnection { pool }, )) diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs index 32e9d77d..c98103f6 100644 --- a/src/entity/active_model.rs +++ b/src/entity/active_model.rs @@ -1,8 +1,8 @@ use crate::{ - error::*, ConnectionTrait, DeleteResult, EntityTrait, Iterable, PrimaryKeyToColumn, - PrimaryKeyTrait, Value, + error::*, ConnectionTrait, DeleteResult, EntityTrait, Iterable, PrimaryKeyToColumn, Value, }; use async_trait::async_trait; +use sea_query::ValueTuple; use std::fmt::Debug; #[derive(Clone, Debug, Default)] @@ -10,7 +10,8 @@ pub struct ActiveValue where V: Into, { - value: Option, + // Don't want to call ActiveValue::unwrap() and cause panic + pub(self) value: Option, state: ActiveValueState, } @@ -67,6 +68,42 @@ pub trait ActiveModelTrait: Clone + Debug { fn default() -> Self; + #[allow(clippy::question_mark)] + fn get_primary_key_value(&self) -> Option { + let mut cols = ::PrimaryKey::iter(); + macro_rules! next { + () => { + if let Some(col) = cols.next() { + if let Some(val) = self.get(col.into_column()).value { + val + } else { + return None; + } + } else { + return None; + } + }; + } + match ::PrimaryKey::iter().count() { + 1 => { + let s1 = next!(); + Some(ValueTuple::One(s1)) + } + 2 => { + let s1 = next!(); + let s2 = next!(); + Some(ValueTuple::Two(s1, s2)) + } + 3 => { + let s1 = next!(); + let s2 = next!(); + let s3 = next!(); + Some(ValueTuple::Three(s1, s2, s3)) + } + _ => panic!("The arity cannot be larger than 3"), + } + } + async fn insert<'a, C>(self, db: &'a C) -> Result where ::Model: IntoActiveModel, @@ -76,19 +113,12 @@ pub trait ActiveModelTrait: Clone + Debug { let am = self; let exec = ::insert(am).exec(db); let res = exec.await?; - // Assume valid last_insert_id is not equals to Default::default() - if res.last_insert_id - != <::PrimaryKey as PrimaryKeyTrait>::ValueType::default() - { - let found = ::find_by_id(res.last_insert_id) - .one(db) - .await?; - match found { - Some(model) => Ok(model.into_active_model()), - None => Err(DbErr::Exec("Failed to find inserted item".to_owned())), - } - } else { - Ok(Self::default()) + let found = ::find_by_id(res.last_insert_id) + .one(db) + .await?; + match found { + Some(model) => Ok(model.into_active_model()), + None => Err(DbErr::Exec("Failed to find inserted item".to_owned())), } } @@ -217,23 +247,23 @@ where matches!(self.state, ActiveValueState::Unset) } - pub fn take(&mut self) -> V { + pub fn take(&mut self) -> Option { self.state = ActiveValueState::Unset; - self.value.take().unwrap() + self.value.take() } pub fn unwrap(self) -> V { self.value.unwrap() } - pub fn into_value(self) -> Value { - self.value.unwrap().into() + pub fn into_value(self) -> Option { + self.value.map(Into::into) } pub fn into_wrapped_value(self) -> ActiveValue { match self.state { - ActiveValueState::Set => ActiveValue::set(self.into_value()), - ActiveValueState::Unchanged => ActiveValue::unchanged(self.into_value()), + ActiveValueState::Set => ActiveValue::set(self.into_value().unwrap()), + ActiveValueState::Unchanged => ActiveValue::unchanged(self.into_value().unwrap()), ActiveValueState::Unset => ActiveValue::unset(), } } diff --git a/src/entity/primary_key.rs b/src/entity/primary_key.rs index 463f1482..a5e4cde0 100644 --- a/src/entity/primary_key.rs +++ b/src/entity/primary_key.rs @@ -1,16 +1,16 @@ use super::{ColumnTrait, IdenStatic, Iterable}; use crate::{TryFromU64, TryGetableMany}; -use sea_query::IntoValueTuple; +use sea_query::{FromValueTuple, IntoValueTuple}; use std::fmt::Debug; //LINT: composite primary key cannot auto increment pub trait PrimaryKeyTrait: IdenStatic + Iterable { type ValueType: Sized + Send - + Default + Debug + PartialEq + IntoValueTuple + + FromValueTuple + TryGetableMany + TryFromU64; diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 7d2e3b11..d9230d11 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -2,14 +2,15 @@ use crate::{ error::*, ActiveModelTrait, ConnectionTrait, DbBackend, EntityTrait, Insert, PrimaryKeyTrait, Statement, TryFromU64, }; -use sea_query::InsertStatement; -use std::marker::PhantomData; +use sea_query::{FromValueTuple, InsertStatement, ValueTuple}; +use std::{future::Future, marker::PhantomData}; -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct Inserter where A: ActiveModelTrait, { + primary_key: Option, query: InsertStatement, model: PhantomData, } @@ -27,12 +28,14 @@ where A: ActiveModelTrait, { #[allow(unused_mut)] - pub async fn exec<'a, C>(self, db: &'a C) -> Result, DbErr> + pub fn exec<'a, C>( + self, + db: &'a C, + ) -> impl Future, DbErr>> + 'a where C: ConnectionTrait<'a>, A: 'a, { - // TODO: extract primary key's value from query // so that self is dropped before entering await let mut query = self.query; if db.get_database_backend() == DbBackend::Postgres { @@ -45,8 +48,7 @@ where ); } } - Inserter::::new(query).exec(db).await - // TODO: return primary key if extracted before, otherwise use InsertResult + Inserter::::new(self.primary_key, query).exec(db) } } @@ -54,46 +56,59 @@ impl Inserter where A: ActiveModelTrait, { - pub fn new(query: InsertStatement) -> Self { + pub fn new(primary_key: Option, query: InsertStatement) -> Self { Self { + primary_key, query, model: PhantomData, } } - pub async fn exec<'a, C>(self, db: &'a C) -> Result, DbErr> + pub fn exec<'a, C>( + self, + db: &'a C, + ) -> impl Future, DbErr>> + 'a where C: ConnectionTrait<'a>, A: 'a, { let builder = db.get_database_backend(); - exec_insert(builder.build(&self.query), db).await + exec_insert(self.primary_key, builder.build(&self.query), db) } } // Only Statement impl Send -async fn exec_insert<'a, A, C>(statement: Statement, db: &C) -> Result, DbErr> +async fn exec_insert<'a, A, C>( + primary_key: Option, + statement: Statement, + db: &'a C, +) -> Result, DbErr> where C: ConnectionTrait<'a>, A: ActiveModelTrait, { type PrimaryKey = <::Entity as EntityTrait>::PrimaryKey; type ValueTypeOf = as PrimaryKeyTrait>::ValueType; - let last_insert_id = match db.get_database_backend() { + let last_insert_id_opt = match db.get_database_backend() { DbBackend::Postgres => { use crate::{sea_query::Iden, Iterable}; let cols = PrimaryKey::::iter() .map(|col| col.to_string()) .collect::>(); let res = db.query_one(statement).await?.unwrap(); - res.try_get_many("", cols.as_ref()).unwrap_or_default() + res.try_get_many("", cols.as_ref()).ok() } _ => { let last_insert_id = db.execute(statement).await?.last_insert_id(); - ValueTypeOf::::try_from_u64(last_insert_id) - .ok() - .unwrap_or_default() + ValueTypeOf::::try_from_u64(last_insert_id).ok() } }; + let last_insert_id = match last_insert_id_opt { + Some(last_insert_id) => last_insert_id, + None => match primary_key { + Some(value_tuple) => FromValueTuple::from_value_tuple(value_tuple), + None => return Err(DbErr::Exec("Fail to unpack last_insert_id".to_owned())), + }, + }; Ok(InsertResult { last_insert_id }) } diff --git a/src/lib.rs b/src/lib.rs index 6ddc442c..1b78cf58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ //! SeaORM is a relational ORM to help you build light weight and concurrent web services in Rust. //! //! [![Getting Started](https://img.shields.io/badge/Getting%20Started-brightgreen)](https://www.sea-ql.org/SeaORM/docs/index) -//! [![Usage Example](https://img.shields.io/badge/Usage%20Example-yellow)](https://github.com/SeaQL/sea-orm/tree/master/examples/async-std) +//! [![Usage Example](https://img.shields.io/badge/Usage%20Example-yellow)](https://github.com/SeaQL/sea-orm/tree/master/examples/basic) //! [![Actix Example](https://img.shields.io/badge/Actix%20Example-blue)](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example) //! [![Rocket Example](https://img.shields.io/badge/Rocket%20Example-orange)](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) //! [![Discord](https://img.shields.io/discord/873880840487206962?label=Discord)](https://discord.com/invite/uCPdDXzbdv) diff --git a/src/query/insert.rs b/src/query/insert.rs index a65071e1..5e504a0c 100644 --- a/src/query/insert.rs +++ b/src/query/insert.rs @@ -1,14 +1,18 @@ -use crate::{ActiveModelTrait, EntityName, EntityTrait, IntoActiveModel, Iterable, QueryTrait}; +use crate::{ + ActiveModelTrait, EntityName, EntityTrait, IntoActiveModel, Iterable, PrimaryKeyTrait, + QueryTrait, +}; use core::marker::PhantomData; -use sea_query::InsertStatement; +use sea_query::{InsertStatement, ValueTuple}; -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct Insert where A: ActiveModelTrait, { pub(crate) query: InsertStatement, pub(crate) columns: Vec, + pub(crate) primary_key: Option, pub(crate) model: PhantomData, } @@ -31,6 +35,7 @@ where .into_table(A::Entity::default().table_ref()) .to_owned(), columns: Vec::new(), + primary_key: None, model: PhantomData, } } @@ -107,6 +112,12 @@ where M: IntoActiveModel, { let mut am: A = m.into_active_model(); + self.primary_key = + if !<::PrimaryKey as PrimaryKeyTrait>::auto_increment() { + am.get_primary_key_value() + } else { + None + }; let mut columns = Vec::new(); let mut values = Vec::new(); let columns_empty = self.columns.is_empty(); @@ -120,7 +131,7 @@ where } if av_has_val { columns.push(col); - values.push(av.into_value()); + values.push(av.into_value().unwrap()); } } self.query.columns(columns); diff --git a/tests/crud/create_cake.rs b/tests/crud/create_cake.rs index 4fa914a5..df5130aa 100644 --- a/tests/crud/create_cake.rs +++ b/tests/crud/create_cake.rs @@ -58,11 +58,7 @@ pub async fn test_create_cake(db: &DbConn) { .expect("could not insert cake_baker"); assert_eq!( cake_baker_res.last_insert_id, - if cfg!(feature = "sqlx-postgres") { - (cake_baker.cake_id.unwrap(), cake_baker.baker_id.unwrap()) - } else { - Default::default() - } + (cake_baker.cake_id.unwrap(), cake_baker.baker_id.unwrap()) ); assert!(cake.is_some()); diff --git a/tests/crud/create_lineitem.rs b/tests/crud/create_lineitem.rs index da82cc82..0ba9c7d3 100644 --- a/tests/crud/create_lineitem.rs +++ b/tests/crud/create_lineitem.rs @@ -57,11 +57,7 @@ pub async fn test_create_lineitem(db: &DbConn) { .expect("could not insert cake_baker"); assert_eq!( cake_baker_res.last_insert_id, - if cfg!(feature = "sqlx-postgres") { - (cake_baker.cake_id.unwrap(), cake_baker.baker_id.unwrap()) - } else { - Default::default() - } + (cake_baker.cake_id.unwrap(), cake_baker.baker_id.unwrap()) ); // Customer diff --git a/tests/crud/create_order.rs b/tests/crud/create_order.rs index ba8ff09b..6de3d46f 100644 --- a/tests/crud/create_order.rs +++ b/tests/crud/create_order.rs @@ -57,11 +57,7 @@ pub async fn test_create_order(db: &DbConn) { .expect("could not insert cake_baker"); assert_eq!( cake_baker_res.last_insert_id, - if cfg!(feature = "sqlx-postgres") { - (cake_baker.cake_id.unwrap(), cake_baker.baker_id.unwrap()) - } else { - Default::default() - } + (cake_baker.cake_id.unwrap(), cake_baker.baker_id.unwrap()) ); // Customer diff --git a/tests/sequential_op_tests.rs b/tests/sequential_op_tests.rs index 47e69ccb..ddaf62c2 100644 --- a/tests/sequential_op_tests.rs +++ b/tests/sequential_op_tests.rs @@ -84,11 +84,7 @@ async fn init_setup(db: &DatabaseConnection) { .expect("could not insert cake_baker"); assert_eq!( cake_baker_res.last_insert_id, - if cfg!(feature = "sqlx-postgres") { - (cake_baker.cake_id.unwrap(), cake_baker.baker_id.unwrap()) - } else { - Default::default() - } + (cake_baker.cake_id.unwrap(), cake_baker.baker_id.unwrap()) ); let customer_kate = customer::ActiveModel { @@ -225,11 +221,7 @@ async fn create_cake(db: &DatabaseConnection, baker: baker::Model) -> Option Result<(), DbErr> { assert_eq!(Metadata::find().one(db).await?, Some(metadata.clone())); - assert_eq!( - res.last_insert_id, - if cfg!(feature = "sqlx-postgres") { - metadata.uuid - } else { - Default::default() - } - ); + assert_eq!(res.last_insert_id, metadata.uuid); let update_res = Metadata::update(metadata::ActiveModel { value: Set("0.22".to_owned()),