From dc14d280b8137de9fb3371c48dd141f3f42133e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Wed, 27 Sep 2023 13:57:21 +0800 Subject: [PATCH 01/49] feat: Add proxy connection type --- Cargo.toml | 8 +- src/database/db_connection.rs | 84 +++++++++++++- src/database/mod.rs | 23 ++++ src/database/proxy.rs | 208 ++++++++++++++++++++++++++++++++++ src/database/stream/query.rs | 19 ++++ src/database/transaction.rs | 16 +++ src/driver/mod.rs | 4 + src/driver/proxy.rs | 152 +++++++++++++++++++++++++ 8 files changed, 507 insertions(+), 7 deletions(-) create mode 100644 src/database/proxy.rs create mode 100644 src/driver/proxy.rs diff --git a/Cargo.toml b/Cargo.toml index ed33aea24..3b2be85ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ keywords = ["async", "orm", "mysql", "postgres", "sqlite"] rust-version = "1.65" [package.metadata.docs.rs] -features = ["default", "sqlx-all", "mock", "runtime-async-std-native-tls", "postgres-array", "sea-orm-internal"] +features = ["default", "sqlx-all", "mock", "proxy", "runtime-async-std-native-tls", "postgres-array", "sea-orm-internal"] rustdoc-args = ["--cfg", "docsrs"] [lib] @@ -55,7 +55,7 @@ actix-rt = { version = "2.2.0" } maplit = { version = "1" } rust_decimal_macros = { version = "1" } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } -sea-orm = { path = ".", features = ["mock", "debug-print", "tests-cfg", "postgres-array", "sea-orm-internal"] } +sea-orm = { path = ".", features = ["mock", "proxy", "debug-print", "tests-cfg", "postgres-array", "sea-orm-internal"] } pretty_assertions = { version = "0.7" } time = { version = "0.3", features = ["macros"] } uuid = { version = "1", features = ["v4"] } @@ -76,6 +76,10 @@ default = [ ] macros = ["sea-orm-macros/derive"] mock = [] +proxy = ['proxy-mysql', 'proxy-postgres', 'proxy-sqlite'] +proxy-mysql = [] +proxy-postgres = [] +proxy-sqlite = [] with-json = ["serde_json", "sea-query/with-json", "chrono?/serde", "time?/serde", "uuid?/serde", "sea-query-binder?/with-json", "sqlx?/json"] with-chrono = ["chrono", "sea-query/with-chrono", "sea-query-binder?/with-chrono", "sqlx?/chrono"] with-rust_decimal = ["rust_decimal", "sea-query/with-rust_decimal", "sea-query-binder?/with-rust_decimal", "sqlx?/rust_decimal"] diff --git a/src/database/db_connection.rs b/src/database/db_connection.rs index ad2b56900..a88c286fe 100644 --- a/src/database/db_connection.rs +++ b/src/database/db_connection.rs @@ -10,7 +10,7 @@ use url::Url; #[cfg(feature = "sqlx-dep")] use sqlx::pool::PoolConnection; -#[cfg(feature = "mock")] +#[cfg(any(feature = "mock", feature = "proxy"))] use std::sync::Arc; /// Handle a database connection depending on the backend @@ -20,15 +20,23 @@ pub enum DatabaseConnection { /// Create a MYSQL database connection and pool #[cfg(feature = "sqlx-mysql")] SqlxMySqlPoolConnection(crate::SqlxMySqlPoolConnection), - /// Create a PostgreSQL database connection and pool + + /// Create a PostgreSQL database connection and pool #[cfg(feature = "sqlx-postgres")] SqlxPostgresPoolConnection(crate::SqlxPostgresPoolConnection), - /// Create a SQLite database connection and pool + + /// Create a SQLite database connection and pool #[cfg(feature = "sqlx-sqlite")] SqlxSqlitePoolConnection(crate::SqlxSqlitePoolConnection), - /// Create a Mock database connection useful for testing + + /// Create a Mock database connection useful for testing #[cfg(feature = "mock")] MockDatabaseConnection(Arc), + + // Create a Proxy database connection useful for proxying + #[cfg(any(feature = "proxy-mysql", feature = "proxy-postgres", feature = "proxy-sqlite"))] + ProxyDatabaseConnection(Arc), + /// The connection to the database has been severed Disconnected, } @@ -66,7 +74,9 @@ pub(crate) enum InnerConnection { #[cfg(feature = "sqlx-sqlite")] Sqlite(PoolConnection), #[cfg(feature = "mock")] - Mock(std::sync::Arc), + Mock(Arc), + #[cfg(feature = "proxy")] + Proxy(Arc), } impl std::fmt::Debug for DatabaseConnection { @@ -83,6 +93,8 @@ impl std::fmt::Debug for DatabaseConnection { Self::SqlxSqlitePoolConnection(_) => "SqlxSqlitePoolConnection", #[cfg(feature = "mock")] Self::MockDatabaseConnection(_) => "MockDatabaseConnection", + #[cfg(feature = "proxy")] + Self::ProxyDatabaseConnection(_) => "ProxyDatabaseConnection", Self::Disconnected => "Disconnected", } ) @@ -101,6 +113,8 @@ impl ConnectionTrait for DatabaseConnection { DatabaseConnection::SqlxSqlitePoolConnection(_) => DbBackend::Sqlite, #[cfg(feature = "mock")] DatabaseConnection::MockDatabaseConnection(conn) => conn.get_database_backend(), + #[cfg(feature = "proxy")] + DatabaseConnection::ProxyDatabaseConnection(conn) => conn.get_database_backend(), DatabaseConnection::Disconnected => panic!("Disconnected"), } } @@ -117,6 +131,8 @@ impl ConnectionTrait for DatabaseConnection { DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.execute(stmt).await, #[cfg(feature = "mock")] DatabaseConnection::MockDatabaseConnection(conn) => conn.execute(stmt), + #[cfg(feature = "proxy")] + DatabaseConnection::ProxyDatabaseConnection(conn) => conn.execute(stmt), DatabaseConnection::Disconnected => Err(conn_err("Disconnected")), } } @@ -141,6 +157,12 @@ impl ConnectionTrait for DatabaseConnection { let stmt = Statement::from_string(db_backend, sql); conn.execute(stmt) } + #[cfg(feature = "proxy")] + DatabaseConnection::ProxyDatabaseConnection(conn) => { + let db_backend = conn.get_database_backend(); + let stmt = Statement::from_string(db_backend, sql); + conn.execute(stmt) + } DatabaseConnection::Disconnected => Err(conn_err("Disconnected")), } } @@ -157,6 +179,8 @@ impl ConnectionTrait for DatabaseConnection { DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.query_one(stmt).await, #[cfg(feature = "mock")] DatabaseConnection::MockDatabaseConnection(conn) => conn.query_one(stmt), + #[cfg(feature = "proxy")] + DatabaseConnection::ProxyDatabaseConnection(conn) => conn.query_one(stmt), DatabaseConnection::Disconnected => Err(conn_err("Disconnected")), } } @@ -173,6 +197,8 @@ impl ConnectionTrait for DatabaseConnection { DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.query_all(stmt).await, #[cfg(feature = "mock")] DatabaseConnection::MockDatabaseConnection(conn) => conn.query_all(stmt), + #[cfg(feature = "proxy")] + DatabaseConnection::ProxyDatabaseConnection(conn) => conn.query_all(stmt), DatabaseConnection::Disconnected => Err(conn_err("Disconnected")), } } @@ -205,6 +231,10 @@ impl StreamTrait for DatabaseConnection { DatabaseConnection::MockDatabaseConnection(conn) => { Ok(crate::QueryStream::from((Arc::clone(conn), stmt, None))) } + #[cfg(feature = "proxy")] + DatabaseConnection::ProxyDatabaseConnection(conn) => { + Ok(crate::QueryStream::from((conn.clone(), stmt, None))) + } DatabaseConnection::Disconnected => Err(conn_err("Disconnected")), } }) @@ -226,6 +256,10 @@ impl TransactionTrait for DatabaseConnection { DatabaseConnection::MockDatabaseConnection(conn) => { DatabaseTransaction::new_mock(Arc::clone(conn), None).await } + #[cfg(feature = "proxy")] + DatabaseConnection::ProxyDatabaseConnection(conn) => { + DatabaseTransaction::new_proxy(conn.clone(), None).await + } DatabaseConnection::Disconnected => Err(conn_err("Disconnected")), } } @@ -253,6 +287,10 @@ impl TransactionTrait for DatabaseConnection { DatabaseConnection::MockDatabaseConnection(conn) => { DatabaseTransaction::new_mock(Arc::clone(conn), None).await } + #[cfg(feature = "proxy")] + DatabaseConnection::ProxyDatabaseConnection(conn) => { + DatabaseTransaction::new_proxy(conn.clone(), None).await + } DatabaseConnection::Disconnected => Err(conn_err("Disconnected")), } } @@ -289,6 +327,13 @@ impl TransactionTrait for DatabaseConnection { .map_err(TransactionError::Connection)?; transaction.run(_callback).await } + #[cfg(feature = "proxy")] + DatabaseConnection::ProxyDatabaseConnection(conn) => { + let transaction = DatabaseTransaction::new_proxy(conn.clone(), None) + .await + .map_err(TransactionError::Connection)?; + transaction.run(_callback).await + } DatabaseConnection::Disconnected => Err(conn_err("Disconnected").into()), } } @@ -333,6 +378,13 @@ impl TransactionTrait for DatabaseConnection { .map_err(TransactionError::Connection)?; transaction.run(_callback).await } + #[cfg(feature = "proxy")] + DatabaseConnection::ProxyDatabaseConnection(conn) => { + let transaction = DatabaseTransaction::new_proxy(conn.clone(), None) + .await + .map_err(TransactionError::Connection)?; + transaction.run(_callback).await + } DatabaseConnection::Disconnected => Err(conn_err("Disconnected").into()), } } @@ -367,6 +419,21 @@ impl DatabaseConnection { } } +#[cfg(feature = "proxy")] +impl DatabaseConnection { + /// Generate a database connection for testing the Proxy database + /// + /// # Panics + /// + /// Panics if [DbConn] is not a proxy connection. + pub fn as_proxy_connection(&self) -> &crate::ProxyDatabaseConnection { + match self { + DatabaseConnection::ProxyDatabaseConnection(proxy_conn) => proxy_conn, + _ => panic!("Not proxy connection"), + } + } +} + impl DatabaseConnection { /// Sets a callback to metric this connection pub fn set_metric_callback(&mut self, _callback: F) @@ -401,6 +468,8 @@ impl DatabaseConnection { DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.ping().await, #[cfg(feature = "mock")] DatabaseConnection::MockDatabaseConnection(conn) => conn.ping(), + #[cfg(feature = "proxy")] + DatabaseConnection::ProxyDatabaseConnection(conn) => conn.ping(), DatabaseConnection::Disconnected => Err(conn_err("Disconnected")), } } @@ -419,6 +488,11 @@ impl DatabaseConnection { // Nothing to cleanup, we just consume the `DatabaseConnection` Ok(()) } + #[cfg(feature = "proxy")] + DatabaseConnection::ProxyDatabaseConnection(_) => { + // Nothing to cleanup, we just consume the `DatabaseConnection` + Ok(()) + }, DatabaseConnection::Disconnected => Err(conn_err("Disconnected")), } } diff --git a/src/database/mod.rs b/src/database/mod.rs index 36fe5709f..b47ac574d 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -5,6 +5,9 @@ mod db_connection; #[cfg(feature = "mock")] #[cfg_attr(docsrs, doc(cfg(feature = "mock")))] mod mock; +#[cfg(feature = "proxy")] +#[cfg_attr(docsrs, doc(cfg(feature = "proxy")))] +mod proxy; mod statement; mod stream; mod transaction; @@ -14,6 +17,9 @@ pub use db_connection::*; #[cfg(feature = "mock")] #[cfg_attr(docsrs, doc(cfg(feature = "mock")))] pub use mock::*; +#[cfg(feature = "proxy")] +#[cfg_attr(docsrs, doc(cfg(feature = "proxy")))] +pub use proxy::*; pub use statement::*; use std::borrow::Cow; pub use stream::*; @@ -79,6 +85,23 @@ impl Database { if crate::MockDatabaseConnector::accepts(&opt.url) { return crate::MockDatabaseConnector::connect(&opt.url).await; } + #[cfg(feature = "proxy")] + { + // Only match the prefix if the proxy feature is enabled + #[cfg(feature = "proxy-mysql")] + if DbBackend::MySql.is_prefix_of(&opt.url) { + return crate::ProxyDatabaseConnector::connect(DbBackend::MySql).await; + } + #[cfg(feature = "proxy-postgres")] + if DbBackend::Postgres.is_prefix_of(&opt.url) { + return crate::ProxyDatabaseConnector::connect(DbBackend::Postgres).await; + } + #[cfg(feature = "proxy-sqlite")] + if DbBackend::Sqlite.is_prefix_of(&opt.url) { + return crate::ProxyDatabaseConnector::connect(DbBackend::Sqlite).await; + } + } + Err(conn_err(format!( "The connection string '{}' has no supporting driver.", opt.url diff --git a/src/database/proxy.rs b/src/database/proxy.rs new file mode 100644 index 000000000..82e98dc8f --- /dev/null +++ b/src/database/proxy.rs @@ -0,0 +1,208 @@ +use crate::{ + error::*, DatabaseConnection, DbBackend, EntityTrait, ExecResult, Iden, IdenStatic, Iterable, + ModelTrait, OpenTransaction, ProxyDatabaseConnection, ProxyDatabaseTrait, QueryResult, SelectA, + SelectB, Statement, Transaction, +}; +use sea_query::{Value, ValueType}; +use std::{collections::BTreeMap, sync::Arc}; +use tracing::instrument; + +/// Defines a Proxy database suitable for testing +#[derive(Debug)] +pub struct ProxyDatabase { + db_backend: DbBackend, + transaction: Option, + transaction_log: Vec, +} + +/// Defines the results obtained from a [ProxyDatabase] +#[derive(Clone, Debug, Default)] +pub struct ProxyExecResult { + /// The last inserted id on auto-increment + pub last_insert_id: u64, + /// The number of rows affected by the database operation + pub rows_affected: u64, +} + +/// Defines the structure of a test Row for the [ProxyDatabase] +/// which is just a [BTreeMap]<[String], [Value]> +#[derive(Clone, Debug)] +pub struct ProxyRow { + values: BTreeMap, +} + +/// A trait to get a [ProxyRow] from a type useful for testing in the [ProxyDatabase] +pub trait IntoProxyRow { + /// The method to perform this operation + fn into_mock_row(self) -> ProxyRow; +} + +impl ProxyDatabase { + /// Instantiate a mock database with a [DbBackend] to simulate real + /// world SQL databases + pub fn new(db_backend: DbBackend) -> Self { + Self { + db_backend, + transaction: None, + transaction_log: Vec::new(), + } + } + + /// Create a database connection + pub fn into_connection(self) -> DatabaseConnection { + DatabaseConnection::ProxyDatabaseConnection(Arc::new(ProxyDatabaseConnection::new(self))) + } +} + +impl ProxyDatabaseTrait for ProxyDatabase { + #[instrument(level = "trace")] + fn execute(&mut self, statement: Statement) -> Result { + todo!("Not done yet"); + } + + #[instrument(level = "trace")] + fn query(&mut self, statement: Statement) -> Result, DbErr> { + todo!("Not done yet"); + } + + #[instrument(level = "trace")] + fn begin(&mut self) { + todo!("Not done yet"); + } + + #[instrument(level = "trace")] + fn commit(&mut self) { + todo!("Not done yet"); + } + + #[instrument(level = "trace")] + fn rollback(&mut self) { + todo!("Not done yet"); + } + + fn drain_transaction_log(&mut self) -> Vec { + std::mem::take(&mut self.transaction_log) + } + + fn get_database_backend(&self) -> DbBackend { + self.db_backend + } + + fn ping(&self) -> Result<(), DbErr> { + Ok(()) + } +} + +impl ProxyRow { + /// Get a value from the [ProxyRow] + pub fn try_get(&self, index: I) -> Result + where + T: ValueType, + { + if let Some(index) = index.as_str() { + T::try_from( + self.values + .get(index) + .ok_or_else(|| query_err(format!("No column for ColIdx {index:?}")))? + .clone(), + ) + .map_err(type_err) + } else if let Some(index) = index.as_usize() { + let (_, value) = self + .values + .iter() + .nth(*index) + .ok_or_else(|| query_err(format!("Column at index {index} not found")))?; + T::try_from(value.clone()).map_err(type_err) + } else { + unreachable!("Missing ColIdx implementation for ProxyRow"); + } + } + + /// An iterator over the keys and values of a mock row + pub fn into_column_value_tuples(self) -> impl Iterator { + self.values.into_iter() + } +} + +impl IntoProxyRow for ProxyRow { + fn into_mock_row(self) -> ProxyRow { + self + } +} + +impl IntoProxyRow for M +where + M: ModelTrait, +{ + fn into_mock_row(self) -> ProxyRow { + let mut values = BTreeMap::new(); + for col in <::Column>::iter() { + values.insert(col.to_string(), self.get(col)); + } + ProxyRow { values } + } +} + +impl IntoProxyRow for (M, N) +where + M: ModelTrait, + N: ModelTrait, +{ + fn into_mock_row(self) -> ProxyRow { + let mut mapped_join = BTreeMap::new(); + + for column in <::Entity as EntityTrait>::Column::iter() { + mapped_join.insert( + format!("{}{}", SelectA.as_str(), column.as_str()), + self.0.get(column), + ); + } + for column in <::Entity as EntityTrait>::Column::iter() { + mapped_join.insert( + format!("{}{}", SelectB.as_str(), column.as_str()), + self.1.get(column), + ); + } + + mapped_join.into_mock_row() + } +} + +impl IntoProxyRow for (M, Option) +where + M: ModelTrait, + N: ModelTrait, +{ + fn into_mock_row(self) -> ProxyRow { + let mut mapped_join = BTreeMap::new(); + + for column in <::Entity as EntityTrait>::Column::iter() { + mapped_join.insert( + format!("{}{}", SelectA.as_str(), column.as_str()), + self.0.get(column), + ); + } + if let Some(b_entity) = self.1 { + for column in <::Entity as EntityTrait>::Column::iter() { + mapped_join.insert( + format!("{}{}", SelectB.as_str(), column.as_str()), + b_entity.get(column), + ); + } + } + + mapped_join.into_mock_row() + } +} + +impl IntoProxyRow for BTreeMap +where + T: Into, +{ + fn into_mock_row(self) -> ProxyRow { + ProxyRow { + values: self.into_iter().map(|(k, v)| (k.into(), v)).collect(), + } + } +} diff --git a/src/database/stream/query.rs b/src/database/stream/query.rs index c38dc3b34..cb392a178 100644 --- a/src/database/stream/query.rs +++ b/src/database/stream/query.rs @@ -105,6 +105,25 @@ impl } } +#[cfg(feature = "proxy")] +impl + From<( + Arc, + Statement, + Option, + )> for QueryStream +{ + fn from( + (conn, stmt, metric_callback): ( + Arc, + Statement, + Option, + ), + ) -> Self { + QueryStream::build(stmt, InnerConnection::Proxy(conn), metric_callback) + } +} + impl std::fmt::Debug for QueryStream { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "QueryStream") diff --git a/src/database/transaction.rs b/src/database/transaction.rs index 14765b214..3216ffd99 100644 --- a/src/database/transaction.rs +++ b/src/database/transaction.rs @@ -95,6 +95,22 @@ impl DatabaseTransaction { .await } + #[cfg(feature = "proxy")] + pub(crate) async fn new_proxy( + inner: Arc, + metric_callback: Option, + ) -> Result { + let backend = inner.get_database_backend(); + Self::begin( + Arc::new(Mutex::new(InnerConnection::Proxy(inner))), + backend, + metric_callback, + None, + None, + ) + .await + } + #[instrument(level = "trace", skip(metric_callback))] async fn begin( conn: Arc>, diff --git a/src/driver/mod.rs b/src/driver/mod.rs index 33b6c847c..5e456e687 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -1,5 +1,7 @@ #[cfg(feature = "mock")] mod mock; +#[cfg(feature = "proxy")] +mod proxy; #[cfg(feature = "sqlx-dep")] mod sqlx_common; #[cfg(feature = "sqlx-mysql")] @@ -11,6 +13,8 @@ pub(crate) mod sqlx_sqlite; #[cfg(feature = "mock")] pub use mock::*; +#[cfg(feature = "proxy")] +pub use proxy::*; #[cfg(feature = "sqlx-dep")] pub use sqlx_common::*; #[cfg(feature = "sqlx-mysql")] diff --git a/src/driver/proxy.rs b/src/driver/proxy.rs new file mode 100644 index 000000000..369b9c804 --- /dev/null +++ b/src/driver/proxy.rs @@ -0,0 +1,152 @@ +use crate::{ + debug_print, error::*, DatabaseConnection, DbBackend, ExecResult, ProxyDatabase, QueryResult, + Statement, Transaction, +}; +use futures::Stream; +use std::{ + fmt::Debug, + pin::Pin, + sync::{Arc, Mutex}, +}; +use tracing::instrument; + +/// Defines a database driver for the [ProxyDatabase] +#[derive(Debug)] +pub struct ProxyDatabaseConnector; + +/// Defines a connection for the [ProxyDatabase] +#[derive(Debug)] +pub struct ProxyDatabaseConnection { + proxy: Mutex>, +} + +/// A Trait for any type wanting to perform operations on the [ProxyDatabase] +pub trait ProxyDatabaseTrait: Send + Debug { + /// Execute a statement in the [ProxyDatabase] + fn execute(&mut self, stmt: Statement) -> Result; + + /// Execute a SQL query in the [ProxyDatabase] + fn query(&mut self, stmt: Statement) -> Result, DbErr>; + + /// Create a transaction that can be committed atomically + fn begin(&mut self); + + /// Commit a successful transaction atomically into the [ProxyDatabase] + fn commit(&mut self); + + /// Roll back a transaction since errors were encountered + fn rollback(&mut self); + + /// Get all logs from a [ProxyDatabase] and return a [Transaction] + fn drain_transaction_log(&mut self) -> Vec; + + /// Get the backend being used in the [ProxyDatabase] + fn get_database_backend(&self) -> DbBackend; + + /// Ping the [ProxyDatabase] + fn ping(&self) -> Result<(), DbErr>; +} + +impl ProxyDatabaseConnector { + /// Check if the database URI given and the [DatabaseBackend](crate::DatabaseBackend) selected are the same + #[allow(unused_variables)] + pub fn accepts(string: &str) -> bool { + // As this is a proxy database, it accepts any URI + true + } + + /// Connect to the [ProxyDatabase] + #[allow(unused_variables)] + #[instrument(level = "trace")] + pub async fn connect(db_type: DbBackend) -> Result { + Ok(DatabaseConnection::ProxyDatabaseConnection(Arc::new( + ProxyDatabaseConnection::new(ProxyDatabase::new(db_type)), + ))) + } +} + +impl ProxyDatabaseConnection { + /// Create a connection to the [ProxyDatabase] + pub fn new(m: M) -> Self + where + M: ProxyDatabaseTrait, + { + Self { + proxy: Mutex::new(Box::new(m)), + } + } + + /// Get the [DatabaseBackend](crate::DatabaseBackend) being used by the [ProxyDatabase] + pub fn get_database_backend(&self) -> DbBackend { + self.proxy + .lock() + .expect("Fail to acquire mocker") + .get_database_backend() + } + + /// Execute the SQL statement in the [ProxyDatabase] + #[instrument(level = "trace")] + pub fn execute(&self, statement: Statement) -> Result { + debug_print!("{}", statement); + self.proxy.lock().map_err(exec_err)?.execute(statement) + } + + /// Return one [QueryResult] if the query was successful + #[instrument(level = "trace")] + pub fn query_one(&self, statement: Statement) -> Result, DbErr> { + debug_print!("{}", statement); + let result = self.proxy.lock().map_err(query_err)?.query(statement)?; + Ok(result.into_iter().next()) + } + + /// Return all [QueryResult]s if the query was successful + #[instrument(level = "trace")] + pub fn query_all(&self, statement: Statement) -> Result, DbErr> { + debug_print!("{}", statement); + self.proxy.lock().map_err(query_err)?.query(statement) + } + + /// Return [QueryResult]s from a multi-query operation + #[instrument(level = "trace")] + pub fn fetch( + &self, + statement: &Statement, + ) -> Pin> + Send>> { + match self.query_all(statement.clone()) { + Ok(v) => Box::pin(futures::stream::iter(v.into_iter().map(Ok))), + Err(e) => Box::pin(futures::stream::iter(Some(Err(e)).into_iter())), + } + } + + /// Create a statement block of SQL statements that execute together. + #[instrument(level = "trace")] + pub fn begin(&self) { + self.proxy + .lock() + .expect("Failed to acquire mocker") + .begin() + } + + /// Commit a transaction atomically to the database + #[instrument(level = "trace")] + pub fn commit(&self) { + self.proxy + .lock() + .expect("Failed to acquire mocker") + .commit() + } + + /// Roll back a faulty transaction + #[instrument(level = "trace")] + pub fn rollback(&self) { + self.proxy + .lock() + .expect("Failed to acquire mocker") + .rollback() + } + + /// Checks if a connection to the database is still valid. + pub fn ping(&self) -> Result<(), DbErr> { + self.proxy.lock().map_err(query_err)?.ping() + } +} From eda0bff5320e46c8a70e79222217233064eb5658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Wed, 27 Sep 2023 17:16:39 +0800 Subject: [PATCH 02/49] feat: Add proxy database's proxy functions trait. --- Cargo.toml | 5 +-- src/database/db_connection.rs | 8 ++--- src/database/mock.rs | 14 ++++----- src/database/mod.rs | 47 +++++++++++++++++++++-------- src/database/proxy.rs | 57 +++++++++++++++++++++++------------ src/database/stream/query.rs | 2 +- src/driver/proxy.rs | 19 +++++------- 7 files changed, 92 insertions(+), 60 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3b2be85ed..6466d035d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,10 +76,7 @@ default = [ ] macros = ["sea-orm-macros/derive"] mock = [] -proxy = ['proxy-mysql', 'proxy-postgres', 'proxy-sqlite'] -proxy-mysql = [] -proxy-postgres = [] -proxy-sqlite = [] +proxy = [] with-json = ["serde_json", "sea-query/with-json", "chrono?/serde", "time?/serde", "uuid?/serde", "sea-query-binder?/with-json", "sqlx?/json"] with-chrono = ["chrono", "sea-query/with-chrono", "sea-query-binder?/with-chrono", "sqlx?/chrono"] with-rust_decimal = ["rust_decimal", "sea-query/with-rust_decimal", "sea-query-binder?/with-rust_decimal", "sqlx?/rust_decimal"] diff --git a/src/database/db_connection.rs b/src/database/db_connection.rs index a88c286fe..29815ec9f 100644 --- a/src/database/db_connection.rs +++ b/src/database/db_connection.rs @@ -33,8 +33,8 @@ pub enum DatabaseConnection { #[cfg(feature = "mock")] MockDatabaseConnection(Arc), - // Create a Proxy database connection useful for proxying - #[cfg(any(feature = "proxy-mysql", feature = "proxy-postgres", feature = "proxy-sqlite"))] + /// Create a Proxy database connection useful for proxying + #[cfg(feature = "proxy")] ProxyDatabaseConnection(Arc), /// The connection to the database has been severed @@ -233,7 +233,7 @@ impl StreamTrait for DatabaseConnection { } #[cfg(feature = "proxy")] DatabaseConnection::ProxyDatabaseConnection(conn) => { - Ok(crate::QueryStream::from((conn.clone(), stmt, None))) + Ok(crate::QueryStream::from((Arc::clone(conn), stmt, None))) } DatabaseConnection::Disconnected => Err(conn_err("Disconnected")), } @@ -492,7 +492,7 @@ impl DatabaseConnection { DatabaseConnection::ProxyDatabaseConnection(_) => { // Nothing to cleanup, we just consume the `DatabaseConnection` Ok(()) - }, + } DatabaseConnection::Disconnected => Err(conn_err("Disconnected")), } } diff --git a/src/database/mock.rs b/src/database/mock.rs index e760ceaf7..fb17d5c0d 100644 --- a/src/database/mock.rs +++ b/src/database/mock.rs @@ -40,7 +40,7 @@ pub trait IntoMockRow { } /// Defines a transaction that is has not been committed -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq)] pub struct OpenTransaction { stmts: Vec, transaction_depth: usize, @@ -361,14 +361,14 @@ impl Transaction { } impl OpenTransaction { - fn init() -> Self { + pub(crate) fn init() -> Self { Self { stmts: vec![Statement::from_string(DbBackend::Postgres, "BEGIN")], transaction_depth: 0, } } - fn begin_nested(&mut self, db_backend: DbBackend) { + pub(crate) fn begin_nested(&mut self, db_backend: DbBackend) { self.transaction_depth += 1; self.push(Statement::from_string( db_backend, @@ -376,7 +376,7 @@ impl OpenTransaction { )); } - fn commit(&mut self, db_backend: DbBackend) -> bool { + pub(crate) fn commit(&mut self, db_backend: DbBackend) -> bool { if self.transaction_depth == 0 { self.push(Statement::from_string(db_backend, "COMMIT")); true @@ -390,7 +390,7 @@ impl OpenTransaction { } } - fn rollback(&mut self, db_backend: DbBackend) -> bool { + pub(crate) fn rollback(&mut self, db_backend: DbBackend) -> bool { if self.transaction_depth == 0 { self.push(Statement::from_string(db_backend, "ROLLBACK")); true @@ -404,11 +404,11 @@ impl OpenTransaction { } } - fn push(&mut self, stmt: Statement) { + pub(crate) fn push(&mut self, stmt: Statement) { self.stmts.push(stmt); } - fn into_transaction(self) -> Transaction { + pub(crate) fn into_transaction(self) -> Transaction { match self.transaction_depth { 0 => Transaction { stmts: self.stmts }, _ => panic!("There is uncommitted nested transaction"), diff --git a/src/database/mod.rs b/src/database/mod.rs index b47ac574d..8bb163cad 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -58,6 +58,13 @@ pub struct ConnectOptions { pub(crate) sqlcipher_key: Option>, /// Schema search path (PostgreSQL only) pub(crate) schema_search_path: Option, + + /// Proxy type for proxy connections, which MySql is the default value (Proxy only) + #[cfg(feature = "proxy")] + pub(crate) proxy_type: Option, + /// Proxy functions for proxy connections (Proxy only) + #[cfg(feature = "proxy")] + pub(crate) proxy_func: Option>, } impl Database { @@ -86,19 +93,28 @@ impl Database { return crate::MockDatabaseConnector::connect(&opt.url).await; } #[cfg(feature = "proxy")] - { - // Only match the prefix if the proxy feature is enabled - #[cfg(feature = "proxy-mysql")] - if DbBackend::MySql.is_prefix_of(&opt.url) { - return crate::ProxyDatabaseConnector::connect(DbBackend::MySql).await; - } - #[cfg(feature = "proxy-postgres")] - if DbBackend::Postgres.is_prefix_of(&opt.url) { - return crate::ProxyDatabaseConnector::connect(DbBackend::Postgres).await; - } - #[cfg(feature = "proxy-sqlite")] - if DbBackend::Sqlite.is_prefix_of(&opt.url) { - return crate::ProxyDatabaseConnector::connect(DbBackend::Sqlite).await; + if let Some(proxy_func_arc) = &opt.proxy_func { + if let Some(proxy_type) = &opt.proxy_type { + match proxy_type { + DbBackend::MySql => { + return crate::ProxyDatabaseConnector::connect( + DbBackend::MySql, + proxy_func_arc.to_owned(), + ); + } + DbBackend::Postgres => { + return crate::ProxyDatabaseConnector::connect( + DbBackend::Postgres, + proxy_func_arc.to_owned(), + ); + } + DbBackend::Sqlite => { + return crate::ProxyDatabaseConnector::connect( + DbBackend::Sqlite, + proxy_func_arc.to_owned(), + ); + } + } } } @@ -136,6 +152,11 @@ impl ConnectOptions { sqlx_logging_level: log::LevelFilter::Info, sqlcipher_key: None, schema_search_path: None, + + #[cfg(feature = "proxy")] + proxy_type: Some(DbBackend::MySql), + #[cfg(feature = "proxy")] + proxy_func: None, } } diff --git a/src/database/proxy.rs b/src/database/proxy.rs index 82e98dc8f..c5e43ae7c 100644 --- a/src/database/proxy.rs +++ b/src/database/proxy.rs @@ -1,18 +1,41 @@ use crate::{ error::*, DatabaseConnection, DbBackend, EntityTrait, ExecResult, Iden, IdenStatic, Iterable, - ModelTrait, OpenTransaction, ProxyDatabaseConnection, ProxyDatabaseTrait, QueryResult, SelectA, - SelectB, Statement, Transaction, + ModelTrait, ProxyDatabaseConnection, ProxyDatabaseTrait, QueryResult, SelectA, SelectB, + Statement, }; + use sea_query::{Value, ValueType}; use std::{collections::BTreeMap, sync::Arc}; use tracing::instrument; +#[cfg(feature = "proxy")] +/// Defines the [ProxyDatabaseFuncTrait] to save the functions +pub trait ProxyDatabaseFuncTrait: Send + Sync + std::fmt::Debug { + /// Execute a query in the [ProxyDatabase], and return the query results + fn query(&self, statement: Statement) -> Result, DbErr>; + + /// Execute a command in the [ProxyDatabase], and report the number of rows affected + fn execute(&self, statement: Statement) -> Result; + + /// Begin a transaction in the [ProxyDatabase] + fn begin(&self); + + /// Commit a transaction in the [ProxyDatabase] + fn commit(&self); + + /// Rollback a transaction in the [ProxyDatabase] + fn rollback(&self); + + /// Ping the [ProxyDatabase], it should return an error if the database is not available + fn ping(&self) -> Result<(), DbErr>; +} + +#[cfg(feature = "proxy")] /// Defines a Proxy database suitable for testing -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ProxyDatabase { db_backend: DbBackend, - transaction: Option, - transaction_log: Vec, + proxy_func: Arc, } /// Defines the results obtained from a [ProxyDatabase] @@ -38,13 +61,11 @@ pub trait IntoProxyRow { } impl ProxyDatabase { - /// Instantiate a mock database with a [DbBackend] to simulate real - /// world SQL databases - pub fn new(db_backend: DbBackend) -> Self { + /// Instantiate a proxy database with a [DbBackend] and the [ProxyDatabaseFuncTrait] + pub fn new(db_backend: DbBackend, func: Arc) -> Self { Self { db_backend, - transaction: None, - transaction_log: Vec::new(), + proxy_func: func.to_owned(), } } @@ -57,31 +78,27 @@ impl ProxyDatabase { impl ProxyDatabaseTrait for ProxyDatabase { #[instrument(level = "trace")] fn execute(&mut self, statement: Statement) -> Result { - todo!("Not done yet"); + self.proxy_func.execute(statement) } #[instrument(level = "trace")] fn query(&mut self, statement: Statement) -> Result, DbErr> { - todo!("Not done yet"); + self.proxy_func.query(statement) } #[instrument(level = "trace")] fn begin(&mut self) { - todo!("Not done yet"); + self.proxy_func.begin() } #[instrument(level = "trace")] fn commit(&mut self) { - todo!("Not done yet"); + self.proxy_func.commit() } #[instrument(level = "trace")] fn rollback(&mut self) { - todo!("Not done yet"); - } - - fn drain_transaction_log(&mut self) -> Vec { - std::mem::take(&mut self.transaction_log) + self.proxy_func.rollback() } fn get_database_backend(&self) -> DbBackend { @@ -89,7 +106,7 @@ impl ProxyDatabaseTrait for ProxyDatabase { } fn ping(&self) -> Result<(), DbErr> { - Ok(()) + self.proxy_func.ping() } } diff --git a/src/database/stream/query.rs b/src/database/stream/query.rs index cb392a178..97da4bca4 100644 --- a/src/database/stream/query.rs +++ b/src/database/stream/query.rs @@ -2,7 +2,7 @@ use tracing::instrument; -#[cfg(feature = "mock")] +#[cfg(any(feature = "mock", feature = "proxy"))] use std::sync::Arc; use std::{pin::Pin, task::Poll}; diff --git a/src/driver/proxy.rs b/src/driver/proxy.rs index 369b9c804..c172f78e9 100644 --- a/src/driver/proxy.rs +++ b/src/driver/proxy.rs @@ -1,6 +1,6 @@ use crate::{ - debug_print, error::*, DatabaseConnection, DbBackend, ExecResult, ProxyDatabase, QueryResult, - Statement, Transaction, + debug_print, error::*, DatabaseConnection, DbBackend, ExecResult, ProxyDatabase, + ProxyDatabaseFuncTrait, QueryResult, Statement, }; use futures::Stream; use std::{ @@ -37,9 +37,6 @@ pub trait ProxyDatabaseTrait: Send + Debug { /// Roll back a transaction since errors were encountered fn rollback(&mut self); - /// Get all logs from a [ProxyDatabase] and return a [Transaction] - fn drain_transaction_log(&mut self) -> Vec; - /// Get the backend being used in the [ProxyDatabase] fn get_database_backend(&self) -> DbBackend; @@ -58,9 +55,12 @@ impl ProxyDatabaseConnector { /// Connect to the [ProxyDatabase] #[allow(unused_variables)] #[instrument(level = "trace")] - pub async fn connect(db_type: DbBackend) -> Result { + pub fn connect( + db_type: DbBackend, + func: Arc, + ) -> Result { Ok(DatabaseConnection::ProxyDatabaseConnection(Arc::new( - ProxyDatabaseConnection::new(ProxyDatabase::new(db_type)), + ProxyDatabaseConnection::new(ProxyDatabase::new(db_type, func)), ))) } } @@ -121,10 +121,7 @@ impl ProxyDatabaseConnection { /// Create a statement block of SQL statements that execute together. #[instrument(level = "trace")] pub fn begin(&self) { - self.proxy - .lock() - .expect("Failed to acquire mocker") - .begin() + self.proxy.lock().expect("Failed to acquire mocker").begin() } /// Commit a transaction atomically to the database From 88f4177f5e4779f8e825eec2bc18ab14cf0fa0fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Thu, 28 Sep 2023 09:32:14 +0800 Subject: [PATCH 03/49] fix: Remove some unused impl to fix the unit test --- src/database/mock.rs | 14 +++---- src/database/proxy.rs | 88 ------------------------------------------- 2 files changed, 7 insertions(+), 95 deletions(-) diff --git a/src/database/mock.rs b/src/database/mock.rs index fb17d5c0d..e760ceaf7 100644 --- a/src/database/mock.rs +++ b/src/database/mock.rs @@ -40,7 +40,7 @@ pub trait IntoMockRow { } /// Defines a transaction that is has not been committed -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug)] pub struct OpenTransaction { stmts: Vec, transaction_depth: usize, @@ -361,14 +361,14 @@ impl Transaction { } impl OpenTransaction { - pub(crate) fn init() -> Self { + fn init() -> Self { Self { stmts: vec![Statement::from_string(DbBackend::Postgres, "BEGIN")], transaction_depth: 0, } } - pub(crate) fn begin_nested(&mut self, db_backend: DbBackend) { + fn begin_nested(&mut self, db_backend: DbBackend) { self.transaction_depth += 1; self.push(Statement::from_string( db_backend, @@ -376,7 +376,7 @@ impl OpenTransaction { )); } - pub(crate) fn commit(&mut self, db_backend: DbBackend) -> bool { + fn commit(&mut self, db_backend: DbBackend) -> bool { if self.transaction_depth == 0 { self.push(Statement::from_string(db_backend, "COMMIT")); true @@ -390,7 +390,7 @@ impl OpenTransaction { } } - pub(crate) fn rollback(&mut self, db_backend: DbBackend) -> bool { + fn rollback(&mut self, db_backend: DbBackend) -> bool { if self.transaction_depth == 0 { self.push(Statement::from_string(db_backend, "ROLLBACK")); true @@ -404,11 +404,11 @@ impl OpenTransaction { } } - pub(crate) fn push(&mut self, stmt: Statement) { + fn push(&mut self, stmt: Statement) { self.stmts.push(stmt); } - pub(crate) fn into_transaction(self) -> Transaction { + fn into_transaction(self) -> Transaction { match self.transaction_depth { 0 => Transaction { stmts: self.stmts }, _ => panic!("There is uncommitted nested transaction"), diff --git a/src/database/proxy.rs b/src/database/proxy.rs index c5e43ae7c..01db02e27 100644 --- a/src/database/proxy.rs +++ b/src/database/proxy.rs @@ -54,12 +54,6 @@ pub struct ProxyRow { values: BTreeMap, } -/// A trait to get a [ProxyRow] from a type useful for testing in the [ProxyDatabase] -pub trait IntoProxyRow { - /// The method to perform this operation - fn into_mock_row(self) -> ProxyRow; -} - impl ProxyDatabase { /// Instantiate a proxy database with a [DbBackend] and the [ProxyDatabaseFuncTrait] pub fn new(db_backend: DbBackend, func: Arc) -> Self { @@ -141,85 +135,3 @@ impl ProxyRow { self.values.into_iter() } } - -impl IntoProxyRow for ProxyRow { - fn into_mock_row(self) -> ProxyRow { - self - } -} - -impl IntoProxyRow for M -where - M: ModelTrait, -{ - fn into_mock_row(self) -> ProxyRow { - let mut values = BTreeMap::new(); - for col in <::Column>::iter() { - values.insert(col.to_string(), self.get(col)); - } - ProxyRow { values } - } -} - -impl IntoProxyRow for (M, N) -where - M: ModelTrait, - N: ModelTrait, -{ - fn into_mock_row(self) -> ProxyRow { - let mut mapped_join = BTreeMap::new(); - - for column in <::Entity as EntityTrait>::Column::iter() { - mapped_join.insert( - format!("{}{}", SelectA.as_str(), column.as_str()), - self.0.get(column), - ); - } - for column in <::Entity as EntityTrait>::Column::iter() { - mapped_join.insert( - format!("{}{}", SelectB.as_str(), column.as_str()), - self.1.get(column), - ); - } - - mapped_join.into_mock_row() - } -} - -impl IntoProxyRow for (M, Option) -where - M: ModelTrait, - N: ModelTrait, -{ - fn into_mock_row(self) -> ProxyRow { - let mut mapped_join = BTreeMap::new(); - - for column in <::Entity as EntityTrait>::Column::iter() { - mapped_join.insert( - format!("{}{}", SelectA.as_str(), column.as_str()), - self.0.get(column), - ); - } - if let Some(b_entity) = self.1 { - for column in <::Entity as EntityTrait>::Column::iter() { - mapped_join.insert( - format!("{}{}", SelectB.as_str(), column.as_str()), - b_entity.get(column), - ); - } - } - - mapped_join.into_mock_row() - } -} - -impl IntoProxyRow for BTreeMap -where - T: Into, -{ - fn into_mock_row(self) -> ProxyRow { - ProxyRow { - values: self.into_iter().map(|(k, v)| (k.into(), v)).collect(), - } - } -} From 0ff195239060bf1299316079f37592187ac23878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Thu, 28 Sep 2023 10:36:11 +0800 Subject: [PATCH 04/49] test: Create the proxy by empty declaration. --- src/database/proxy.rs | 86 ++++++++++++++++++++++++++++++++++------- src/executor/execute.rs | 3 ++ 2 files changed, 76 insertions(+), 13 deletions(-) diff --git a/src/database/proxy.rs b/src/database/proxy.rs index 01db02e27..f846c3f18 100644 --- a/src/database/proxy.rs +++ b/src/database/proxy.rs @@ -1,7 +1,6 @@ use crate::{ - error::*, DatabaseConnection, DbBackend, EntityTrait, ExecResult, Iden, IdenStatic, Iterable, - ModelTrait, ProxyDatabaseConnection, ProxyDatabaseTrait, QueryResult, SelectA, SelectB, - Statement, + error::*, DatabaseConnection, DbBackend, ExecResult, ExecResultHolder, ProxyDatabaseConnection, + ProxyDatabaseTrait, QueryResult, Statement, }; use sea_query::{Value, ValueType}; @@ -30,14 +29,6 @@ pub trait ProxyDatabaseFuncTrait: Send + Sync + std::fmt::Debug { fn ping(&self) -> Result<(), DbErr>; } -#[cfg(feature = "proxy")] -/// Defines a Proxy database suitable for testing -#[derive(Debug, Clone)] -pub struct ProxyDatabase { - db_backend: DbBackend, - proxy_func: Arc, -} - /// Defines the results obtained from a [ProxyDatabase] #[derive(Clone, Debug, Default)] pub struct ProxyExecResult { @@ -54,6 +45,14 @@ pub struct ProxyRow { values: BTreeMap, } +#[cfg(feature = "proxy")] +/// Defines a Proxy database suitable for testing +#[derive(Debug, Clone)] +pub struct ProxyDatabase { + db_backend: DbBackend, + proxy_func: Arc, +} + impl ProxyDatabase { /// Instantiate a proxy database with a [DbBackend] and the [ProxyDatabaseFuncTrait] pub fn new(db_backend: DbBackend, func: Arc) -> Self { @@ -72,7 +71,15 @@ impl ProxyDatabase { impl ProxyDatabaseTrait for ProxyDatabase { #[instrument(level = "trace")] fn execute(&mut self, statement: Statement) -> Result { - self.proxy_func.execute(statement) + match self.proxy_func.execute(statement) { + Ok(result) => Ok(ExecResult { + result: ExecResultHolder::Proxy(ProxyExecResult { + last_insert_id: result.last_insert_id(), + rows_affected: result.rows_affected(), + }), + }), + Err(err) => Err(err), + } } #[instrument(level = "trace")] @@ -130,8 +137,61 @@ impl ProxyRow { } } - /// An iterator over the keys and values of a mock row + /// An iterator over the keys and values of a proxy row pub fn into_column_value_tuples(self) -> impl Iterator { self.values.into_iter() } } + +#[cfg(test)] +#[cfg(feature = "proxy")] +mod tests { + use crate::{ + entity::*, tests_cfg::*, DbBackend, DbErr, ExecResult, ProxyDatabase, + ProxyDatabaseFuncTrait, QueryResult, Statement, + }; + use pretty_assertions::assert_eq; + use std::sync::Arc; + + #[derive(Debug)] + struct EmptyProxyFunc {} + + impl ProxyDatabaseFuncTrait for EmptyProxyFunc { + fn query(&self, statement: Statement) -> Result, DbErr> { + Ok(vec![]) + } + + fn execute(&self, statement: Statement) -> Result { + Ok(ExecResult { + result: crate::ExecResultHolder::Proxy(crate::ProxyExecResult { + last_insert_id: 0, + rows_affected: 0, + }), + }) + } + + fn begin(&self) {} + + fn commit(&self) {} + + fn rollback(&self) {} + + fn ping(&self) -> Result<(), DbErr> { + Ok(()) + } + } + + use once_cell::sync::Lazy; + + static EMPTY_DB_FUNC: Lazy> = Lazy::new(|| Arc::new(EmptyProxyFunc {})); + + #[smol_potat::test] + async fn test_empty_oper() { + let db = + ProxyDatabase::new(DbBackend::MySql, (*EMPTY_DB_FUNC).to_owned()).into_connection(); + + let _ = cake::Entity::find().all(&db).await; + + assert_eq!("1", "1"); + } +} diff --git a/src/executor/execute.rs b/src/executor/execute.rs index 487ca41aa..2b869b31d 100644 --- a/src/executor/execute.rs +++ b/src/executor/execute.rs @@ -22,6 +22,9 @@ pub(crate) enum ExecResultHolder { /// Holds the result of executing an operation on the Mock database #[cfg(feature = "mock")] Mock(crate::MockExecResult), + /// Holds the result of executing an operation on the Proxy database + #[cfg(feature = "proxy")] + Proxy(crate::ProxyExecResult), } // ExecResult // From 5e2e3fa0b0be8d79443101f05eac61004e8c680c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Thu, 28 Sep 2023 11:15:44 +0800 Subject: [PATCH 05/49] test: Try to genereate query and exec commands. --- src/database/proxy.rs | 12 ++++++++++-- src/executor/execute.rs | 4 ++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/database/proxy.rs b/src/database/proxy.rs index f846c3f18..9141d2202 100644 --- a/src/database/proxy.rs +++ b/src/database/proxy.rs @@ -158,14 +158,16 @@ mod tests { impl ProxyDatabaseFuncTrait for EmptyProxyFunc { fn query(&self, statement: Statement) -> Result, DbErr> { + println!("query: {:?}", statement); Ok(vec![]) } fn execute(&self, statement: Statement) -> Result { + println!("execute: {:?}", statement); Ok(ExecResult { result: crate::ExecResultHolder::Proxy(crate::ProxyExecResult { - last_insert_id: 0, - rows_affected: 0, + last_insert_id: 1, + rows_affected: 1, }), }) } @@ -192,6 +194,12 @@ mod tests { let _ = cake::Entity::find().all(&db).await; + let item = cake::ActiveModel { + id: NotSet, + name: Set("Alice".to_string()), + }; + cake::Entity::insert(item).exec(&db).await.unwrap(); + assert_eq!("1", "1"); } } diff --git a/src/executor/execute.rs b/src/executor/execute.rs index 2b869b31d..626dd81b8 100644 --- a/src/executor/execute.rs +++ b/src/executor/execute.rs @@ -54,6 +54,8 @@ impl ExecResult { } #[cfg(feature = "mock")] ExecResultHolder::Mock(result) => result.last_insert_id, + #[cfg(feature = "proxy")] + ExecResultHolder::Proxy(result) => result.last_insert_id, #[allow(unreachable_patterns)] _ => unreachable!(), } @@ -70,6 +72,8 @@ impl ExecResult { ExecResultHolder::SqlxSqlite(result) => result.rows_affected(), #[cfg(feature = "mock")] ExecResultHolder::Mock(result) => result.rows_affected, + #[cfg(feature = "proxy")] + ExecResultHolder::Proxy(result) => result.rows_affected, #[allow(unreachable_patterns)] _ => unreachable!(), } From ef357ffc8ba5d81e710f60ae8e20b82bd830ecb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Thu, 28 Sep 2023 12:01:26 +0800 Subject: [PATCH 06/49] perf: Add more query debug trait for debugging. --- src/database/stream/query.rs | 7 +++ src/database/stream/transaction.rs | 7 +++ src/executor/query.rs | 75 ++++++++++++++++++++++++++++++ src/query/json.rs | 12 +++++ 4 files changed, 101 insertions(+) diff --git a/src/database/stream/query.rs b/src/database/stream/query.rs index 97da4bca4..30642bde0 100644 --- a/src/database/stream/query.rs +++ b/src/database/stream/query.rs @@ -182,6 +182,13 @@ impl QueryStream { let elapsed = _start.map(|s| s.elapsed().unwrap_or_default()); MetricStream::new(_metric_callback, stmt, elapsed, stream) } + #[cfg(feature = "proxy")] + InnerConnection::Proxy(c) => { + let _start = _metric_callback.is_some().then(std::time::SystemTime::now); + let stream = c.fetch(stmt); + let elapsed = _start.map(|s| s.elapsed().unwrap_or_default()); + MetricStream::new(_metric_callback, stmt, elapsed, stream) + } #[allow(unreachable_patterns)] _ => unreachable!(), }, diff --git a/src/database/stream/transaction.rs b/src/database/stream/transaction.rs index d64feaba4..3e9285d15 100644 --- a/src/database/stream/transaction.rs +++ b/src/database/stream/transaction.rs @@ -86,6 +86,13 @@ impl<'a> TransactionStream<'a> { let elapsed = _start.map(|s| s.elapsed().unwrap_or_default()); MetricStream::new(_metric_callback, stmt, elapsed, stream) } + #[cfg(feature = "proxy")] + InnerConnection::Proxy(c) => { + let _start = _metric_callback.is_some().then(std::time::SystemTime::now); + let stream = c.fetch(stmt); + let elapsed = _start.map(|s| s.elapsed().unwrap_or_default()); + MetricStream::new(_metric_callback, stmt, elapsed, stream) + } #[allow(unreachable_patterns)] _ => unreachable!(), }, diff --git a/src/executor/query.rs b/src/executor/query.rs index 28093822b..5bb8f4564 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -25,6 +25,8 @@ pub(crate) enum QueryResultRow { SqlxSqlite(sqlx::sqlite::SqliteRow), #[cfg(feature = "mock")] Mock(crate::MockRow), + #[cfg(feature = "proxy")] + Proxy(crate::ProxyRow), } /// An interface to get a value from the query result @@ -127,6 +129,8 @@ impl fmt::Debug for QueryResultRow { Self::SqlxSqlite(_) => write!(f, "QueryResultRow::SqlxSqlite cannot be inspected"), #[cfg(feature = "mock")] Self::Mock(row) => write!(f, "{row:?}"), + #[cfg(feature = "proxy")] + Self::Proxy(row) => write!(f, "{row:?}"), #[allow(unreachable_patterns)] _ => unreachable!(), } @@ -271,6 +275,11 @@ macro_rules! try_getable_all { debug_print!("{:#?}", e.to_string()); err_null_idx_col(idx) }), + #[cfg(feature = "proxy")] + QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| { + debug_print!("{:#?}", e.to_string()); + err_null_idx_col(idx) + }), #[allow(unreachable_patterns)] _ => unreachable!(), } @@ -306,6 +315,11 @@ macro_rules! try_getable_unsigned { debug_print!("{:#?}", e.to_string()); err_null_idx_col(idx) }), + #[cfg(feature = "proxy")] + QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| { + debug_print!("{:#?}", e.to_string()); + err_null_idx_col(idx) + }), #[allow(unreachable_patterns)] _ => unreachable!(), } @@ -342,6 +356,11 @@ macro_rules! try_getable_mysql { debug_print!("{:#?}", e.to_string()); err_null_idx_col(idx) }), + #[cfg(feature = "proxy")] + QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| { + debug_print!("{:#?}", e.to_string()); + err_null_idx_col(idx) + }), #[allow(unreachable_patterns)] _ => unreachable!(), } @@ -383,6 +402,11 @@ macro_rules! try_getable_date_time { debug_print!("{:#?}", e.to_string()); err_null_idx_col(idx) }), + #[cfg(feature = "proxy")] + QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| { + debug_print!("{:#?}", e.to_string()); + err_null_idx_col(idx) + }), #[allow(unreachable_patterns)] _ => unreachable!(), } @@ -478,6 +502,12 @@ impl TryGetable for Decimal { debug_print!("{:#?}", e.to_string()); err_null_idx_col(idx) }), + #[cfg(feature = "proxy")] + #[allow(unused_variables)] + QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| { + debug_print!("{:#?}", e.to_string()); + err_null_idx_col(idx) + }), #[allow(unreachable_patterns)] _ => unreachable!(), } @@ -525,6 +555,12 @@ impl TryGetable for BigDecimal { debug_print!("{:#?}", e.to_string()); err_null_idx_col(idx) }), + #[cfg(feature = "proxy")] + #[allow(unused_variables)] + QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| { + debug_print!("{:#?}", e.to_string()); + err_null_idx_col(idx) + }), #[allow(unreachable_patterns)] _ => unreachable!(), } @@ -559,6 +595,12 @@ macro_rules! try_getable_uuid { debug_print!("{:#?}", e.to_string()); err_null_idx_col(idx) }), + #[cfg(feature = "proxy")] + #[allow(unused_variables)] + QueryResultRow::Proxy(row) => row.try_get::(idx).map_err(|e| { + debug_print!("{:#?}", e.to_string()); + err_null_idx_col(idx) + }), #[allow(unreachable_patterns)] _ => unreachable!(), }; @@ -613,6 +655,12 @@ impl TryGetable for u32 { debug_print!("{:#?}", e.to_string()); err_null_idx_col(idx) }), + #[cfg(feature = "proxy")] + #[allow(unused_variables)] + QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| { + debug_print!("{:#?}", e.to_string()); + err_null_idx_col(idx) + }), #[allow(unreachable_patterns)] _ => unreachable!(), } @@ -658,6 +706,12 @@ mod postgres_array { debug_print!("{:#?}", e.to_string()); err_null_idx_col(idx) }), + #[cfg(feature = "proxy")] + #[allow(unused_variables)] + QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| { + debug_print!("{:#?}", e.to_string()); + err_null_idx_col(idx) + }), #[allow(unreachable_patterns)] _ => unreachable!(), } @@ -745,6 +799,13 @@ mod postgres_array { err_null_idx_col(idx) }) } + #[cfg(feature = "proxy")] + QueryResultRow::Proxy(row) => { + row.try_get::, _>(idx).map_err(|e| { + debug_print!("{:#?}", e.to_string()); + err_null_idx_col(idx) + }) + } #[allow(unreachable_patterns)] _ => unreachable!(), }; @@ -799,6 +860,12 @@ mod postgres_array { debug_print!("{:#?}", e.to_string()); err_null_idx_col(idx) }), + #[cfg(feature = "proxy")] + #[allow(unused_variables)] + QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| { + debug_print!("{:#?}", e.to_string()); + err_null_idx_col(idx) + }), #[allow(unreachable_patterns)] _ => unreachable!(), } @@ -995,6 +1062,14 @@ where err_null_idx_col(idx) }) .and_then(|json| serde_json::from_value(json).map_err(|e| json_err(e).into())), + #[cfg(feature = "proxy")] + QueryResultRow::Proxy(row) => row + .try_get::(idx) + .map_err(|e| { + debug_print!("{:#?}", e.to_string()); + err_null_idx_col(idx) + }) + .and_then(|json| serde_json::from_value(json).map_err(|e| json_err(e).into())), #[allow(unreachable_patterns)] _ => unreachable!(), } diff --git a/src/query/json.rs b/src/query/json.rs index 66b4dc978..da91fb3b6 100644 --- a/src/query/json.rs +++ b/src/query/json.rs @@ -214,6 +214,18 @@ impl FromQueryResult for JsonValue { } Ok(JsonValue::Object(map)) } + #[cfg(feature = "proxy")] + crate::QueryResultRow::Proxy(row) => { + for (column, value) in row.clone().into_column_value_tuples() { + let col = if !column.starts_with(pre) { + continue; + } else { + column.replacen(pre, "", 1) + }; + map.insert(col, sea_query::sea_value_to_json_value(&value)); + } + Ok(JsonValue::Object(map)) + } #[allow(unreachable_patterns)] _ => unreachable!(), } From 0d67fe2dbdb97e1a6f58ad3876afab62c96f227e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Thu, 5 Oct 2023 02:30:10 +0800 Subject: [PATCH 07/49] chore: Add the example for wasi + proxy. --- examples/wasi_example/Cargo.toml | 3 +++ examples/wasi_example/README.md | 15 ++++++++++++ examples/wasi_example/api/Cargo.toml | 7 ++++++ examples/wasi_example/api/src/main.rs | 15 ++++++++++++ examples/wasi_example/vm/Cargo.toml | 12 ++++++++++ examples/wasi_example/vm/src/main.rs | 33 +++++++++++++++++++++++++++ 6 files changed, 85 insertions(+) create mode 100644 examples/wasi_example/Cargo.toml create mode 100644 examples/wasi_example/README.md create mode 100644 examples/wasi_example/api/Cargo.toml create mode 100644 examples/wasi_example/api/src/main.rs create mode 100644 examples/wasi_example/vm/Cargo.toml create mode 100644 examples/wasi_example/vm/src/main.rs diff --git a/examples/wasi_example/Cargo.toml b/examples/wasi_example/Cargo.toml new file mode 100644 index 000000000..6f3c58b0c --- /dev/null +++ b/examples/wasi_example/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +members = ["api", "vm"] +resolver = "2" diff --git a/examples/wasi_example/README.md b/examples/wasi_example/README.md new file mode 100644 index 000000000..78a1ef110 --- /dev/null +++ b/examples/wasi_example/README.md @@ -0,0 +1,15 @@ +# SeaORM WASI + Proxy example + +Simple implementation of WASI and proxy connections using Wasmtime and SeaORM. + +First build the client wasm using + +```bash +cargo build --target wasm32-wasi --package api +``` + +Then run server using + +```bash +cargo run --package vm +``` diff --git a/examples/wasi_example/api/Cargo.toml b/examples/wasi_example/api/Cargo.toml new file mode 100644 index 000000000..c3e400c1f --- /dev/null +++ b/examples/wasi_example/api/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "api" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] diff --git a/examples/wasi_example/api/src/main.rs b/examples/wasi_example/api/src/main.rs new file mode 100644 index 000000000..1610d87e6 --- /dev/null +++ b/examples/wasi_example/api/src/main.rs @@ -0,0 +1,15 @@ +#[link(wasm_import_module = "sea-orm")] +extern "C" { + pub fn query(str: i32, len: i32) -> i32; +} + +pub fn main() { + let str = "SELECT * FROM users"; + + unsafe { + let result = query(str.as_ptr() as i32, str.len() as i32); + println!("result: {}", result); + }; + + println!("Done at api"); +} diff --git a/examples/wasi_example/vm/Cargo.toml b/examples/wasi_example/vm/Cargo.toml new file mode 100644 index 000000000..5a0c84005 --- /dev/null +++ b/examples/wasi_example/vm/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "vm" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +anyhow = "^1" +tokio = { version = "^1", features = ["full"] } + +wasmtime = "^13" +wasmtime-wasi = "^13" diff --git a/examples/wasi_example/vm/src/main.rs b/examples/wasi_example/vm/src/main.rs new file mode 100644 index 000000000..82645af40 --- /dev/null +++ b/examples/wasi_example/vm/src/main.rs @@ -0,0 +1,33 @@ +use anyhow::Result; + +use wasmtime::*; +use wasmtime_wasi::sync::WasiCtxBuilder; + +#[tokio::main] +async fn main() -> Result<()> { + let engine = Engine::default(); + let module = Module::from_file(&engine, "./target/wasm32-wasi/debug/api.wasm")?; + + let wasi = WasiCtxBuilder::new() + .inherit_stdio() + .inherit_args()? + .build(); + + let mut linker = Linker::new(&engine); + let mut store = Store::new(&engine, wasi); + wasmtime_wasi::add_to_linker(&mut linker, |s| s)?; + + linker.func_wrap("sea-orm", "query", |ptr: i32, len: i32| -> i32 { + println!("ptr: {}, len: {}", ptr, len); + 1 + })?; + linker.module(&mut store, "", &module)?; + + linker + .get_default(&mut store, "")? + .typed::<(), ()>(&store)? + .call(&mut store, ())?; + + println!("Done at vm"); + Ok(()) +} From 32c381329b7d5718619af6ed7f9483537af83ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Fri, 6 Oct 2023 16:48:00 +0800 Subject: [PATCH 08/49] chore: Try to read string from wasmtime vm. --- examples/wasi_example/Cargo.toml | 2 +- examples/wasi_example/api/Cargo.toml | 4 +++- examples/wasi_example/api/src/main.rs | 2 +- examples/wasi_example/vm/Cargo.toml | 1 + examples/wasi_example/vm/src/main.rs | 30 +++++++++++++++++++++------ 5 files changed, 30 insertions(+), 9 deletions(-) diff --git a/examples/wasi_example/Cargo.toml b/examples/wasi_example/Cargo.toml index 6f3c58b0c..3a05b9599 100644 --- a/examples/wasi_example/Cargo.toml +++ b/examples/wasi_example/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -members = ["api", "vm"] +members = [".", "api", "vm"] resolver = "2" diff --git a/examples/wasi_example/api/Cargo.toml b/examples/wasi_example/api/Cargo.toml index c3e400c1f..e644012bf 100644 --- a/examples/wasi_example/api/Cargo.toml +++ b/examples/wasi_example/api/Cargo.toml @@ -4,4 +4,6 @@ version = "0.1.0" edition = "2021" publish = false -[dependencies] +[dependencies.sea-orm] +path = "../../.." +version = "*" diff --git a/examples/wasi_example/api/src/main.rs b/examples/wasi_example/api/src/main.rs index 1610d87e6..1c0a8dc97 100644 --- a/examples/wasi_example/api/src/main.rs +++ b/examples/wasi_example/api/src/main.rs @@ -11,5 +11,5 @@ pub fn main() { println!("result: {}", result); }; - println!("Done at api"); + println!("Done at WASI runtime"); } diff --git a/examples/wasi_example/vm/Cargo.toml b/examples/wasi_example/vm/Cargo.toml index 5a0c84005..fdd7cc197 100644 --- a/examples/wasi_example/vm/Cargo.toml +++ b/examples/wasi_example/vm/Cargo.toml @@ -6,6 +6,7 @@ publish = false [dependencies] anyhow = "^1" +lazy_static = "^1" tokio = { version = "^1", features = ["full"] } wasmtime = "^13" diff --git a/examples/wasi_example/vm/src/main.rs b/examples/wasi_example/vm/src/main.rs index 82645af40..6ed7d5e67 100644 --- a/examples/wasi_example/vm/src/main.rs +++ b/examples/wasi_example/vm/src/main.rs @@ -1,7 +1,17 @@ use anyhow::Result; use wasmtime::*; -use wasmtime_wasi::sync::WasiCtxBuilder; +use wasmtime_wasi::{sync::WasiCtxBuilder, WasiCtx}; + +lazy_static::lazy_static! { + static ref STORE: Option> = None; +} + +fn query(ptr: i32, len: i32) -> i32 { + println!("ptr: {}, len: {}", ptr, len); + + 1 +} #[tokio::main] async fn main() -> Result<()> { @@ -15,12 +25,10 @@ async fn main() -> Result<()> { let mut linker = Linker::new(&engine); let mut store = Store::new(&engine, wasi); + wasmtime_wasi::add_to_linker(&mut linker, |s| s)?; - linker.func_wrap("sea-orm", "query", |ptr: i32, len: i32| -> i32 { - println!("ptr: {}, len: {}", ptr, len); - 1 - })?; + linker.func_wrap("sea-orm", "query", query)?; linker.module(&mut store, "", &module)?; linker @@ -28,6 +36,16 @@ async fn main() -> Result<()> { .typed::<(), ()>(&store)? .call(&mut store, ())?; - println!("Done at vm"); + let instance = linker.instantiate(&mut store, &module)?; + + // Read a string of 19 bytes from memory at position 1048600 + // TODO - Use tokio to read asynchronously to avoid the lifetime problem of wasmtime objects + let memory = instance.get_memory(&mut store, "memory").unwrap(); + let data = memory.data(&mut store); + let str = std::str::from_utf8(&data[1048600..1048619]).unwrap(); + println!("str: {}", str); + + println!("Done at VM"); + Ok(()) } From f35725a6fe0badf1dfd7a0438a5eb13ba99427ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Sat, 7 Oct 2023 11:51:44 +0800 Subject: [PATCH 09/49] chore: Sucks, but how to do without tokio::spawn? --- examples/wasi_example/vm/src/main.rs | 60 +++++++++++++++++++++------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/examples/wasi_example/vm/src/main.rs b/examples/wasi_example/vm/src/main.rs index 6ed7d5e67..4bce11981 100644 --- a/examples/wasi_example/vm/src/main.rs +++ b/examples/wasi_example/vm/src/main.rs @@ -1,4 +1,5 @@ -use anyhow::Result; +use anyhow::{Context, Result}; +use tokio::sync::oneshot; use wasmtime::*; use wasmtime_wasi::{sync::WasiCtxBuilder, WasiCtx}; @@ -7,12 +8,6 @@ lazy_static::lazy_static! { static ref STORE: Option> = None; } -fn query(ptr: i32, len: i32) -> i32 { - println!("ptr: {}, len: {}", ptr, len); - - 1 -} - #[tokio::main] async fn main() -> Result<()> { let engine = Engine::default(); @@ -28,9 +23,44 @@ async fn main() -> Result<()> { wasmtime_wasi::add_to_linker(&mut linker, |s| s)?; - linker.func_wrap("sea-orm", "query", query)?; + let (tx_before, rx_before) = oneshot::channel::<(usize, usize)>(); + let (tx_after, rx_after) = oneshot::channel::(); + + let tx_before_clone = std::sync::Arc::new(std::sync::Mutex::new(Some(tx_before))); + let rx_after_clone = std::sync::Arc::new(std::sync::Mutex::new(Some(rx_after))); + linker.func_wrap("sea-orm", "query", move |ptr: i32, len: i32| -> i32 { + println!("ptr: {}, len: {}", ptr, len); + let tx_before = tx_before_clone.lock().unwrap().take().unwrap(); + println!("sending"); + tx_before + .send((ptr as usize, len as usize)) + .expect("Cannot send message from inner"); + println!("sent"); + + let rx_after = rx_after_clone.lock().unwrap().take().unwrap(); + tokio::task::block_in_place(move || { + let ret = rx_after + .blocking_recv() + .context("Cannot receive message from inner") + .unwrap(); + println!("ret: {}", ret); + }); + + 1 + })?; + linker.module(&mut store, "", &module)?; + // TODO - Sucks, but I don't know how to do this without tokio::spawn + tokio::spawn(async move { + tokio::task::block_in_place(move || { + let (ptr, len) = rx_before + .blocking_recv() + .context("Cannot receive message from outer") + .unwrap(); + println!("ptr: {}, len: {}", ptr, len); + }); + }); linker .get_default(&mut store, "")? .typed::<(), ()>(&store)? @@ -38,12 +68,14 @@ async fn main() -> Result<()> { let instance = linker.instantiate(&mut store, &module)?; - // Read a string of 19 bytes from memory at position 1048600 - // TODO - Use tokio to read asynchronously to avoid the lifetime problem of wasmtime objects - let memory = instance.get_memory(&mut store, "memory").unwrap(); - let data = memory.data(&mut store); - let str = std::str::from_utf8(&data[1048600..1048619]).unwrap(); - println!("str: {}", str); + // let memory = instance + // .get_memory(&mut store, "memory") + // .expect("Cannot get memory"); + // let data = memory.data(&mut store); + // let str = std::str::from_utf8(&data[ptr..ptr + len]).context("Cannot convert to str")?; + // tx_after + // .send(str.to_owned()) + // .expect("Cannot send message from outer"); println!("Done at VM"); From bbcd987bece69c592bd700c0bc9ab1bc96f29b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Sat, 7 Oct 2023 17:12:53 +0800 Subject: [PATCH 10/49] chore: Complete the basic memory read logic. --- examples/wasi_example/api/Cargo.toml | 4 ++ examples/wasi_example/api/src/main.rs | 27 ++++++++++-- examples/wasi_example/vm/src/main.rs | 63 +++++++++------------------ 3 files changed, 48 insertions(+), 46 deletions(-) diff --git a/examples/wasi_example/api/Cargo.toml b/examples/wasi_example/api/Cargo.toml index e644012bf..aee17e875 100644 --- a/examples/wasi_example/api/Cargo.toml +++ b/examples/wasi_example/api/Cargo.toml @@ -7,3 +7,7 @@ publish = false [dependencies.sea-orm] path = "../../.." version = "*" + +[dependencies] +anyhow = "^1" +lazy_static = "^1" diff --git a/examples/wasi_example/api/src/main.rs b/examples/wasi_example/api/src/main.rs index 1c0a8dc97..71fee6fd5 100644 --- a/examples/wasi_example/api/src/main.rs +++ b/examples/wasi_example/api/src/main.rs @@ -1,14 +1,35 @@ +use std::sync::Mutex; + #[link(wasm_import_module = "sea-orm")] extern "C" { - pub fn query(str: i32, len: i32) -> i32; + pub fn query(str: usize, len: usize) -> usize; +} + +lazy_static::lazy_static! { + static ref RET: Mutex>> = Mutex::new(None); } pub fn main() { + // Static string test + let str = "SELECT * FROM users"; unsafe { - let result = query(str.as_ptr() as i32, str.len() as i32); - println!("result: {}", result); + let result = query(str.as_ptr() as usize, str.len() as usize); + println!("Count: {}", result); + }; + + // Dynamic string test + RET.lock() + .unwrap() + .replace(format!("SELECT * FROM users WHERE id = {}", 42).into()); + + unsafe { + let ptr = RET.lock().unwrap().as_ref().unwrap().as_ptr() as usize; + let len = RET.lock().unwrap().as_ref().unwrap().len(); + + let result = query(ptr, len); + println!("Count: {}", result); }; println!("Done at WASI runtime"); diff --git a/examples/wasi_example/vm/src/main.rs b/examples/wasi_example/vm/src/main.rs index 4bce11981..8b679d633 100644 --- a/examples/wasi_example/vm/src/main.rs +++ b/examples/wasi_example/vm/src/main.rs @@ -1,5 +1,5 @@ use anyhow::{Context, Result}; -use tokio::sync::oneshot; +use tokio::sync::mpsc; use wasmtime::*; use wasmtime_wasi::{sync::WasiCtxBuilder, WasiCtx}; @@ -23,44 +23,23 @@ async fn main() -> Result<()> { wasmtime_wasi::add_to_linker(&mut linker, |s| s)?; - let (tx_before, rx_before) = oneshot::channel::<(usize, usize)>(); - let (tx_after, rx_after) = oneshot::channel::(); + let (tx, mut rx) = mpsc::channel::<(usize, usize)>(32); - let tx_before_clone = std::sync::Arc::new(std::sync::Mutex::new(Some(tx_before))); - let rx_after_clone = std::sync::Arc::new(std::sync::Mutex::new(Some(rx_after))); + let count = std::sync::Arc::new(std::sync::Mutex::new(0)); linker.func_wrap("sea-orm", "query", move |ptr: i32, len: i32| -> i32 { - println!("ptr: {}, len: {}", ptr, len); - let tx_before = tx_before_clone.lock().unwrap().take().unwrap(); - println!("sending"); - tx_before - .send((ptr as usize, len as usize)) - .expect("Cannot send message from inner"); - println!("sent"); - - let rx_after = rx_after_clone.lock().unwrap().take().unwrap(); - tokio::task::block_in_place(move || { - let ret = rx_after - .blocking_recv() - .context("Cannot receive message from inner") - .unwrap(); - println!("ret: {}", ret); + let tx = tx.clone(); + tokio::spawn(async move { + tx.send((ptr as usize, len as usize)).await.unwrap(); }); - 1 + let count = count.clone(); + let mut count = count.lock().unwrap(); + *count += 1; + *count })?; linker.module(&mut store, "", &module)?; - // TODO - Sucks, but I don't know how to do this without tokio::spawn - tokio::spawn(async move { - tokio::task::block_in_place(move || { - let (ptr, len) = rx_before - .blocking_recv() - .context("Cannot receive message from outer") - .unwrap(); - println!("ptr: {}, len: {}", ptr, len); - }); - }); linker .get_default(&mut store, "")? .typed::<(), ()>(&store)? @@ -68,16 +47,14 @@ async fn main() -> Result<()> { let instance = linker.instantiate(&mut store, &module)?; - // let memory = instance - // .get_memory(&mut store, "memory") - // .expect("Cannot get memory"); - // let data = memory.data(&mut store); - // let str = std::str::from_utf8(&data[ptr..ptr + len]).context("Cannot convert to str")?; - // tx_after - // .send(str.to_owned()) - // .expect("Cannot send message from outer"); - - println!("Done at VM"); - - Ok(()) + loop { + let (ptr, len) = rx.recv().await.context("Cannot receive message")?; + println!("ptr: {}, len: {}", ptr, len); + let memory = instance + .get_memory(&mut store, "memory") + .expect("Cannot get memory"); + let data = memory.data(&mut store); + let str = std::str::from_utf8(&data[ptr..ptr + len]).context("Cannot convert to str")?; + println!("str: {}", str); + } } From 460c97870407442f557ee4c59fe1c0d574632d3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Mon, 9 Oct 2023 11:49:36 +0800 Subject: [PATCH 11/49] chore: Abandon the WASI demo, native demo first... --- examples/proxy-conn-example/Cargo.toml | 24 ++++++ examples/proxy-conn-example/src/main.rs | 35 +++++++++ examples/wasi_example/Cargo.toml | 3 - examples/wasi_example/README.md | 15 ---- examples/wasi_example/api/Cargo.toml | 13 ---- examples/wasi_example/api/src/main.rs | 36 --------- examples/wasi_example/vm/Cargo.toml | 13 ---- examples/wasi_example/vm/src/main.rs | 60 --------------- src/database/mod.rs | 26 ++++++- src/database/proxy.rs | 97 +++++++++++++++++++++++-- src/executor/query.rs | 2 +- 11 files changed, 175 insertions(+), 149 deletions(-) create mode 100644 examples/proxy-conn-example/Cargo.toml create mode 100644 examples/proxy-conn-example/src/main.rs delete mode 100644 examples/wasi_example/Cargo.toml delete mode 100644 examples/wasi_example/README.md delete mode 100644 examples/wasi_example/api/Cargo.toml delete mode 100644 examples/wasi_example/api/src/main.rs delete mode 100644 examples/wasi_example/vm/Cargo.toml delete mode 100644 examples/wasi_example/vm/src/main.rs diff --git a/examples/proxy-conn-example/Cargo.toml b/examples/proxy-conn-example/Cargo.toml new file mode 100644 index 000000000..ae2fb5c61 --- /dev/null +++ b/examples/proxy-conn-example/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "sea-orm-proxy-conn-example" +version = "0.1.0" +authors = ["Langyo "] +edition = "2021" +publish = false + +[workspace] +members = ["."] + +[dependencies] +async-std = { version = "1.12", features = ["attributes", "tokio1"] } +serde_json = { version = "1" } +serde = { version = "1" } +futures = { version = "0.3" } +async-stream = { version = "0.3" } +futures-util = { version = "0.3" } + +sea-orm = { path = "../../", features = [ + "sqlx-all", + "proxy", + "runtime-async-std-native-tls", + "debug-print", +] } diff --git a/examples/proxy-conn-example/src/main.rs b/examples/proxy-conn-example/src/main.rs new file mode 100644 index 000000000..93b8613e7 --- /dev/null +++ b/examples/proxy-conn-example/src/main.rs @@ -0,0 +1,35 @@ +//! Proxy connection example. + +#![deny(missing_docs)] + +use std::sync::Arc; + +use sea_orm::{ + ConnectOptions, Database, DbBackend, DbErr, ExecResult, ProxyDatabaseFuncTrait, + ProxyExecResult, QueryResult, Statement, +}; + +#[derive(Debug)] +struct ProxyDb {} + +impl ProxyDatabaseFuncTrait for ProxyDb { + fn query(&self, statement: Statement) -> Result, DbErr> { + println!("SQL query: {}", statement.sql); + Ok(vec![]) + } + + fn execute(&self, statement: Statement) -> Result { + println!("SQL execute: {}", statement.sql); + Ok(ProxyExecResult::default().into()) + } +} + +#[async_std::main] +async fn main() { + let mut option = ConnectOptions::new(""); + option.proxy_type(DbBackend::MySql); + option.proxy_func(Arc::new(ProxyDb {})); + let db = Database::connect(option).await.unwrap(); + + println!("{:?}", db); +} diff --git a/examples/wasi_example/Cargo.toml b/examples/wasi_example/Cargo.toml deleted file mode 100644 index 3a05b9599..000000000 --- a/examples/wasi_example/Cargo.toml +++ /dev/null @@ -1,3 +0,0 @@ -[workspace] -members = [".", "api", "vm"] -resolver = "2" diff --git a/examples/wasi_example/README.md b/examples/wasi_example/README.md deleted file mode 100644 index 78a1ef110..000000000 --- a/examples/wasi_example/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# SeaORM WASI + Proxy example - -Simple implementation of WASI and proxy connections using Wasmtime and SeaORM. - -First build the client wasm using - -```bash -cargo build --target wasm32-wasi --package api -``` - -Then run server using - -```bash -cargo run --package vm -``` diff --git a/examples/wasi_example/api/Cargo.toml b/examples/wasi_example/api/Cargo.toml deleted file mode 100644 index aee17e875..000000000 --- a/examples/wasi_example/api/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "api" -version = "0.1.0" -edition = "2021" -publish = false - -[dependencies.sea-orm] -path = "../../.." -version = "*" - -[dependencies] -anyhow = "^1" -lazy_static = "^1" diff --git a/examples/wasi_example/api/src/main.rs b/examples/wasi_example/api/src/main.rs deleted file mode 100644 index 71fee6fd5..000000000 --- a/examples/wasi_example/api/src/main.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::sync::Mutex; - -#[link(wasm_import_module = "sea-orm")] -extern "C" { - pub fn query(str: usize, len: usize) -> usize; -} - -lazy_static::lazy_static! { - static ref RET: Mutex>> = Mutex::new(None); -} - -pub fn main() { - // Static string test - - let str = "SELECT * FROM users"; - - unsafe { - let result = query(str.as_ptr() as usize, str.len() as usize); - println!("Count: {}", result); - }; - - // Dynamic string test - RET.lock() - .unwrap() - .replace(format!("SELECT * FROM users WHERE id = {}", 42).into()); - - unsafe { - let ptr = RET.lock().unwrap().as_ref().unwrap().as_ptr() as usize; - let len = RET.lock().unwrap().as_ref().unwrap().len(); - - let result = query(ptr, len); - println!("Count: {}", result); - }; - - println!("Done at WASI runtime"); -} diff --git a/examples/wasi_example/vm/Cargo.toml b/examples/wasi_example/vm/Cargo.toml deleted file mode 100644 index fdd7cc197..000000000 --- a/examples/wasi_example/vm/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "vm" -version = "0.1.0" -edition = "2021" -publish = false - -[dependencies] -anyhow = "^1" -lazy_static = "^1" -tokio = { version = "^1", features = ["full"] } - -wasmtime = "^13" -wasmtime-wasi = "^13" diff --git a/examples/wasi_example/vm/src/main.rs b/examples/wasi_example/vm/src/main.rs deleted file mode 100644 index 8b679d633..000000000 --- a/examples/wasi_example/vm/src/main.rs +++ /dev/null @@ -1,60 +0,0 @@ -use anyhow::{Context, Result}; -use tokio::sync::mpsc; - -use wasmtime::*; -use wasmtime_wasi::{sync::WasiCtxBuilder, WasiCtx}; - -lazy_static::lazy_static! { - static ref STORE: Option> = None; -} - -#[tokio::main] -async fn main() -> Result<()> { - let engine = Engine::default(); - let module = Module::from_file(&engine, "./target/wasm32-wasi/debug/api.wasm")?; - - let wasi = WasiCtxBuilder::new() - .inherit_stdio() - .inherit_args()? - .build(); - - let mut linker = Linker::new(&engine); - let mut store = Store::new(&engine, wasi); - - wasmtime_wasi::add_to_linker(&mut linker, |s| s)?; - - let (tx, mut rx) = mpsc::channel::<(usize, usize)>(32); - - let count = std::sync::Arc::new(std::sync::Mutex::new(0)); - linker.func_wrap("sea-orm", "query", move |ptr: i32, len: i32| -> i32 { - let tx = tx.clone(); - tokio::spawn(async move { - tx.send((ptr as usize, len as usize)).await.unwrap(); - }); - - let count = count.clone(); - let mut count = count.lock().unwrap(); - *count += 1; - *count - })?; - - linker.module(&mut store, "", &module)?; - - linker - .get_default(&mut store, "")? - .typed::<(), ()>(&store)? - .call(&mut store, ())?; - - let instance = linker.instantiate(&mut store, &module)?; - - loop { - let (ptr, len) = rx.recv().await.context("Cannot receive message")?; - println!("ptr: {}, len: {}", ptr, len); - let memory = instance - .get_memory(&mut store, "memory") - .expect("Cannot get memory"); - let data = memory.data(&mut store); - let str = std::str::from_utf8(&data[ptr..ptr + len]).context("Cannot convert to str")?; - println!("str: {}", str); - } -} diff --git a/src/database/mod.rs b/src/database/mod.rs index 8bb163cad..2da768a7b 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -93,8 +93,8 @@ impl Database { return crate::MockDatabaseConnector::connect(&opt.url).await; } #[cfg(feature = "proxy")] - if let Some(proxy_func_arc) = &opt.proxy_func { - if let Some(proxy_type) = &opt.proxy_type { + if let Some(proxy_type) = &opt.proxy_type { + if let Some(proxy_func_arc) = &opt.proxy_func { match proxy_type { DbBackend::MySql => { return crate::ProxyDatabaseConnector::connect( @@ -115,6 +115,11 @@ impl Database { ); } } + } else { + return Err(conn_err(format!( + "The connection string '{}' has no proxy function.", + opt.url + ))); } } @@ -299,4 +304,21 @@ impl ConnectOptions { self.schema_search_path = Some(schema_search_path.into()); self } + + /// Set the proxy type for proxy connections, which MySql is the default value + #[cfg(feature = "proxy")] + pub fn proxy_type(&mut self, proxy_type: DbBackend) -> &mut Self { + self.proxy_type = Some(proxy_type); + self + } + + /// Set the proxy functions for proxy connections + #[cfg(feature = "proxy")] + pub fn proxy_func( + &mut self, + proxy_func: std::sync::Arc, + ) -> &mut Self { + self.proxy_func = Some(proxy_func); + self + } } diff --git a/src/database/proxy.rs b/src/database/proxy.rs index 9141d2202..e598729cf 100644 --- a/src/database/proxy.rs +++ b/src/database/proxy.rs @@ -1,10 +1,10 @@ use crate::{ error::*, DatabaseConnection, DbBackend, ExecResult, ExecResultHolder, ProxyDatabaseConnection, - ProxyDatabaseTrait, QueryResult, Statement, + ProxyDatabaseTrait, QueryResult, QueryResultRow, Statement, }; use sea_query::{Value, ValueType}; -use std::{collections::BTreeMap, sync::Arc}; +use std::{collections::BTreeMap, fmt::Debug, sync::Arc}; use tracing::instrument; #[cfg(feature = "proxy")] @@ -17,16 +17,18 @@ pub trait ProxyDatabaseFuncTrait: Send + Sync + std::fmt::Debug { fn execute(&self, statement: Statement) -> Result; /// Begin a transaction in the [ProxyDatabase] - fn begin(&self); + fn begin(&self) {} /// Commit a transaction in the [ProxyDatabase] - fn commit(&self); + fn commit(&self) {} /// Rollback a transaction in the [ProxyDatabase] - fn rollback(&self); + fn rollback(&self) {} /// Ping the [ProxyDatabase], it should return an error if the database is not available - fn ping(&self) -> Result<(), DbErr>; + fn ping(&self) -> Result<(), DbErr> { + Ok(()) + } } /// Defines the results obtained from a [ProxyDatabase] @@ -38,6 +40,39 @@ pub struct ProxyExecResult { pub rows_affected: u64, } +impl ProxyExecResult { + /// Create a new [ProxyExecResult] from the last inserted id and the number of rows affected + pub fn new(last_insert_id: u64, rows_affected: u64) -> Self { + Self { + last_insert_id, + rows_affected, + } + } +} + +impl Default for ExecResultHolder { + fn default() -> Self { + Self::Proxy(ProxyExecResult::default()) + } +} + +impl From for ExecResult { + fn from(result: ProxyExecResult) -> Self { + Self { + result: ExecResultHolder::Proxy(result), + } + } +} + +impl From for ProxyExecResult { + fn from(result: ExecResult) -> Self { + match result.result { + ExecResultHolder::Proxy(result) => result, + _ => unreachable!("Cannot convert ExecResult to ProxyExecResult"), + } + } +} + /// Defines the structure of a test Row for the [ProxyDatabase] /// which is just a [BTreeMap]<[String], [Value]> #[derive(Clone, Debug)] @@ -45,6 +80,56 @@ pub struct ProxyRow { values: BTreeMap, } +impl ProxyRow { + /// Create a new [ProxyRow] from a [BTreeMap]<[String], [Value]> + pub fn new(values: BTreeMap) -> Self { + Self { values } + } +} + +impl Default for ProxyRow { + fn default() -> Self { + Self { + values: BTreeMap::new(), + } + } +} + +impl From> for ProxyRow { + fn from(values: BTreeMap) -> Self { + Self { values } + } +} + +impl From for BTreeMap { + fn from(row: ProxyRow) -> Self { + row.values + } +} + +impl From for Vec<(String, Value)> { + fn from(row: ProxyRow) -> Self { + row.values.into_iter().collect() + } +} + +impl From for QueryResult { + fn from(row: ProxyRow) -> Self { + QueryResult { + row: QueryResultRow::Proxy(row), + } + } +} + +impl From for ProxyRow { + fn from(result: QueryResult) -> Self { + match result.row { + QueryResultRow::Proxy(row) => row, + _ => unreachable!("Cannot convert QueryResult to ProxyRow"), + } + } +} + #[cfg(feature = "proxy")] /// Defines a Proxy database suitable for testing #[derive(Debug, Clone)] diff --git a/src/executor/query.rs b/src/executor/query.rs index 5bb8f4564..c513ddaa6 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -1,7 +1,7 @@ use crate::{error::*, SelectGetableValue, SelectorRaw, Statement}; use std::fmt; -#[cfg(feature = "mock")] +#[cfg(any(feature = "mock", feature = "proxy"))] use crate::debug_print; #[cfg(feature = "sqlx-dep")] From c354c83c834337cd82f104a402ea5c78daaa8eba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Mon, 9 Oct 2023 15:31:36 +0800 Subject: [PATCH 12/49] refactor: Use single proxy connection generator to avoid stack overflow --- examples/proxy-conn-example/src/main.rs | 22 +++--- src/database/mod.rs | 93 +++++++++---------------- 2 files changed, 43 insertions(+), 72 deletions(-) diff --git a/examples/proxy-conn-example/src/main.rs b/examples/proxy-conn-example/src/main.rs index 93b8613e7..b56063173 100644 --- a/examples/proxy-conn-example/src/main.rs +++ b/examples/proxy-conn-example/src/main.rs @@ -2,34 +2,32 @@ #![deny(missing_docs)] -use std::sync::Arc; - use sea_orm::{ - ConnectOptions, Database, DbBackend, DbErr, ExecResult, ProxyDatabaseFuncTrait, - ProxyExecResult, QueryResult, Statement, + Database, DbBackend, DbErr, ExecResult, ProxyDatabaseTrait, ProxyExecResult, ProxyQueryRow, + Statement, }; +use std::sync::{Arc, Mutex}; #[derive(Debug)] struct ProxyDb {} -impl ProxyDatabaseFuncTrait for ProxyDb { - fn query(&self, statement: Statement) -> Result, DbErr> { +impl ProxyDatabaseTrait for ProxyDb { + fn query(&self, statement: Statement) -> Result, DbErr> { println!("SQL query: {}", statement.sql); Ok(vec![]) } - fn execute(&self, statement: Statement) -> Result { + fn execute(&self, statement: Statement) -> Result { println!("SQL execute: {}", statement.sql); - Ok(ProxyExecResult::default().into()) + Ok(ProxyExecResult::default()) } } #[async_std::main] async fn main() { - let mut option = ConnectOptions::new(""); - option.proxy_type(DbBackend::MySql); - option.proxy_func(Arc::new(ProxyDb {})); - let db = Database::connect(option).await.unwrap(); + let db = Database::connect_proxy(DbBackend::MySql, Arc::new(Mutex::new(Box::new(ProxyDb {})))) + .await + .unwrap(); println!("{:?}", db); } diff --git a/src/database/mod.rs b/src/database/mod.rs index 2da768a7b..10d9807d8 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -21,7 +21,10 @@ pub use mock::*; #[cfg_attr(docsrs, doc(cfg(feature = "proxy")))] pub use proxy::*; pub use statement::*; -use std::borrow::Cow; +use std::{ + borrow::Cow, + sync::{Arc, Mutex}, +}; pub use stream::*; use tracing::instrument; pub use transaction::*; @@ -58,13 +61,6 @@ pub struct ConnectOptions { pub(crate) sqlcipher_key: Option>, /// Schema search path (PostgreSQL only) pub(crate) schema_search_path: Option, - - /// Proxy type for proxy connections, which MySql is the default value (Proxy only) - #[cfg(feature = "proxy")] - pub(crate) proxy_type: Option, - /// Proxy functions for proxy connections (Proxy only) - #[cfg(feature = "proxy")] - pub(crate) proxy_func: Option>, } impl Database { @@ -92,42 +88,41 @@ impl Database { if crate::MockDatabaseConnector::accepts(&opt.url) { return crate::MockDatabaseConnector::connect(&opt.url).await; } - #[cfg(feature = "proxy")] - if let Some(proxy_type) = &opt.proxy_type { - if let Some(proxy_func_arc) = &opt.proxy_func { - match proxy_type { - DbBackend::MySql => { - return crate::ProxyDatabaseConnector::connect( - DbBackend::MySql, - proxy_func_arc.to_owned(), - ); - } - DbBackend::Postgres => { - return crate::ProxyDatabaseConnector::connect( - DbBackend::Postgres, - proxy_func_arc.to_owned(), - ); - } - DbBackend::Sqlite => { - return crate::ProxyDatabaseConnector::connect( - DbBackend::Sqlite, - proxy_func_arc.to_owned(), - ); - } - } - } else { - return Err(conn_err(format!( - "The connection string '{}' has no proxy function.", - opt.url - ))); - } - } Err(conn_err(format!( "The connection string '{}' has no supporting driver.", opt.url ))) } + + /// Method to create a [DatabaseConnection] on a proxy database + #[cfg(feature = "proxy")] + #[instrument(level = "trace", skip(proxy_func_arc))] + pub async fn connect_proxy( + db_type: DbBackend, + proxy_func_arc: Arc>>, + ) -> Result { + match db_type { + DbBackend::MySql => { + return crate::ProxyDatabaseConnector::connect( + DbBackend::MySql, + proxy_func_arc.to_owned(), + ); + } + DbBackend::Postgres => { + return crate::ProxyDatabaseConnector::connect( + DbBackend::Postgres, + proxy_func_arc.to_owned(), + ); + } + DbBackend::Sqlite => { + return crate::ProxyDatabaseConnector::connect( + DbBackend::Sqlite, + proxy_func_arc.to_owned(), + ); + } + } + } } impl From for ConnectOptions @@ -157,11 +152,6 @@ impl ConnectOptions { sqlx_logging_level: log::LevelFilter::Info, sqlcipher_key: None, schema_search_path: None, - - #[cfg(feature = "proxy")] - proxy_type: Some(DbBackend::MySql), - #[cfg(feature = "proxy")] - proxy_func: None, } } @@ -304,21 +294,4 @@ impl ConnectOptions { self.schema_search_path = Some(schema_search_path.into()); self } - - /// Set the proxy type for proxy connections, which MySql is the default value - #[cfg(feature = "proxy")] - pub fn proxy_type(&mut self, proxy_type: DbBackend) -> &mut Self { - self.proxy_type = Some(proxy_type); - self - } - - /// Set the proxy functions for proxy connections - #[cfg(feature = "proxy")] - pub fn proxy_func( - &mut self, - proxy_func: std::sync::Arc, - ) -> &mut Self { - self.proxy_func = Some(proxy_func); - self - } } From ac8816f04e5e1d2c911c8053d99cb569163012e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Mon, 9 Oct 2023 16:53:54 +0800 Subject: [PATCH 13/49] refactor: Rename the inner structs' name --- examples/proxy-conn-example/src/main.rs | 9 +- src/database/proxy.rs | 147 ++++++------------------ src/driver/proxy.rs | 73 ++++++------ 3 files changed, 72 insertions(+), 157 deletions(-) diff --git a/examples/proxy-conn-example/src/main.rs b/examples/proxy-conn-example/src/main.rs index b56063173..13a8bec39 100644 --- a/examples/proxy-conn-example/src/main.rs +++ b/examples/proxy-conn-example/src/main.rs @@ -3,8 +3,7 @@ #![deny(missing_docs)] use sea_orm::{ - Database, DbBackend, DbErr, ExecResult, ProxyDatabaseTrait, ProxyExecResult, ProxyQueryRow, - Statement, + Database, DbBackend, DbErr, ProxyDatabaseTrait, ProxyExecResult, ProxyRow, Statement, }; use std::sync::{Arc, Mutex}; @@ -12,14 +11,14 @@ use std::sync::{Arc, Mutex}; struct ProxyDb {} impl ProxyDatabaseTrait for ProxyDb { - fn query(&self, statement: Statement) -> Result, DbErr> { + fn query(&self, statement: Statement) -> Result, DbErr> { println!("SQL query: {}", statement.sql); - Ok(vec![]) + Ok(vec![].into()) } fn execute(&self, statement: Statement) -> Result { println!("SQL execute: {}", statement.sql); - Ok(ProxyExecResult::default()) + Ok(Default::default()) } } diff --git a/src/database/proxy.rs b/src/database/proxy.rs index e598729cf..c6afae2b6 100644 --- a/src/database/proxy.rs +++ b/src/database/proxy.rs @@ -1,20 +1,16 @@ -use crate::{ - error::*, DatabaseConnection, DbBackend, ExecResult, ExecResultHolder, ProxyDatabaseConnection, - ProxyDatabaseTrait, QueryResult, QueryResultRow, Statement, -}; +use crate::{error::*, ExecResult, ExecResultHolder, QueryResult, QueryResultRow, Statement}; use sea_query::{Value, ValueType}; -use std::{collections::BTreeMap, fmt::Debug, sync::Arc}; -use tracing::instrument; +use std::{collections::BTreeMap, fmt::Debug}; #[cfg(feature = "proxy")] -/// Defines the [ProxyDatabaseFuncTrait] to save the functions -pub trait ProxyDatabaseFuncTrait: Send + Sync + std::fmt::Debug { +/// Defines the [ProxyDatabaseTrait] to save the functions +pub trait ProxyDatabaseTrait: Send + Sync + std::fmt::Debug { /// Execute a query in the [ProxyDatabase], and return the query results - fn query(&self, statement: Statement) -> Result, DbErr>; + fn query(&self, statement: Statement) -> Result, DbErr>; /// Execute a command in the [ProxyDatabase], and report the number of rows affected - fn execute(&self, statement: Statement) -> Result; + fn execute(&self, statement: Statement) -> Result; /// Begin a transaction in the [ProxyDatabase] fn begin(&self) {} @@ -32,6 +28,7 @@ pub trait ProxyDatabaseFuncTrait: Send + Sync + std::fmt::Debug { } /// Defines the results obtained from a [ProxyDatabase] +#[cfg(feature = "proxy")] #[derive(Clone, Debug, Default)] pub struct ProxyExecResult { /// The last inserted id on auto-increment @@ -40,6 +37,7 @@ pub struct ProxyExecResult { pub rows_affected: u64, } +#[cfg(feature = "proxy")] impl ProxyExecResult { /// Create a new [ProxyExecResult] from the last inserted id and the number of rows affected pub fn new(last_insert_id: u64, rows_affected: u64) -> Self { @@ -50,12 +48,14 @@ impl ProxyExecResult { } } +#[cfg(feature = "proxy")] impl Default for ExecResultHolder { fn default() -> Self { Self::Proxy(ProxyExecResult::default()) } } +#[cfg(feature = "proxy")] impl From for ExecResult { fn from(result: ProxyExecResult) -> Self { Self { @@ -64,6 +64,7 @@ impl From for ExecResult { } } +#[cfg(feature = "proxy")] impl From for ProxyExecResult { fn from(result: ExecResult) -> Self { match result.result { @@ -73,13 +74,16 @@ impl From for ProxyExecResult { } } -/// Defines the structure of a test Row for the [ProxyDatabase] +/// Defines the structure of a Row for the [ProxyDatabase] /// which is just a [BTreeMap]<[String], [Value]> +#[cfg(feature = "proxy")] #[derive(Clone, Debug)] pub struct ProxyRow { - values: BTreeMap, + /// The values of the single row + pub values: BTreeMap, } +#[cfg(feature = "proxy")] impl ProxyRow { /// Create a new [ProxyRow] from a [BTreeMap]<[String], [Value]> pub fn new(values: BTreeMap) -> Self { @@ -87,6 +91,7 @@ impl ProxyRow { } } +#[cfg(feature = "proxy")] impl Default for ProxyRow { fn default() -> Self { Self { @@ -95,24 +100,28 @@ impl Default for ProxyRow { } } +#[cfg(feature = "proxy")] impl From> for ProxyRow { fn from(values: BTreeMap) -> Self { Self { values } } } +#[cfg(feature = "proxy")] impl From for BTreeMap { fn from(row: ProxyRow) -> Self { row.values } } +#[cfg(feature = "proxy")] impl From for Vec<(String, Value)> { fn from(row: ProxyRow) -> Self { row.values.into_iter().collect() } } +#[cfg(feature = "proxy")] impl From for QueryResult { fn from(row: ProxyRow) -> Self { QueryResult { @@ -121,6 +130,7 @@ impl From for QueryResult { } } +#[cfg(feature = "proxy")] impl From for ProxyRow { fn from(result: QueryResult) -> Self { match result.row { @@ -130,72 +140,6 @@ impl From for ProxyRow { } } -#[cfg(feature = "proxy")] -/// Defines a Proxy database suitable for testing -#[derive(Debug, Clone)] -pub struct ProxyDatabase { - db_backend: DbBackend, - proxy_func: Arc, -} - -impl ProxyDatabase { - /// Instantiate a proxy database with a [DbBackend] and the [ProxyDatabaseFuncTrait] - pub fn new(db_backend: DbBackend, func: Arc) -> Self { - Self { - db_backend, - proxy_func: func.to_owned(), - } - } - - /// Create a database connection - pub fn into_connection(self) -> DatabaseConnection { - DatabaseConnection::ProxyDatabaseConnection(Arc::new(ProxyDatabaseConnection::new(self))) - } -} - -impl ProxyDatabaseTrait for ProxyDatabase { - #[instrument(level = "trace")] - fn execute(&mut self, statement: Statement) -> Result { - match self.proxy_func.execute(statement) { - Ok(result) => Ok(ExecResult { - result: ExecResultHolder::Proxy(ProxyExecResult { - last_insert_id: result.last_insert_id(), - rows_affected: result.rows_affected(), - }), - }), - Err(err) => Err(err), - } - } - - #[instrument(level = "trace")] - fn query(&mut self, statement: Statement) -> Result, DbErr> { - self.proxy_func.query(statement) - } - - #[instrument(level = "trace")] - fn begin(&mut self) { - self.proxy_func.begin() - } - - #[instrument(level = "trace")] - fn commit(&mut self) { - self.proxy_func.commit() - } - - #[instrument(level = "trace")] - fn rollback(&mut self) { - self.proxy_func.rollback() - } - - fn get_database_backend(&self) -> DbBackend { - self.db_backend - } - - fn ping(&self) -> Result<(), DbErr> { - self.proxy_func.ping() - } -} - impl ProxyRow { /// Get a value from the [ProxyRow] pub fn try_get(&self, index: I) -> Result @@ -232,50 +176,33 @@ impl ProxyRow { #[cfg(feature = "proxy")] mod tests { use crate::{ - entity::*, tests_cfg::*, DbBackend, DbErr, ExecResult, ProxyDatabase, - ProxyDatabaseFuncTrait, QueryResult, Statement, + entity::*, tests_cfg::*, Database, DbBackend, DbErr, ProxyDatabaseTrait, ProxyExecResult, + ProxyRow, Statement, }; use pretty_assertions::assert_eq; - use std::sync::Arc; + use std::sync::{Arc, Mutex}; #[derive(Debug)] - struct EmptyProxyFunc {} - - impl ProxyDatabaseFuncTrait for EmptyProxyFunc { - fn query(&self, statement: Statement) -> Result, DbErr> { - println!("query: {:?}", statement); - Ok(vec![]) - } + struct ProxyDb {} - fn execute(&self, statement: Statement) -> Result { - println!("execute: {:?}", statement); - Ok(ExecResult { - result: crate::ExecResultHolder::Proxy(crate::ProxyExecResult { - last_insert_id: 1, - rows_affected: 1, - }), - }) + impl ProxyDatabaseTrait for ProxyDb { + fn query(&self, statement: Statement) -> Result, DbErr> { + println!("SQL query: {}", statement.sql); + Ok(vec![].into()) } - fn begin(&self) {} - - fn commit(&self) {} - - fn rollback(&self) {} - - fn ping(&self) -> Result<(), DbErr> { - Ok(()) + fn execute(&self, statement: Statement) -> Result { + println!("SQL execute: {}", statement.sql); + Ok(Default::default()) } } - use once_cell::sync::Lazy; - - static EMPTY_DB_FUNC: Lazy> = Lazy::new(|| Arc::new(EmptyProxyFunc {})); - #[smol_potat::test] async fn test_empty_oper() { let db = - ProxyDatabase::new(DbBackend::MySql, (*EMPTY_DB_FUNC).to_owned()).into_connection(); + Database::connect_proxy(DbBackend::MySql, Arc::new(Mutex::new(Box::new(ProxyDb {})))) + .await + .unwrap(); let _ = cake::Entity::find().all(&db).await; @@ -284,7 +211,5 @@ mod tests { name: Set("Alice".to_string()), }; cake::Entity::insert(item).exec(&db).await.unwrap(); - - assert_eq!("1", "1"); } } diff --git a/src/driver/proxy.rs b/src/driver/proxy.rs index c172f78e9..1ed1ab81b 100644 --- a/src/driver/proxy.rs +++ b/src/driver/proxy.rs @@ -1,6 +1,6 @@ use crate::{ - debug_print, error::*, DatabaseConnection, DbBackend, ExecResult, ProxyDatabase, - ProxyDatabaseFuncTrait, QueryResult, Statement, + debug_print, error::*, DatabaseConnection, DbBackend, ExecResult, ProxyDatabaseTrait, + QueryResult, Statement, }; use futures::Stream; use std::{ @@ -17,31 +17,8 @@ pub struct ProxyDatabaseConnector; /// Defines a connection for the [ProxyDatabase] #[derive(Debug)] pub struct ProxyDatabaseConnection { - proxy: Mutex>, -} - -/// A Trait for any type wanting to perform operations on the [ProxyDatabase] -pub trait ProxyDatabaseTrait: Send + Debug { - /// Execute a statement in the [ProxyDatabase] - fn execute(&mut self, stmt: Statement) -> Result; - - /// Execute a SQL query in the [ProxyDatabase] - fn query(&mut self, stmt: Statement) -> Result, DbErr>; - - /// Create a transaction that can be committed atomically - fn begin(&mut self); - - /// Commit a successful transaction atomically into the [ProxyDatabase] - fn commit(&mut self); - - /// Roll back a transaction since errors were encountered - fn rollback(&mut self); - - /// Get the backend being used in the [ProxyDatabase] - fn get_database_backend(&self) -> DbBackend; - - /// Ping the [ProxyDatabase] - fn ping(&self) -> Result<(), DbErr>; + db_backend: DbBackend, + proxy: Arc>>, } impl ProxyDatabaseConnector { @@ -57,38 +34,38 @@ impl ProxyDatabaseConnector { #[instrument(level = "trace")] pub fn connect( db_type: DbBackend, - func: Arc, + func: Arc>>, ) -> Result { Ok(DatabaseConnection::ProxyDatabaseConnection(Arc::new( - ProxyDatabaseConnection::new(ProxyDatabase::new(db_type, func)), + ProxyDatabaseConnection::new(db_type, func), ))) } } impl ProxyDatabaseConnection { /// Create a connection to the [ProxyDatabase] - pub fn new(m: M) -> Self - where - M: ProxyDatabaseTrait, - { + pub fn new(db_backend: DbBackend, funcs: Arc>>) -> Self { Self { - proxy: Mutex::new(Box::new(m)), + db_backend, + proxy: funcs.to_owned(), } } /// Get the [DatabaseBackend](crate::DatabaseBackend) being used by the [ProxyDatabase] pub fn get_database_backend(&self) -> DbBackend { - self.proxy - .lock() - .expect("Fail to acquire mocker") - .get_database_backend() + self.db_backend } /// Execute the SQL statement in the [ProxyDatabase] #[instrument(level = "trace")] pub fn execute(&self, statement: Statement) -> Result { debug_print!("{}", statement); - self.proxy.lock().map_err(exec_err)?.execute(statement) + Ok(self + .proxy + .lock() + .map_err(exec_err)? + .execute(statement)? + .into()) } /// Return one [QueryResult] if the query was successful @@ -96,14 +73,28 @@ impl ProxyDatabaseConnection { pub fn query_one(&self, statement: Statement) -> Result, DbErr> { debug_print!("{}", statement); let result = self.proxy.lock().map_err(query_err)?.query(statement)?; - Ok(result.into_iter().next()) + + if let Some(first) = result.first() { + return Ok(Some(QueryResult { + row: crate::QueryResultRow::Proxy(first.to_owned()), + })); + } else { + return Ok(None); + } } /// Return all [QueryResult]s if the query was successful #[instrument(level = "trace")] pub fn query_all(&self, statement: Statement) -> Result, DbErr> { debug_print!("{}", statement); - self.proxy.lock().map_err(query_err)?.query(statement) + let result = self.proxy.lock().map_err(query_err)?.query(statement)?; + + Ok(result + .into_iter() + .map(|row| QueryResult { + row: crate::QueryResultRow::Proxy(row), + }) + .collect()) } /// Return [QueryResult]s from a multi-query operation From 1ab8a5502d05c6f1cb813da714d6a61f43f5fdca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Mon, 9 Oct 2023 17:04:47 +0800 Subject: [PATCH 14/49] fix: Fix CI clippy and unit test --- examples/proxy-conn-example/src/main.rs | 2 +- src/database/mod.rs | 7 ++----- src/database/proxy.rs | 25 ++++++++++++++++++++++--- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/examples/proxy-conn-example/src/main.rs b/examples/proxy-conn-example/src/main.rs index 13a8bec39..8bc15247c 100644 --- a/examples/proxy-conn-example/src/main.rs +++ b/examples/proxy-conn-example/src/main.rs @@ -13,7 +13,7 @@ struct ProxyDb {} impl ProxyDatabaseTrait for ProxyDb { fn query(&self, statement: Statement) -> Result, DbErr> { println!("SQL query: {}", statement.sql); - Ok(vec![].into()) + Ok(vec![]) } fn execute(&self, statement: Statement) -> Result { diff --git a/src/database/mod.rs b/src/database/mod.rs index 10d9807d8..a844d1c85 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -21,10 +21,7 @@ pub use mock::*; #[cfg_attr(docsrs, doc(cfg(feature = "proxy")))] pub use proxy::*; pub use statement::*; -use std::{ - borrow::Cow, - sync::{Arc, Mutex}, -}; +use std::borrow::Cow; pub use stream::*; use tracing::instrument; pub use transaction::*; @@ -100,7 +97,7 @@ impl Database { #[instrument(level = "trace", skip(proxy_func_arc))] pub async fn connect_proxy( db_type: DbBackend, - proxy_func_arc: Arc>>, + proxy_func_arc: std::sync::Arc>>, ) -> Result { match db_type { DbBackend::MySql => { diff --git a/src/database/proxy.rs b/src/database/proxy.rs index c6afae2b6..53c3d59e0 100644 --- a/src/database/proxy.rs +++ b/src/database/proxy.rs @@ -179,7 +179,6 @@ mod tests { entity::*, tests_cfg::*, Database, DbBackend, DbErr, ProxyDatabaseTrait, ProxyExecResult, ProxyRow, Statement, }; - use pretty_assertions::assert_eq; use std::sync::{Arc, Mutex}; #[derive(Debug)] @@ -193,23 +192,43 @@ mod tests { fn execute(&self, statement: Statement) -> Result { println!("SQL execute: {}", statement.sql); - Ok(Default::default()) + Ok(ProxyExecResult { + last_insert_id: 1, + rows_affected: 1, + }) } } #[smol_potat::test] - async fn test_empty_oper() { + async fn create_proxy_conn() { + let db = + Database::connect_proxy(DbBackend::MySql, Arc::new(Mutex::new(Box::new(ProxyDb {})))) + .await + .unwrap(); + } + + #[smol_potat::test] + async fn select_rows() { let db = Database::connect_proxy(DbBackend::MySql, Arc::new(Mutex::new(Box::new(ProxyDb {})))) .await .unwrap(); let _ = cake::Entity::find().all(&db).await; + } + + #[smol_potat::test] + async fn insert_one_row() { + let db = + Database::connect_proxy(DbBackend::MySql, Arc::new(Mutex::new(Box::new(ProxyDb {})))) + .await + .unwrap(); let item = cake::ActiveModel { id: NotSet, name: Set("Alice".to_string()), }; + cake::Entity::insert(item).exec(&db).await.unwrap(); } } From c54ed1e7f5d366b0b887ce5a180d1378cb7f24a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Tue, 17 Oct 2023 10:38:43 +0800 Subject: [PATCH 15/49] fix: Rename the example. --- examples/{proxy-conn-example => proxy_conn_example}/Cargo.toml | 0 examples/{proxy-conn-example => proxy_conn_example}/src/main.rs | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename examples/{proxy-conn-example => proxy_conn_example}/Cargo.toml (100%) rename examples/{proxy-conn-example => proxy_conn_example}/src/main.rs (100%) diff --git a/examples/proxy-conn-example/Cargo.toml b/examples/proxy_conn_example/Cargo.toml similarity index 100% rename from examples/proxy-conn-example/Cargo.toml rename to examples/proxy_conn_example/Cargo.toml diff --git a/examples/proxy-conn-example/src/main.rs b/examples/proxy_conn_example/src/main.rs similarity index 100% rename from examples/proxy-conn-example/src/main.rs rename to examples/proxy_conn_example/src/main.rs From bf914847dc8dbe225a1d318bc1954b59a2aa8cca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Tue, 17 Oct 2023 14:44:53 +0800 Subject: [PATCH 16/49] chore: Try to embed surrealdb for proxy test. --- examples/proxy_conn_example/Cargo.toml | 1 + examples/proxy_conn_example/src/entity/mod.rs | 1 + .../proxy_conn_example/src/entity/post.rs | 16 ++++ examples/proxy_conn_example/src/main.rs | 85 ++++++++++++++++--- 4 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 examples/proxy_conn_example/src/entity/mod.rs create mode 100644 examples/proxy_conn_example/src/entity/post.rs diff --git a/examples/proxy_conn_example/Cargo.toml b/examples/proxy_conn_example/Cargo.toml index ae2fb5c61..4cfe79f0c 100644 --- a/examples/proxy_conn_example/Cargo.toml +++ b/examples/proxy_conn_example/Cargo.toml @@ -22,3 +22,4 @@ sea-orm = { path = "../../", features = [ "runtime-async-std-native-tls", "debug-print", ] } +surrealdb = { version = "1", features = ["kv-mem"] } diff --git a/examples/proxy_conn_example/src/entity/mod.rs b/examples/proxy_conn_example/src/entity/mod.rs new file mode 100644 index 000000000..e8b6291ac --- /dev/null +++ b/examples/proxy_conn_example/src/entity/mod.rs @@ -0,0 +1 @@ +pub mod post; diff --git a/examples/proxy_conn_example/src/entity/post.rs b/examples/proxy_conn_example/src/entity/post.rs new file mode 100644 index 000000000..6c7b7e886 --- /dev/null +++ b/examples/proxy_conn_example/src/entity/post.rs @@ -0,0 +1,16 @@ +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Deserialize, Serialize)] +#[sea_orm(table_name = "posts")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub title: String, + pub text: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/proxy_conn_example/src/main.rs b/examples/proxy_conn_example/src/main.rs index 8bc15247c..ba943687e 100644 --- a/examples/proxy_conn_example/src/main.rs +++ b/examples/proxy_conn_example/src/main.rs @@ -2,31 +2,96 @@ #![deny(missing_docs)] +mod entity; + +use std::sync::{Arc, Mutex}; + use sea_orm::{ - Database, DbBackend, DbErr, ProxyDatabaseTrait, ProxyExecResult, ProxyRow, Statement, + ActiveValue::Set, Database, DbBackend, DbErr, EntityTrait, ProxyDatabaseTrait, ProxyExecResult, + ProxyRow, Statement, }; -use std::sync::{Arc, Mutex}; +use surrealdb::{ + engine::local::{Db, Mem}, + Surreal, +}; + +use entity::post::{ActiveModel, Entity}; #[derive(Debug)] -struct ProxyDb {} +struct ProxyDb { + mem: Surreal, +} impl ProxyDatabaseTrait for ProxyDb { fn query(&self, statement: Statement) -> Result, DbErr> { - println!("SQL query: {}", statement.sql); + println!("SQL query: {:?}", statement); + let sql = statement.sql.clone(); + let ret = async_std::task::block_on(async { self.mem.query(sql).await }).unwrap(); + println!("SQL query result: {:?}", ret); Ok(vec![]) } fn execute(&self, statement: Statement) -> Result { - println!("SQL execute: {}", statement.sql); - Ok(Default::default()) + async_std::task::block_on(async { + if let Some(values) = statement.values { + // Replace all the '?' with the statement values + let mut new_sql = statement.sql.clone(); + let mark_count = new_sql.matches('?').count(); + for (i, v) in values.0.iter().enumerate() { + if i >= mark_count { + break; + } + new_sql = new_sql.replacen('?', &v.to_string(), 1); + } + println!("SQL execute: {}", new_sql); + + self.mem.query(new_sql).await + } else { + self.mem.query(statement.sql).await + } + }) + .unwrap(); + + Ok(ProxyExecResult { + last_insert_id: 1, + rows_affected: 1, + }) } } #[async_std::main] async fn main() { - let db = Database::connect_proxy(DbBackend::MySql, Arc::new(Mutex::new(Box::new(ProxyDb {})))) - .await - .unwrap(); + let mem = Surreal::new::(()).await.unwrap(); + mem.use_ns("test").use_db("post").await.unwrap(); + + let db = Database::connect_proxy( + DbBackend::MySql, + Arc::new(Mutex::new(Box::new(ProxyDb { mem }))), + ) + .await + .unwrap(); + + println!("Initialized"); + + let data = ActiveModel { + title: Set("Homo".to_owned()), + text: Set("いいよ、来いよ".to_owned()), + ..Default::default() + }; + Entity::insert(data).exec(&db).await.unwrap(); + let data = ActiveModel { + title: Set("Homo".to_owned()), + text: Set("そうだよ".to_owned()), + ..Default::default() + }; + Entity::insert(data).exec(&db).await.unwrap(); + let data = ActiveModel { + title: Set("Homo".to_owned()), + text: Set("悔い改めて".to_owned()), + ..Default::default() + }; + Entity::insert(data).exec(&db).await.unwrap(); - println!("{:?}", db); + let list = Entity::find().all(&db).await.unwrap().to_vec(); + println!("Result: {:?}", list); } From d593d7b50cebf0ebdf326747639b47d1bca4fcb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Tue, 17 Oct 2023 17:23:14 +0800 Subject: [PATCH 17/49] fix: Transfer the query result correctly. --- .../proxy_conn_example/src/entity/post.rs | 3 +- examples/proxy_conn_example/src/main.rs | 89 ++++++++++++++++++- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/examples/proxy_conn_example/src/entity/post.rs b/examples/proxy_conn_example/src/entity/post.rs index 6c7b7e886..1fde0c4cc 100644 --- a/examples/proxy_conn_example/src/entity/post.rs +++ b/examples/proxy_conn_example/src/entity/post.rs @@ -5,7 +5,8 @@ use serde::{Deserialize, Serialize}; #[sea_orm(table_name = "posts")] pub struct Model { #[sea_orm(primary_key)] - pub id: i32, + pub id: String, + pub title: String, pub text: String, } diff --git a/examples/proxy_conn_example/src/main.rs b/examples/proxy_conn_example/src/main.rs index ba943687e..4b94bcb6a 100644 --- a/examples/proxy_conn_example/src/main.rs +++ b/examples/proxy_conn_example/src/main.rs @@ -4,7 +4,10 @@ mod entity; -use std::sync::{Arc, Mutex}; +use std::{ + collections::BTreeMap, + sync::{Arc, Mutex}, +}; use sea_orm::{ ActiveValue::Set, Database, DbBackend, DbErr, EntityTrait, ProxyDatabaseTrait, ProxyExecResult, @@ -26,9 +29,87 @@ impl ProxyDatabaseTrait for ProxyDb { fn query(&self, statement: Statement) -> Result, DbErr> { println!("SQL query: {:?}", statement); let sql = statement.sql.clone(); - let ret = async_std::task::block_on(async { self.mem.query(sql).await }).unwrap(); - println!("SQL query result: {:?}", ret); - Ok(vec![]) + let mut ret = async_std::task::block_on(async { + // Surrealdb's grammar is not compatible with sea-orm's + // so we need to remove the extra clauses + // from "SELECT `from`.`col` FROM `from` WHERE `from`.`col` = xx" + // to "SELECT `col` FROM `from` WHERE `col` = xx" + + // Get the first index of "FROM" + let from_index = sql.find("FROM").unwrap(); + // Get the name after "FROM" + let from_name = sql[from_index + 5..].split(' ').next().unwrap(); + // Delete the name before all the columns + let new_sql = sql.replace(&format!("{}.", from_name), ""); + + self.mem.query(new_sql).await + }) + .unwrap(); + + // Convert the result to sea-orm's format + let ret: Vec = ret.take(0).unwrap(); + println!("SQL query result: {}", serde_json::to_string(&ret).unwrap()); + let ret = ret + .iter() + .map(|row| { + let mut map = serde_json::Map::new(); + for (k, v) in row.as_object().unwrap().iter() { + if k == "id" { + // Get `tb` and `id` columns from surrealdb + // and convert them to sea-orm's `id` + let tb = v.as_object().unwrap().get("tb").unwrap().to_string(); + let id = v + .as_object() + .unwrap() + .get("id") + .unwrap() + .get("String") + .unwrap(); + + // Remove the quotes + let tb = tb.to_string().replace("\"", ""); + let id = id.to_string().replace("\"", ""); + + map.insert("id".to_owned(), format!("{}:{}", tb, id).into()); + continue; + } + + map.insert(k.to_owned(), v.to_owned()); + } + serde_json::Value::Object(map) + }) + .map(|v: serde_json::Value| { + let mut ret: BTreeMap = BTreeMap::new(); + for (k, v) in v.as_object().unwrap().iter() { + ret.insert( + k.to_owned(), + match v { + serde_json::Value::Bool(b) => { + sea_orm::Value::TinyInt(if *b { Some(1) } else { Some(0) }) + } + serde_json::Value::Number(n) => { + if n.is_i64() { + sea_orm::Value::BigInt(Some(n.as_i64().unwrap())) + } else if n.is_u64() { + sea_orm::Value::BigUnsigned(Some(n.as_u64().unwrap())) + } else if n.is_f64() { + sea_orm::Value::Double(Some(n.as_f64().unwrap())) + } else { + unreachable!() + } + } + serde_json::Value::String(s) => { + sea_orm::Value::String(Some(Box::new(s.to_owned()))) + } + _ => sea_orm::Value::Json(Some(Box::new(v.to_owned()))), + }, + ); + } + ProxyRow { values: ret } + }) + .collect::>(); + + Ok(ret) } fn execute(&self, statement: Statement) -> Result { From a3d6a0d161a737394af38346af6c4a71bcc7ea91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Tue, 17 Oct 2023 17:25:28 +0800 Subject: [PATCH 18/49] refactor: Rename the example. --- .../{proxy_conn_example => proxy_surrealdb_example}/Cargo.toml | 2 +- .../src/entity/mod.rs | 0 .../src/entity/post.rs | 0 .../{proxy_conn_example => proxy_surrealdb_example}/src/main.rs | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename examples/{proxy_conn_example => proxy_surrealdb_example}/Cargo.toml (93%) rename examples/{proxy_conn_example => proxy_surrealdb_example}/src/entity/mod.rs (100%) rename examples/{proxy_conn_example => proxy_surrealdb_example}/src/entity/post.rs (100%) rename examples/{proxy_conn_example => proxy_surrealdb_example}/src/main.rs (100%) diff --git a/examples/proxy_conn_example/Cargo.toml b/examples/proxy_surrealdb_example/Cargo.toml similarity index 93% rename from examples/proxy_conn_example/Cargo.toml rename to examples/proxy_surrealdb_example/Cargo.toml index 4cfe79f0c..74f7ca1f9 100644 --- a/examples/proxy_conn_example/Cargo.toml +++ b/examples/proxy_surrealdb_example/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "sea-orm-proxy-conn-example" +name = "sea-orm-proxy-surrealdb-example" version = "0.1.0" authors = ["Langyo "] edition = "2021" diff --git a/examples/proxy_conn_example/src/entity/mod.rs b/examples/proxy_surrealdb_example/src/entity/mod.rs similarity index 100% rename from examples/proxy_conn_example/src/entity/mod.rs rename to examples/proxy_surrealdb_example/src/entity/mod.rs diff --git a/examples/proxy_conn_example/src/entity/post.rs b/examples/proxy_surrealdb_example/src/entity/post.rs similarity index 100% rename from examples/proxy_conn_example/src/entity/post.rs rename to examples/proxy_surrealdb_example/src/entity/post.rs diff --git a/examples/proxy_conn_example/src/main.rs b/examples/proxy_surrealdb_example/src/main.rs similarity index 100% rename from examples/proxy_conn_example/src/main.rs rename to examples/proxy_surrealdb_example/src/main.rs From 96389267744ab3558bbbf948830085111555550c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Tue, 17 Oct 2023 19:45:38 +0800 Subject: [PATCH 19/49] chore: Ready to add example for wasmtime proxy. --- examples/proxy_surrealdb_example/Cargo.toml | 1 - examples/proxy_wasmtime_example/Cargo.toml | 9 ++ .../proxy_wasmtime_example/module/Cargo.toml | 11 ++ .../proxy_wasmtime_example/module/src/main.rs | 28 +++++ examples/proxy_wasmtime_example/vm/Cargo.toml | 21 ++++ .../res/wasi_snapshot_preview1.command.wasm | Bin 0 -> 82997 bytes .../proxy_wasmtime_example/vm/src/main.rs | 83 +++++++++++++ .../proxy_wasmtime_example/vm/src/runtime.rs | 114 ++++++++++++++++++ .../proxy_wasmtime_example/vm/src/stream.rs | 66 ++++++++++ 9 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 examples/proxy_wasmtime_example/Cargo.toml create mode 100644 examples/proxy_wasmtime_example/module/Cargo.toml create mode 100644 examples/proxy_wasmtime_example/module/src/main.rs create mode 100644 examples/proxy_wasmtime_example/vm/Cargo.toml create mode 100644 examples/proxy_wasmtime_example/vm/res/wasi_snapshot_preview1.command.wasm create mode 100644 examples/proxy_wasmtime_example/vm/src/main.rs create mode 100644 examples/proxy_wasmtime_example/vm/src/runtime.rs create mode 100644 examples/proxy_wasmtime_example/vm/src/stream.rs diff --git a/examples/proxy_surrealdb_example/Cargo.toml b/examples/proxy_surrealdb_example/Cargo.toml index 74f7ca1f9..bd9106a71 100644 --- a/examples/proxy_surrealdb_example/Cargo.toml +++ b/examples/proxy_surrealdb_example/Cargo.toml @@ -6,7 +6,6 @@ edition = "2021" publish = false [workspace] -members = ["."] [dependencies] async-std = { version = "1.12", features = ["attributes", "tokio1"] } diff --git a/examples/proxy_wasmtime_example/Cargo.toml b/examples/proxy_wasmtime_example/Cargo.toml new file mode 100644 index 000000000..ae8b0cc70 --- /dev/null +++ b/examples/proxy_wasmtime_example/Cargo.toml @@ -0,0 +1,9 @@ +[workspace] +members = ["module", "vm"] +resolver = "2" + +[profile.release] +lto = true +opt-level = 'z' +codegen-units = 1 +panic = "abort" diff --git a/examples/proxy_wasmtime_example/module/Cargo.toml b/examples/proxy_wasmtime_example/module/Cargo.toml new file mode 100644 index 000000000..f93526eac --- /dev/null +++ b/examples/proxy_wasmtime_example/module/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "sea-orm-proxy-wasmtime-example-module" +version = "0.1.0" +authors = ["Langyo "] +edition = "2021" +publish = false + +[dependencies] +ron = "0.8" +serde = { version = "1", features = ["derive"] } +anyhow = "1" diff --git a/examples/proxy_wasmtime_example/module/src/main.rs b/examples/proxy_wasmtime_example/module/src/main.rs new file mode 100644 index 000000000..3ca9912e5 --- /dev/null +++ b/examples/proxy_wasmtime_example/module/src/main.rs @@ -0,0 +1,28 @@ +use anyhow::Result; +use std::io; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Msg { + pub id: u32, + pub data: String, +} + +fn main() -> Result<()> { + loop { + let mut buffer = String::new(); + let stdin = io::stdin(); + stdin.read_line(&mut buffer)?; + + let msg: Msg = ron::from_str(&buffer)?; + + let ret = Msg { + id: msg.id + 1, + data: msg.data + " hahaha", + }; + let ret = ron::to_string(&ret)?; + + println!("{}", ret); + } +} diff --git a/examples/proxy_wasmtime_example/vm/Cargo.toml b/examples/proxy_wasmtime_example/vm/Cargo.toml new file mode 100644 index 000000000..3bd422a3e --- /dev/null +++ b/examples/proxy_wasmtime_example/vm/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "sea-orm-proxy-wasmtime-example-vm" +version = "0.1.0" +authors = ["Langyo "] +edition = "2021" +publish = false + +[dependencies] +anyhow = "1" +bytes = "1" +async-trait = "0.1" +ron = "0.8" +serde = { version = "1", features = ["derive"] } +reqwest = { version = "0.11", features = ["blocking"] } +lazy_static = "1" +flume = "0.11" +async-std = { version = "1", features = ["attributes", "tokio1"] } + +wit-component = "0.15" +wasmtime = { version = "13", features = ["component-model"] } +wasmtime-wasi = "13" diff --git a/examples/proxy_wasmtime_example/vm/res/wasi_snapshot_preview1.command.wasm b/examples/proxy_wasmtime_example/vm/res/wasi_snapshot_preview1.command.wasm new file mode 100644 index 0000000000000000000000000000000000000000..b1baf57dfa92b0b3b6dd0fe941d4dacca204deb2 GIT binary patch literal 82997 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 BVZi_Z literal 0 HcmV?d00001 diff --git a/examples/proxy_wasmtime_example/vm/src/main.rs b/examples/proxy_wasmtime_example/vm/src/main.rs new file mode 100644 index 000000000..66d69215f --- /dev/null +++ b/examples/proxy_wasmtime_example/vm/src/main.rs @@ -0,0 +1,83 @@ +use anyhow::Result; +use bytes::Bytes; + +use wasmtime::{Config, Engine}; +use wit_component::ComponentEncoder; + +mod runtime; +mod stream; + +use {runtime::Runtime, stream::Msg}; + +#[async_std::main] +async fn main() -> Result<()> { + // Transfer the wasm binary to wasm component binary + + let adapter = include_bytes!("../res/wasi_snapshot_preview1.command.wasm"); + + let component = &ComponentEncoder::default() + .module(include_bytes!( + "../../target/wasm32-wasi/debug/sea-orm-proxy-wasmtime-example-module.wasm" + ))? + .validate(true) + .adapter("wasi_snapshot_preview1", adapter)? + .encode()?; + + let mut config = Config::new(); + config.wasm_component_model(true); + + let engine = &Engine::new(&config)?; + + let cwasm = engine.precompile_component(component)?; + let cwasm = Bytes::from(cwasm); + + // Run the prototype demo + + println!("Running prototype demo..."); + let entity_base = Runtime::new(cwasm); + + use std::time::Instant; + let now = Instant::now(); + + let mut threads = Vec::new(); + for index in 0..10 { + let mut entity = entity_base.clone(); + threads.push(std::thread::spawn(move || { + let mut runner = entity.init().unwrap(); + + let tx = runner.tx.clone(); + let rx = runner.rx.clone(); + + std::thread::spawn(move || { + runner.run().unwrap(); + }); + + let data = Msg { + id: 233, + data: "hello".to_string(), + }; + println!("#{index} Sending to vm: {:?}", data); + tx.send(data).unwrap(); + + let msg = rx.recv().unwrap(); + println!("#{index} Received on main: {:?}", msg); + + let data = Msg { + id: 23333, + data: "hi".to_string(), + }; + println!("#{index} Sending to vm: {:?}", data); + tx.send(data).unwrap(); + + let msg = rx.recv().unwrap(); + println!("#{index} Received on main: {:?}", msg); + })); + } + + for thread in threads { + thread.join().unwrap(); + } + println!("Time elapsed: {:?}", now.elapsed()); + + Ok(()) +} diff --git a/examples/proxy_wasmtime_example/vm/src/runtime.rs b/examples/proxy_wasmtime_example/vm/src/runtime.rs new file mode 100644 index 000000000..5411ab959 --- /dev/null +++ b/examples/proxy_wasmtime_example/vm/src/runtime.rs @@ -0,0 +1,114 @@ +use anyhow::{anyhow, Result}; +use bytes::Bytes; +use flume::{Receiver, Sender}; + +use wasmtime::{ + component::{Component, Linker}, + Config, Engine, Store, +}; +use wasmtime_wasi::preview2::{ + command::{self, sync::Command}, + Table, WasiCtx, WasiCtxBuilder, WasiView, +}; + +use crate::stream::{InputStream, Msg, OutputStream}; + +struct Ctx { + wasi: WasiCtx, + table: Table, +} + +impl WasiView for Ctx { + fn ctx(&self) -> &WasiCtx { + &self.wasi + } + fn ctx_mut(&mut self) -> &mut WasiCtx { + &mut self.wasi + } + fn table(&self) -> &Table { + &self.table + } + fn table_mut(&mut self) -> &mut Table { + &mut self.table + } +} + +#[derive(Clone)] +pub struct Runtime { + engine: Engine, + component: Component, +} + +pub struct Runner { + store: Store, + component: Component, + linker: Linker, + + pub tx: Sender, + pub rx: Receiver, +} + +impl Runtime { + pub fn new(bin: Bytes) -> Self { + let mut config = Config::new(); + config.wasm_component_model(true); + let engine = Engine::new(&config).unwrap(); + + let component = unsafe { Component::deserialize(&engine, &bin).unwrap() }; + + Self { engine, component } + } + + pub fn init(&mut self) -> Result { + let mut linker = Linker::new(&self.engine); + command::sync::add_to_linker(&mut linker).unwrap(); + + let mut table = Table::new(); + let mut wasi = WasiCtxBuilder::new(); + wasi.inherit_stderr(); + + let (tx_in, rx_in) = flume::unbounded(); + let (tx_out, rx_out) = flume::unbounded(); + + let input_stream = InputStream { + tasks: Default::default(), + }; + let output_stream = OutputStream { tx: tx_out }; + + let rx = rx_in.clone(); + let tasks = input_stream.tasks.clone(); + std::thread::spawn(move || { + while let Ok(msg) = rx.recv() { + tasks.lock().unwrap().push(msg); + } + }); + + wasi.stdin(input_stream, wasmtime_wasi::preview2::IsATTY::No); + wasi.stdout(output_stream, wasmtime_wasi::preview2::IsATTY::No); + + let wasi = wasi.build(&mut table).unwrap(); + let store = Store::new(&self.engine, Ctx { wasi, table }); + + Ok(Runner { + store, + component: self.component.clone(), + linker, + + tx: tx_in, + rx: rx_out, + }) + } +} + +impl Runner { + pub fn run(&mut self) -> Result<()> { + let (command, _) = Command::instantiate(&mut self.store, &self.component, &self.linker)?; + + command + .wasi_cli_run() + .call_run(&mut self.store)? + .map_err(|()| anyhow!("guest command returned error"))?; + + Ok(()) + } +} diff --git a/examples/proxy_wasmtime_example/vm/src/stream.rs b/examples/proxy_wasmtime_example/vm/src/stream.rs new file mode 100644 index 000000000..ebda805e8 --- /dev/null +++ b/examples/proxy_wasmtime_example/vm/src/stream.rs @@ -0,0 +1,66 @@ +use anyhow::Result; +use bytes::Bytes; +use flume::Sender; +use serde::{Deserialize, Serialize}; +use std::sync::{Arc, Mutex}; + +use wasmtime_wasi::preview2::{HostInputStream, HostOutputStream, OutputStreamError, StreamState}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Msg { + pub id: u32, + pub data: String, +} + +pub struct InputStream { + pub tasks: Arc>>, +} + +#[async_trait::async_trait] +impl HostInputStream for InputStream { + fn read(&mut self, _size: usize) -> Result<(Bytes, StreamState)> { + loop { + { + let mut tasks = self.tasks.lock().unwrap(); + if tasks.len() > 0 { + let ret = tasks.remove(0); + let ret = ron::to_string(&ret).unwrap() + "\n"; + let ret = Bytes::from(ret); + + return Ok((ret, StreamState::Open)); + } + } + std::thread::sleep(std::time::Duration::from_millis(100)); + } + } + + async fn ready(&mut self) -> Result<()> { + Ok(()) + } +} + +pub struct OutputStream { + pub tx: Sender, +} + +#[async_trait::async_trait] +impl HostOutputStream for OutputStream { + fn write(&mut self, bytes: Bytes) -> Result<(), OutputStreamError> { + let msg = + String::from_utf8(bytes.to_vec()).map_err(|e| OutputStreamError::Trap(e.into()))?; + let msg = ron::from_str::(&msg).map_err(|e| OutputStreamError::Trap(e.into()))?; + + self.tx + .send(msg) + .map_err(|e| OutputStreamError::Trap(e.into()))?; + Ok(()) + } + + fn flush(&mut self) -> Result<(), OutputStreamError> { + Ok(()) + } + + async fn write_ready(&mut self) -> Result { + Ok(8192) + } +} From 4e51c869734361e6967507b6a96238a09a61c324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Tue, 17 Oct 2023 22:40:14 +0800 Subject: [PATCH 20/49] feat: Try to compile sea-orm into wasm binary. But it would failed on wasm32-wasi target because of the socket deps. It can be compiled on wasm32-unknown-unknown target. --- .../proxy_wasmtime_example/module/Cargo.toml | 7 ++++ .../proxy_wasmtime_example/module/src/main.rs | 40 ++++++++++++------- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/examples/proxy_wasmtime_example/module/Cargo.toml b/examples/proxy_wasmtime_example/module/Cargo.toml index f93526eac..d9cd3a7fc 100644 --- a/examples/proxy_wasmtime_example/module/Cargo.toml +++ b/examples/proxy_wasmtime_example/module/Cargo.toml @@ -9,3 +9,10 @@ publish = false ron = "0.8" serde = { version = "1", features = ["derive"] } anyhow = "1" +async-std = { version = "1", features = ["attributes", "tokio1"] } + +sea-orm = { path = "../../../", default-features = false, features = [ + "macros", + "proxy", + "runtime-async-std", +] } diff --git a/examples/proxy_wasmtime_example/module/src/main.rs b/examples/proxy_wasmtime_example/module/src/main.rs index 3ca9912e5..6a36d4205 100644 --- a/examples/proxy_wasmtime_example/module/src/main.rs +++ b/examples/proxy_wasmtime_example/module/src/main.rs @@ -1,7 +1,10 @@ use anyhow::Result; -use std::io; - use serde::{Deserialize, Serialize}; +use std::sync::{Arc, Mutex}; + +use sea_orm::{ + Database, DbBackend, DbErr, ProxyDatabaseTrait, ProxyExecResult, ProxyRow, Statement, +}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Msg { @@ -9,20 +12,29 @@ pub struct Msg { pub data: String, } -fn main() -> Result<()> { - loop { - let mut buffer = String::new(); - let stdin = io::stdin(); - stdin.read_line(&mut buffer)?; +#[derive(Debug)] +struct ProxyDb {} - let msg: Msg = ron::from_str(&buffer)?; +impl ProxyDatabaseTrait for ProxyDb { + fn query(&self, statement: Statement) -> Result, DbErr> { + Ok(vec![]) + } - let ret = Msg { - id: msg.id + 1, - data: msg.data + " hahaha", - }; - let ret = ron::to_string(&ret)?; + fn execute(&self, statement: Statement) -> Result { + Ok(ProxyExecResult { + last_insert_id: 1, + rows_affected: 1, + }) + } +} - println!("{}", ret); +#[async_std::main] +async fn main() { + if let Ok(db) = + Database::connect_proxy(DbBackend::MySql, Arc::new(Mutex::new(Box::new(ProxyDb {})))).await + { + println!("Initialized {:?}", db); + } else { + println!("Failed to initialize"); } } From f818e66fb27333135ad45070bef50828ccec797a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Thu, 19 Oct 2023 11:29:14 +0800 Subject: [PATCH 21/49] fix: WASM targets can't use sqlx. --- Cargo.toml | 102 +++++++++++++++--- .../proxy_wasmtime_example/module/Cargo.toml | 4 +- .../proxy_wasmtime_example/module/src/main.rs | 2 +- 3 files changed, 90 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 14a03b41a..a9c5466eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,15 @@ keywords = ["async", "orm", "mysql", "postgres", "sqlite"] rust-version = "1.65" [package.metadata.docs.rs] -features = ["default", "sqlx-all", "mock", "proxy", "runtime-async-std-native-tls", "postgres-array", "sea-orm-internal"] +features = [ + "default", + "sqlx-all", + "mock", + "proxy", + "runtime-async-std-native-tls", + "postgres-array", + "sea-orm-internal", +] rustdoc-args = ["--cfg", "docsrs"] [lib] @@ -30,21 +38,34 @@ chrono = { version = "0.4.30", default-features = false, optional = true } time = { version = "0.3", default-features = false, optional = true } futures = { version = "0.3", default-features = false, features = ["std"] } log = { version = "0.4", default-features = false } -tracing = { version = "0.1", default-features = false, features = ["attributes", "log"] } +tracing = { version = "0.1", default-features = false, features = [ + "attributes", + "log", +] } rust_decimal = { version = "1", default-features = false, optional = true } bigdecimal = { version = "0.3", default-features = false, optional = true } -sea-orm-macros = { version = "0.12.3", path = "sea-orm-macros", default-features = false, features = ["strum"] } -sea-query = { version = "0.30.2", default-features = false, features = ["thread-safe", "hashable-value", "backend-mysql", "backend-postgres", "backend-sqlite"] } +sea-orm-macros = { version = "0.12.3", path = "sea-orm-macros", default-features = false, features = [ + "strum", +] } +sea-query = { version = "0.30.2", default-features = false, features = [ + "thread-safe", + "hashable-value", + "backend-mysql", + "backend-postgres", + "backend-sqlite", +] } sea-query-binder = { version = "0.5.0", default-features = false, optional = true } strum = { version = "0.25", default-features = false } serde = { version = "1.0", default-features = false } serde_json = { version = "1.0", default-features = false, optional = true } -sqlx = { version = "0.7", default-features = false, optional = true } uuid = { version = "1", default-features = false, optional = true } ouroboros = { version = "0.17", default-features = false } url = { version = "2.2", default-features = false } thiserror = { version = "1", default-features = false } +[target.'cfg(not(wasm))'.dependencies] +sqlx = { version = "0.7", default-features = false, optional = true } + [dev-dependencies] smol = { version = "1.2" } smol-potat = { version = "1.1" } @@ -55,7 +76,14 @@ actix-rt = { version = "2.2.0" } maplit = { version = "1" } rust_decimal_macros = { version = "1" } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } -sea-orm = { path = ".", features = ["mock", "proxy", "debug-print", "tests-cfg", "postgres-array", "sea-orm-internal"] } +sea-orm = { path = ".", features = [ + "mock", + "proxy", + "debug-print", + "tests-cfg", + "postgres-array", + "sea-orm-internal", +] } pretty_assertions = { version = "0.7" } time = { version = "0.3", features = ["macros"] } uuid = { version = "1", features = ["v4"] } @@ -77,19 +105,63 @@ default = [ macros = ["sea-orm-macros/derive"] mock = [] proxy = [] -with-json = ["serde_json", "sea-query/with-json", "chrono?/serde", "time?/serde", "uuid?/serde", "sea-query-binder?/with-json", "sqlx?/json"] -with-chrono = ["chrono", "sea-query/with-chrono", "sea-query-binder?/with-chrono", "sqlx?/chrono"] -with-rust_decimal = ["rust_decimal", "sea-query/with-rust_decimal", "sea-query-binder?/with-rust_decimal", "sqlx?/rust_decimal"] -with-bigdecimal = ["bigdecimal", "sea-query/with-bigdecimal", "sea-query-binder?/with-bigdecimal", "sqlx?/bigdecimal"] -with-uuid = ["uuid", "sea-query/with-uuid", "sea-query-binder?/with-uuid", "sqlx?/uuid"] -with-time = ["time", "sea-query/with-time", "sea-query-binder?/with-time", "sqlx?/time"] -postgres-array = ["sea-query/postgres-array", "sea-query-binder?/postgres-array", "sea-orm-macros/postgres-array"] -json-array = ["postgres-array"] # this does not actually enable sqlx-postgres, but only a few traits to support array in sea-query +with-json = [ + "serde_json", + "sea-query/with-json", + "chrono?/serde", + "time?/serde", + "uuid?/serde", + "sea-query-binder?/with-json", + "sqlx?/json", +] +with-chrono = [ + "chrono", + "sea-query/with-chrono", + "sea-query-binder?/with-chrono", + "sqlx?/chrono", +] +with-rust_decimal = [ + "rust_decimal", + "sea-query/with-rust_decimal", + "sea-query-binder?/with-rust_decimal", + "sqlx?/rust_decimal", +] +with-bigdecimal = [ + "bigdecimal", + "sea-query/with-bigdecimal", + "sea-query-binder?/with-bigdecimal", + "sqlx?/bigdecimal", +] +with-uuid = [ + "uuid", + "sea-query/with-uuid", + "sea-query-binder?/with-uuid", + "sqlx?/uuid", +] +with-time = [ + "time", + "sea-query/with-time", + "sea-query-binder?/with-time", + "sqlx?/time", +] +postgres-array = [ + "sea-query/postgres-array", + "sea-query-binder?/postgres-array", + "sea-orm-macros/postgres-array", +] +json-array = [ + "postgres-array", +] # this does not actually enable sqlx-postgres, but only a few traits to support array in sea-query sea-orm-internal = [] sqlx-dep = [] sqlx-all = ["sqlx-mysql", "sqlx-postgres", "sqlx-sqlite"] sqlx-mysql = ["sqlx-dep", "sea-query-binder/sqlx-mysql", "sqlx/mysql"] -sqlx-postgres = ["sqlx-dep", "sea-query-binder/sqlx-postgres", "sqlx/postgres", "postgres-array"] +sqlx-postgres = [ + "sqlx-dep", + "sea-query-binder/sqlx-postgres", + "sqlx/postgres", + "postgres-array", +] sqlx-sqlite = ["sqlx-dep", "sea-query-binder/sqlx-sqlite", "sqlx/sqlite"] runtime-async-std = [] runtime-async-std-native-tls = [ diff --git a/examples/proxy_wasmtime_example/module/Cargo.toml b/examples/proxy_wasmtime_example/module/Cargo.toml index d9cd3a7fc..91cd77afb 100644 --- a/examples/proxy_wasmtime_example/module/Cargo.toml +++ b/examples/proxy_wasmtime_example/module/Cargo.toml @@ -9,10 +9,10 @@ publish = false ron = "0.8" serde = { version = "1", features = ["derive"] } anyhow = "1" -async-std = { version = "1", features = ["attributes", "tokio1"] } +tokio_wasi = { version = "1", features = ["full"] } sea-orm = { path = "../../../", default-features = false, features = [ "macros", "proxy", - "runtime-async-std", + "runtime-tokio", ] } diff --git a/examples/proxy_wasmtime_example/module/src/main.rs b/examples/proxy_wasmtime_example/module/src/main.rs index 6a36d4205..a05dff5b7 100644 --- a/examples/proxy_wasmtime_example/module/src/main.rs +++ b/examples/proxy_wasmtime_example/module/src/main.rs @@ -28,7 +28,7 @@ impl ProxyDatabaseTrait for ProxyDb { } } -#[async_std::main] +#[tokio::main] async fn main() { if let Ok(db) = Database::connect_proxy(DbBackend::MySql, Arc::new(Mutex::new(Box::new(ProxyDb {})))).await From 82b0075db1683e8831ca6cf5f3ad156dcff3cedc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Thu, 19 Oct 2023 23:17:54 +0800 Subject: [PATCH 22/49] fix: Try to fix CI by remove toml. --- examples/proxy_wasmtime_example/Cargo.toml | 9 --------- examples/proxy_wasmtime_example/module/Cargo.toml | 6 ++++++ 2 files changed, 6 insertions(+), 9 deletions(-) delete mode 100644 examples/proxy_wasmtime_example/Cargo.toml diff --git a/examples/proxy_wasmtime_example/Cargo.toml b/examples/proxy_wasmtime_example/Cargo.toml deleted file mode 100644 index ae8b0cc70..000000000 --- a/examples/proxy_wasmtime_example/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[workspace] -members = ["module", "vm"] -resolver = "2" - -[profile.release] -lto = true -opt-level = 'z' -codegen-units = 1 -panic = "abort" diff --git a/examples/proxy_wasmtime_example/module/Cargo.toml b/examples/proxy_wasmtime_example/module/Cargo.toml index 91cd77afb..be17322d6 100644 --- a/examples/proxy_wasmtime_example/module/Cargo.toml +++ b/examples/proxy_wasmtime_example/module/Cargo.toml @@ -16,3 +16,9 @@ sea-orm = { path = "../../../", default-features = false, features = [ "proxy", "runtime-tokio", ] } + +[profile.release] +lto = true +opt-level = 'z' +codegen-units = 1 +panic = "abort" From 05416c755cd30a7e9486ecfd1e2022c666cbf6af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Thu, 19 Oct 2023 23:21:28 +0800 Subject: [PATCH 23/49] fix: Try to fix CI by remove toml. --- examples/proxy_wasmtime_example/Cargo.toml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/examples/proxy_wasmtime_example/Cargo.toml b/examples/proxy_wasmtime_example/Cargo.toml index ae8b0cc70..4765ad6c8 100644 --- a/examples/proxy_wasmtime_example/Cargo.toml +++ b/examples/proxy_wasmtime_example/Cargo.toml @@ -1,5 +1,12 @@ +[package] +name = "sea-orm-proxy-wasmtime-example" +version = "0.1.0" +authors = ["Langyo "] +edition = "2021" +publish = false + [workspace] -members = ["module", "vm"] +members = [".", "module", "vm"] resolver = "2" [profile.release] From 9a1c594582f3fa6875ddf0de073c673f23819905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Thu, 19 Oct 2023 23:26:45 +0800 Subject: [PATCH 24/49] fix: Move vm to the example's root dir. --- examples/proxy_wasmtime_example/Cargo.toml | 21 +++++++++++++----- .../proxy_wasmtime_example/module/Cargo.toml | 4 ++++ .../res/wasi_snapshot_preview1.command.wasm | Bin .../{vm => }/src/main.rs | 2 +- .../{vm => }/src/runtime.rs | 0 .../{vm => }/src/stream.rs | 0 examples/proxy_wasmtime_example/vm/Cargo.toml | 21 ------------------ 7 files changed, 20 insertions(+), 28 deletions(-) rename examples/proxy_wasmtime_example/{vm => }/res/wasi_snapshot_preview1.command.wasm (100%) rename examples/proxy_wasmtime_example/{vm => }/src/main.rs (95%) rename examples/proxy_wasmtime_example/{vm => }/src/runtime.rs (100%) rename examples/proxy_wasmtime_example/{vm => }/src/stream.rs (100%) delete mode 100644 examples/proxy_wasmtime_example/vm/Cargo.toml diff --git a/examples/proxy_wasmtime_example/Cargo.toml b/examples/proxy_wasmtime_example/Cargo.toml index 4765ad6c8..73b21e589 100644 --- a/examples/proxy_wasmtime_example/Cargo.toml +++ b/examples/proxy_wasmtime_example/Cargo.toml @@ -6,11 +6,20 @@ edition = "2021" publish = false [workspace] -members = [".", "module", "vm"] +members = [".", "module"] resolver = "2" -[profile.release] -lto = true -opt-level = 'z' -codegen-units = 1 -panic = "abort" +[dependencies] +anyhow = "1" +bytes = "1" +async-trait = "0.1" +ron = "0.8" +serde = { version = "1", features = ["derive"] } +reqwest = { version = "0.11", features = ["blocking"] } +lazy_static = "1" +flume = "0.11" +async-std = { version = "1", features = ["attributes", "tokio1"] } + +wit-component = "0.15" +wasmtime = { version = "13", features = ["component-model"] } +wasmtime-wasi = "13" diff --git a/examples/proxy_wasmtime_example/module/Cargo.toml b/examples/proxy_wasmtime_example/module/Cargo.toml index be17322d6..f4d0134e3 100644 --- a/examples/proxy_wasmtime_example/module/Cargo.toml +++ b/examples/proxy_wasmtime_example/module/Cargo.toml @@ -5,6 +5,10 @@ authors = ["Langyo "] edition = "2021" publish = false +[lib] +name = "module" +path = "src/main.rs" + [dependencies] ron = "0.8" serde = { version = "1", features = ["derive"] } diff --git a/examples/proxy_wasmtime_example/vm/res/wasi_snapshot_preview1.command.wasm b/examples/proxy_wasmtime_example/res/wasi_snapshot_preview1.command.wasm similarity index 100% rename from examples/proxy_wasmtime_example/vm/res/wasi_snapshot_preview1.command.wasm rename to examples/proxy_wasmtime_example/res/wasi_snapshot_preview1.command.wasm diff --git a/examples/proxy_wasmtime_example/vm/src/main.rs b/examples/proxy_wasmtime_example/src/main.rs similarity index 95% rename from examples/proxy_wasmtime_example/vm/src/main.rs rename to examples/proxy_wasmtime_example/src/main.rs index 66d69215f..308d47c0a 100644 --- a/examples/proxy_wasmtime_example/vm/src/main.rs +++ b/examples/proxy_wasmtime_example/src/main.rs @@ -17,7 +17,7 @@ async fn main() -> Result<()> { let component = &ComponentEncoder::default() .module(include_bytes!( - "../../target/wasm32-wasi/debug/sea-orm-proxy-wasmtime-example-module.wasm" + "../target/wasm32-wasi/debug/sea-orm-proxy-wasmtime-example-module.wasm" ))? .validate(true) .adapter("wasi_snapshot_preview1", adapter)? diff --git a/examples/proxy_wasmtime_example/vm/src/runtime.rs b/examples/proxy_wasmtime_example/src/runtime.rs similarity index 100% rename from examples/proxy_wasmtime_example/vm/src/runtime.rs rename to examples/proxy_wasmtime_example/src/runtime.rs diff --git a/examples/proxy_wasmtime_example/vm/src/stream.rs b/examples/proxy_wasmtime_example/src/stream.rs similarity index 100% rename from examples/proxy_wasmtime_example/vm/src/stream.rs rename to examples/proxy_wasmtime_example/src/stream.rs diff --git a/examples/proxy_wasmtime_example/vm/Cargo.toml b/examples/proxy_wasmtime_example/vm/Cargo.toml deleted file mode 100644 index 3bd422a3e..000000000 --- a/examples/proxy_wasmtime_example/vm/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "sea-orm-proxy-wasmtime-example-vm" -version = "0.1.0" -authors = ["Langyo "] -edition = "2021" -publish = false - -[dependencies] -anyhow = "1" -bytes = "1" -async-trait = "0.1" -ron = "0.8" -serde = { version = "1", features = ["derive"] } -reqwest = { version = "0.11", features = ["blocking"] } -lazy_static = "1" -flume = "0.11" -async-std = { version = "1", features = ["attributes", "tokio1"] } - -wit-component = "0.15" -wasmtime = { version = "13", features = ["component-model"] } -wasmtime-wasi = "13" From b87ce9201bee5d90b72582dff1b4953eb38fe70d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Fri, 20 Oct 2023 11:01:24 +0800 Subject: [PATCH 25/49] fix: Add a pre-build script. --- examples/proxy_wasmtime_example/build.rs | 17 +++++++++++++++++ examples/proxy_wasmtime_example/src/main.rs | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 examples/proxy_wasmtime_example/build.rs diff --git a/examples/proxy_wasmtime_example/build.rs b/examples/proxy_wasmtime_example/build.rs new file mode 100644 index 000000000..7d2493137 --- /dev/null +++ b/examples/proxy_wasmtime_example/build.rs @@ -0,0 +1,17 @@ +use std::env; +use std::process::Command; + +fn main() { + let pwd = env::current_dir().unwrap(); + + Command::new("cargo") + .current_dir(pwd.join("module")) + .arg("build") + .arg("--target") + .arg("wasm32-wasi") + .arg("--package") + .arg("sea-orm-proxy-wasmtime-example-module") + .arg("--release") + .status() + .unwrap(); +} diff --git a/examples/proxy_wasmtime_example/src/main.rs b/examples/proxy_wasmtime_example/src/main.rs index 308d47c0a..9279a061e 100644 --- a/examples/proxy_wasmtime_example/src/main.rs +++ b/examples/proxy_wasmtime_example/src/main.rs @@ -17,7 +17,7 @@ async fn main() -> Result<()> { let component = &ComponentEncoder::default() .module(include_bytes!( - "../target/wasm32-wasi/debug/sea-orm-proxy-wasmtime-example-module.wasm" + "../target/wasm32-wasi/release/sea-orm-proxy-wasmtime-example-module.wasm" ))? .validate(true) .adapter("wasi_snapshot_preview1", adapter)? From 933f952346d767b51614662d4212961ffc902dec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Fri, 20 Oct 2023 11:26:08 +0800 Subject: [PATCH 26/49] chore: Add README. --- examples/proxy_surrealdb_example/README.md | 7 +++++++ examples/proxy_wasmtime_example/README.md | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 examples/proxy_surrealdb_example/README.md create mode 100644 examples/proxy_wasmtime_example/README.md diff --git a/examples/proxy_surrealdb_example/README.md b/examples/proxy_surrealdb_example/README.md new file mode 100644 index 000000000..773850bc3 --- /dev/null +++ b/examples/proxy_surrealdb_example/README.md @@ -0,0 +1,7 @@ +# SeaORM Proxy Demo for SurrealDB + +Run this demo for [SurrealDB](https://surrealdb.com/) with the following command: + +```bash +cargo run +``` diff --git a/examples/proxy_wasmtime_example/README.md b/examples/proxy_wasmtime_example/README.md new file mode 100644 index 000000000..faa0a4980 --- /dev/null +++ b/examples/proxy_wasmtime_example/README.md @@ -0,0 +1,7 @@ +# SeaORM Proxy Demo on WASI + +Run this demo by [wasmtime](https://wasmtime.dev/) with the following command: + +```bash +cargo run +``` From 6bae09fbf2ee6b182ac77ebe9d302b188cdddc3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Fri, 20 Oct 2023 13:55:20 +0800 Subject: [PATCH 27/49] fix: Try to fix CI. --- examples/proxy_wasmtime_example/Cargo.toml | 3 ++- examples/proxy_wasmtime_example/build.rs | 2 +- examples/proxy_wasmtime_example/module/Cargo.toml | 12 +----------- examples/proxy_wasmtime_example/src/main.rs | 5 +---- 4 files changed, 5 insertions(+), 17 deletions(-) diff --git a/examples/proxy_wasmtime_example/Cargo.toml b/examples/proxy_wasmtime_example/Cargo.toml index 73b21e589..1b510dd8d 100644 --- a/examples/proxy_wasmtime_example/Cargo.toml +++ b/examples/proxy_wasmtime_example/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" authors = ["Langyo "] edition = "2021" publish = false +build = "build.rs" [workspace] members = [".", "module"] @@ -20,6 +21,6 @@ lazy_static = "1" flume = "0.11" async-std = { version = "1", features = ["attributes", "tokio1"] } -wit-component = "0.15" +wit-component = "0.16" wasmtime = { version = "13", features = ["component-model"] } wasmtime-wasi = "13" diff --git a/examples/proxy_wasmtime_example/build.rs b/examples/proxy_wasmtime_example/build.rs index 7d2493137..4dcfa5b9d 100644 --- a/examples/proxy_wasmtime_example/build.rs +++ b/examples/proxy_wasmtime_example/build.rs @@ -10,7 +10,7 @@ fn main() { .arg("--target") .arg("wasm32-wasi") .arg("--package") - .arg("sea-orm-proxy-wasmtime-example-module") + .arg("module") .arg("--release") .status() .unwrap(); diff --git a/examples/proxy_wasmtime_example/module/Cargo.toml b/examples/proxy_wasmtime_example/module/Cargo.toml index f4d0134e3..98771beb2 100644 --- a/examples/proxy_wasmtime_example/module/Cargo.toml +++ b/examples/proxy_wasmtime_example/module/Cargo.toml @@ -1,14 +1,10 @@ [package] -name = "sea-orm-proxy-wasmtime-example-module" +name = "module" version = "0.1.0" authors = ["Langyo "] edition = "2021" publish = false -[lib] -name = "module" -path = "src/main.rs" - [dependencies] ron = "0.8" serde = { version = "1", features = ["derive"] } @@ -20,9 +16,3 @@ sea-orm = { path = "../../../", default-features = false, features = [ "proxy", "runtime-tokio", ] } - -[profile.release] -lto = true -opt-level = 'z' -codegen-units = 1 -panic = "abort" diff --git a/examples/proxy_wasmtime_example/src/main.rs b/examples/proxy_wasmtime_example/src/main.rs index 9279a061e..1880f61a4 100644 --- a/examples/proxy_wasmtime_example/src/main.rs +++ b/examples/proxy_wasmtime_example/src/main.rs @@ -12,13 +12,10 @@ use {runtime::Runtime, stream::Msg}; #[async_std::main] async fn main() -> Result<()> { // Transfer the wasm binary to wasm component binary - let adapter = include_bytes!("../res/wasi_snapshot_preview1.command.wasm"); let component = &ComponentEncoder::default() - .module(include_bytes!( - "../target/wasm32-wasi/release/sea-orm-proxy-wasmtime-example-module.wasm" - ))? + .module(include_bytes!("../target/wasm32-wasi/release/module.wasm"))? .validate(true) .adapter("wasi_snapshot_preview1", adapter)? .encode()?; From 2fc95b03b36b06765756f594f4818b0a0354b6c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Fri, 20 Oct 2023 14:50:48 +0800 Subject: [PATCH 28/49] feat: Add proxy logic in wasm module. --- examples/proxy_wasmtime_example/Cargo.toml | 1 + .../proxy_wasmtime_example/module/Cargo.toml | 8 +- .../module/src/entity/mod.rs | 1 + .../module/src/entity/post.rs | 17 ++ .../proxy_wasmtime_example/module/src/main.rs | 196 ++++++++++++++++-- src/database/proxy.rs | 2 +- 6 files changed, 200 insertions(+), 25 deletions(-) create mode 100644 examples/proxy_wasmtime_example/module/src/entity/mod.rs create mode 100644 examples/proxy_wasmtime_example/module/src/entity/post.rs diff --git a/examples/proxy_wasmtime_example/Cargo.toml b/examples/proxy_wasmtime_example/Cargo.toml index 1b510dd8d..a50f7f189 100644 --- a/examples/proxy_wasmtime_example/Cargo.toml +++ b/examples/proxy_wasmtime_example/Cargo.toml @@ -24,3 +24,4 @@ async-std = { version = "1", features = ["attributes", "tokio1"] } wit-component = "0.16" wasmtime = { version = "13", features = ["component-model"] } wasmtime-wasi = "13" +surrealdb = { version = "1", features = ["kv-mem"] } diff --git a/examples/proxy_wasmtime_example/module/Cargo.toml b/examples/proxy_wasmtime_example/module/Cargo.toml index 98771beb2..b32eaf0ef 100644 --- a/examples/proxy_wasmtime_example/module/Cargo.toml +++ b/examples/proxy_wasmtime_example/module/Cargo.toml @@ -6,13 +6,9 @@ edition = "2021" publish = false [dependencies] -ron = "0.8" +serde_json = "1" serde = { version = "1", features = ["derive"] } anyhow = "1" tokio_wasi = { version = "1", features = ["full"] } -sea-orm = { path = "../../../", default-features = false, features = [ - "macros", - "proxy", - "runtime-tokio", -] } +sea-orm = { path = "../../../", features = ["proxy", "runtime-tokio"] } diff --git a/examples/proxy_wasmtime_example/module/src/entity/mod.rs b/examples/proxy_wasmtime_example/module/src/entity/mod.rs new file mode 100644 index 000000000..e8b6291ac --- /dev/null +++ b/examples/proxy_wasmtime_example/module/src/entity/mod.rs @@ -0,0 +1 @@ +pub mod post; diff --git a/examples/proxy_wasmtime_example/module/src/entity/post.rs b/examples/proxy_wasmtime_example/module/src/entity/post.rs new file mode 100644 index 000000000..1fde0c4cc --- /dev/null +++ b/examples/proxy_wasmtime_example/module/src/entity/post.rs @@ -0,0 +1,17 @@ +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Deserialize, Serialize)] +#[sea_orm(table_name = "posts")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: String, + + pub title: String, + pub text: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/proxy_wasmtime_example/module/src/main.rs b/examples/proxy_wasmtime_example/module/src/main.rs index a05dff5b7..1f9b3d5c7 100644 --- a/examples/proxy_wasmtime_example/module/src/main.rs +++ b/examples/proxy_wasmtime_example/module/src/main.rs @@ -1,15 +1,36 @@ -use anyhow::Result; +//! Proxy connection example. + +#![deny(missing_docs)] + +mod entity; + use serde::{Deserialize, Serialize}; -use std::sync::{Arc, Mutex}; +use std::{ + collections::BTreeMap, + sync::{Arc, Mutex}, +}; use sea_orm::{ - Database, DbBackend, DbErr, ProxyDatabaseTrait, ProxyExecResult, ProxyRow, Statement, + ActiveValue::Set, Database, DbBackend, DbErr, EntityTrait, ProxyDatabaseTrait, ProxyExecResult, + ProxyRow, Statement, }; +use entity::post::{ActiveModel, Entity}; + #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Msg { - pub id: u32, - pub data: String, +pub(crate) enum RequestMsg { + Query(String), + Execute(String), + + Debug(String), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub(crate) enum ResponseMsg { + Query(Vec), + Execute(ProxyExecResult), + + None, } #[derive(Debug)] @@ -17,24 +38,163 @@ struct ProxyDb {} impl ProxyDatabaseTrait for ProxyDb { fn query(&self, statement: Statement) -> Result, DbErr> { - Ok(vec![]) + let sql = statement.sql.clone(); + + // Surrealdb's grammar is not compatible with sea-orm's + // so we need to remove the extra clauses + // from "SELECT `from`.`col` FROM `from` WHERE `from`.`col` = xx" + // to "SELECT `col` FROM `from` WHERE `col` = xx" + + // Get the first index of "FROM" + let from_index = sql.find("FROM").unwrap(); + // Get the name after "FROM" + let from_name = sql[from_index + 5..].split(' ').next().unwrap(); + // Delete the name before all the columns + let new_sql = sql.replace(&format!("{}.", from_name), ""); + + // Send the query to stdout + let msg = RequestMsg::Query(new_sql); + let msg = serde_json::to_string(&msg).unwrap(); + println!("{}", msg); + + // Get the result from stdin + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + + // Convert the result to sea-orm's format + let ret: ResponseMsg = serde_json::from_str(&input).unwrap(); + let ret = match ret { + ResponseMsg::Query(v) => v, + _ => unreachable!(), + }; + let ret = ret + .iter() + .map(|row| { + let mut map = serde_json::Map::new(); + for (k, v) in row.as_object().unwrap().iter() { + if k == "id" { + // Get `tb` and `id` columns from surrealdb + // and convert them to sea-orm's `id` + let tb = v.as_object().unwrap().get("tb").unwrap().to_string(); + let id = v + .as_object() + .unwrap() + .get("id") + .unwrap() + .get("String") + .unwrap(); + + // Remove the quotes + let tb = tb.to_string().replace("\"", ""); + let id = id.to_string().replace("\"", ""); + + map.insert("id".to_owned(), format!("{}:{}", tb, id).into()); + continue; + } + + map.insert(k.to_owned(), v.to_owned()); + } + serde_json::Value::Object(map) + }) + .map(|v: serde_json::Value| { + let mut ret: BTreeMap = BTreeMap::new(); + for (k, v) in v.as_object().unwrap().iter() { + ret.insert( + k.to_owned(), + match v { + serde_json::Value::Bool(b) => { + sea_orm::Value::TinyInt(if *b { Some(1) } else { Some(0) }) + } + serde_json::Value::Number(n) => { + if n.is_i64() { + sea_orm::Value::BigInt(Some(n.as_i64().unwrap())) + } else if n.is_u64() { + sea_orm::Value::BigUnsigned(Some(n.as_u64().unwrap())) + } else if n.is_f64() { + sea_orm::Value::Double(Some(n.as_f64().unwrap())) + } else { + unreachable!() + } + } + serde_json::Value::String(s) => { + sea_orm::Value::String(Some(Box::new(s.to_owned()))) + } + _ => sea_orm::Value::Json(Some(Box::new(v.to_owned()))), + }, + ); + } + ProxyRow { values: ret } + }) + .collect::>(); + + Ok(ret) } fn execute(&self, statement: Statement) -> Result { - Ok(ProxyExecResult { - last_insert_id: 1, - rows_affected: 1, - }) + let sql = { + if let Some(values) = statement.values { + // Replace all the '?' with the statement values + let mut new_sql = statement.sql.clone(); + let mark_count = new_sql.matches('?').count(); + for (i, v) in values.0.iter().enumerate() { + if i >= mark_count { + break; + } + new_sql = new_sql.replacen('?', &v.to_string(), 1); + } + + new_sql + } else { + statement.sql + } + }; + + // Send the query to stdout + let msg = RequestMsg::Execute(sql); + let msg = serde_json::to_string(&msg).unwrap(); + println!("{}", msg); + + // Get the result from stdin + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + let ret: ResponseMsg = serde_json::from_str(&input).unwrap(); + let ret = match ret { + ResponseMsg::Execute(v) => v, + _ => unreachable!(), + }; + + Ok(ret) } } #[tokio::main] async fn main() { - if let Ok(db) = - Database::connect_proxy(DbBackend::MySql, Arc::new(Mutex::new(Box::new(ProxyDb {})))).await - { - println!("Initialized {:?}", db); - } else { - println!("Failed to initialize"); - } + let db = Database::connect_proxy(DbBackend::MySql, Arc::new(Mutex::new(Box::new(ProxyDb {})))) + .await + .unwrap(); + + let data = ActiveModel { + title: Set("Homo".to_owned()), + text: Set("いいよ、来いよ".to_owned()), + ..Default::default() + }; + Entity::insert(data).exec(&db).await.unwrap(); + let data = ActiveModel { + title: Set("Homo".to_owned()), + text: Set("そうだよ".to_owned()), + ..Default::default() + }; + Entity::insert(data).exec(&db).await.unwrap(); + let data = ActiveModel { + title: Set("Homo".to_owned()), + text: Set("悔い改めて".to_owned()), + ..Default::default() + }; + Entity::insert(data).exec(&db).await.unwrap(); + + let list = Entity::find().all(&db).await.unwrap().to_vec(); + println!( + "{}", + serde_json::to_string(&RequestMsg::Debug(format!("{:?}", list))).unwrap() + ); } diff --git a/src/database/proxy.rs b/src/database/proxy.rs index 53c3d59e0..e22c951fe 100644 --- a/src/database/proxy.rs +++ b/src/database/proxy.rs @@ -29,7 +29,7 @@ pub trait ProxyDatabaseTrait: Send + Sync + std::fmt::Debug { /// Defines the results obtained from a [ProxyDatabase] #[cfg(feature = "proxy")] -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] pub struct ProxyExecResult { /// The last inserted id on auto-increment pub last_insert_id: u64, From c2e4c2adf3c8d2cc3e585b4ca6f216dd8f9fc5f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Fri, 20 Oct 2023 16:51:51 +0800 Subject: [PATCH 29/49] fix: Try to run the wasi module. But WASI cannot support multi threads.. so the module was run failed. --- examples/proxy_wasmtime_example/Cargo.toml | 9 ++- examples/proxy_wasmtime_example/build.rs | 17 ---- examples/proxy_wasmtime_example/src/main.rs | 79 ++++++++----------- .../proxy_wasmtime_example/src/runtime.rs | 8 +- examples/proxy_wasmtime_example/src/stream.rs | 26 ++++-- 5 files changed, 64 insertions(+), 75 deletions(-) delete mode 100644 examples/proxy_wasmtime_example/build.rs diff --git a/examples/proxy_wasmtime_example/Cargo.toml b/examples/proxy_wasmtime_example/Cargo.toml index a50f7f189..92d0a0c46 100644 --- a/examples/proxy_wasmtime_example/Cargo.toml +++ b/examples/proxy_wasmtime_example/Cargo.toml @@ -4,7 +4,6 @@ version = "0.1.0" authors = ["Langyo "] edition = "2021" publish = false -build = "build.rs" [workspace] members = [".", "module"] @@ -16,12 +15,18 @@ bytes = "1" async-trait = "0.1" ron = "0.8" serde = { version = "1", features = ["derive"] } +serde_json = "1" reqwest = { version = "0.11", features = ["blocking"] } lazy_static = "1" flume = "0.11" -async-std = { version = "1", features = ["attributes", "tokio1"] } +tokio = { version = "1", features = ["full"] } wit-component = "0.16" wasmtime = { version = "13", features = ["component-model"] } wasmtime-wasi = "13" surrealdb = { version = "1", features = ["kv-mem"] } +sea-orm = { path = "../..", features = [ + "proxy", + "sqlx-sqlite", + "runtime-tokio-rustls", +] } diff --git a/examples/proxy_wasmtime_example/build.rs b/examples/proxy_wasmtime_example/build.rs deleted file mode 100644 index 4dcfa5b9d..000000000 --- a/examples/proxy_wasmtime_example/build.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::env; -use std::process::Command; - -fn main() { - let pwd = env::current_dir().unwrap(); - - Command::new("cargo") - .current_dir(pwd.join("module")) - .arg("build") - .arg("--target") - .arg("wasm32-wasi") - .arg("--package") - .arg("module") - .arg("--release") - .status() - .unwrap(); -} diff --git a/examples/proxy_wasmtime_example/src/main.rs b/examples/proxy_wasmtime_example/src/main.rs index 1880f61a4..59765a959 100644 --- a/examples/proxy_wasmtime_example/src/main.rs +++ b/examples/proxy_wasmtime_example/src/main.rs @@ -1,5 +1,6 @@ use anyhow::Result; use bytes::Bytes; +use std::{env, path::Path, process::Command}; use wasmtime::{Config, Engine}; use wit_component::ComponentEncoder; @@ -7,15 +8,31 @@ use wit_component::ComponentEncoder; mod runtime; mod stream; -use {runtime::Runtime, stream::Msg}; +use { + runtime::Runtime, + stream::{RequestMsg, ResponseMsg}, +}; + +fn main() -> Result<()> { + // Build the wasm component binary + let pwd = Path::new(env!("CARGO_MANIFEST_DIR")).to_path_buf(); + Command::new("cargo") + .current_dir(pwd.clone()) + .arg("build") + .arg("--target") + .arg("wasm32-wasi") + .arg("--package") + .arg("module") + .arg("--release") + .status() + .unwrap(); -#[async_std::main] -async fn main() -> Result<()> { // Transfer the wasm binary to wasm component binary let adapter = include_bytes!("../res/wasi_snapshot_preview1.command.wasm"); - + let component = pwd.join("target/wasm32-wasi/release/module.wasm"); + let component = std::fs::read(component)?; let component = &ComponentEncoder::default() - .module(include_bytes!("../target/wasm32-wasi/release/module.wasm"))? + .module(&component)? .validate(true) .adapter("wasi_snapshot_preview1", adapter)? .encode()?; @@ -31,50 +48,20 @@ async fn main() -> Result<()> { // Run the prototype demo println!("Running prototype demo..."); - let entity_base = Runtime::new(cwasm); - - use std::time::Instant; - let now = Instant::now(); - - let mut threads = Vec::new(); - for index in 0..10 { - let mut entity = entity_base.clone(); - threads.push(std::thread::spawn(move || { - let mut runner = entity.init().unwrap(); - - let tx = runner.tx.clone(); - let rx = runner.rx.clone(); + let mut runner = Runtime::new(cwasm).init()?; - std::thread::spawn(move || { - runner.run().unwrap(); - }); + let tx = runner.tx.clone(); + let rx = runner.rx.clone(); - let data = Msg { - id: 233, - data: "hello".to_string(), - }; - println!("#{index} Sending to vm: {:?}", data); - tx.send(data).unwrap(); + std::thread::spawn(move || { + runner.run().unwrap(); + }); - let msg = rx.recv().unwrap(); - println!("#{index} Received on main: {:?}", msg); + loop { + let msg = rx.recv()?; + println!("Received on main: {:?}", msg); - let data = Msg { - id: 23333, - data: "hi".to_string(), - }; - println!("#{index} Sending to vm: {:?}", data); - tx.send(data).unwrap(); - - let msg = rx.recv().unwrap(); - println!("#{index} Received on main: {:?}", msg); - })); - } - - for thread in threads { - thread.join().unwrap(); + // TODO - Send the result + loop {} } - println!("Time elapsed: {:?}", now.elapsed()); - - Ok(()) } diff --git a/examples/proxy_wasmtime_example/src/runtime.rs b/examples/proxy_wasmtime_example/src/runtime.rs index 5411ab959..c687181a5 100644 --- a/examples/proxy_wasmtime_example/src/runtime.rs +++ b/examples/proxy_wasmtime_example/src/runtime.rs @@ -11,7 +11,9 @@ use wasmtime_wasi::preview2::{ Table, WasiCtx, WasiCtxBuilder, WasiView, }; -use crate::stream::{InputStream, Msg, OutputStream}; +use crate::stream::{ + InputStream, OutputStream, {RequestMsg, ResponseMsg}, +}; struct Ctx { wasi: WasiCtx, @@ -44,8 +46,8 @@ pub struct Runner { component: Component, linker: Linker, - pub tx: Sender, - pub rx: Receiver, + pub tx: Sender, + pub rx: Receiver, } impl Runtime { diff --git a/examples/proxy_wasmtime_example/src/stream.rs b/examples/proxy_wasmtime_example/src/stream.rs index ebda805e8..4f3f77e46 100644 --- a/examples/proxy_wasmtime_example/src/stream.rs +++ b/examples/proxy_wasmtime_example/src/stream.rs @@ -4,16 +4,27 @@ use flume::Sender; use serde::{Deserialize, Serialize}; use std::sync::{Arc, Mutex}; +use sea_orm::ProxyExecResult; use wasmtime_wasi::preview2::{HostInputStream, HostOutputStream, OutputStreamError, StreamState}; #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Msg { - pub id: u32, - pub data: String, +pub enum RequestMsg { + Query(String), + Execute(String), + + Debug(String), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum ResponseMsg { + Query(Vec), + Execute(ProxyExecResult), + + None, } pub struct InputStream { - pub tasks: Arc>>, + pub tasks: Arc>>, } #[async_trait::async_trait] @@ -24,7 +35,7 @@ impl HostInputStream for InputStream { let mut tasks = self.tasks.lock().unwrap(); if tasks.len() > 0 { let ret = tasks.remove(0); - let ret = ron::to_string(&ret).unwrap() + "\n"; + let ret = serde_json::to_string(&ret).unwrap() + "\n"; let ret = Bytes::from(ret); return Ok((ret, StreamState::Open)); @@ -40,7 +51,7 @@ impl HostInputStream for InputStream { } pub struct OutputStream { - pub tx: Sender, + pub tx: Sender, } #[async_trait::async_trait] @@ -48,7 +59,8 @@ impl HostOutputStream for OutputStream { fn write(&mut self, bytes: Bytes) -> Result<(), OutputStreamError> { let msg = String::from_utf8(bytes.to_vec()).map_err(|e| OutputStreamError::Trap(e.into()))?; - let msg = ron::from_str::(&msg).map_err(|e| OutputStreamError::Trap(e.into()))?; + let msg = serde_json::from_str::(&msg) + .map_err(|e| OutputStreamError::Trap(e.into()))?; self.tx .send(msg) From fc20fa9603cde48b28099d3a32583135ad637bdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Sat, 21 Oct 2023 15:24:55 +0800 Subject: [PATCH 30/49] refactor: Bump wasmtime to 14. --- examples/proxy_wasmtime_example/Cargo.toml | 9 +-- .../res/wasi_snapshot_preview1.command.wasm | Bin 82997 -> 96688 bytes .../proxy_wasmtime_example/src/runtime.rs | 15 ++-- examples/proxy_wasmtime_example/src/stream.rs | 73 +++++++++++++----- 4 files changed, 65 insertions(+), 32 deletions(-) diff --git a/examples/proxy_wasmtime_example/Cargo.toml b/examples/proxy_wasmtime_example/Cargo.toml index 92d0a0c46..83f40db05 100644 --- a/examples/proxy_wasmtime_example/Cargo.toml +++ b/examples/proxy_wasmtime_example/Cargo.toml @@ -13,20 +13,19 @@ resolver = "2" anyhow = "1" bytes = "1" async-trait = "0.1" -ron = "0.8" serde = { version = "1", features = ["derive"] } serde_json = "1" reqwest = { version = "0.11", features = ["blocking"] } lazy_static = "1" flume = "0.11" -tokio = { version = "1", features = ["full"] } +async-std = { version = "^1", features = ["attributes", "tokio1"] } wit-component = "0.16" -wasmtime = { version = "13", features = ["component-model"] } -wasmtime-wasi = "13" +wasmtime = { version = "14", features = ["component-model"] } +wasmtime-wasi = "14" surrealdb = { version = "1", features = ["kv-mem"] } sea-orm = { path = "../..", features = [ "proxy", "sqlx-sqlite", - "runtime-tokio-rustls", + "runtime-async-std-rustls", ] } diff --git a/examples/proxy_wasmtime_example/res/wasi_snapshot_preview1.command.wasm b/examples/proxy_wasmtime_example/res/wasi_snapshot_preview1.command.wasm index b1baf57dfa92b0b3b6dd0fe941d4dacca204deb2..f0997c61985e76ebd1c3db0229c9a6f7973f1805 100644 GIT binary patch literal 96688 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 z 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%0D+BJ4;XKRkqpEB7jA_Tb% z6r214f?w5}cJG_suAfZS`xq$AqvjA|)@qGRVIkYf7R#Bk3lGfGcQ5MA9s@ReCL_Ne zyEMu>ovCG8=|UE&aJtqiHqzB+>^?HKZHFWM;Mpczi?A#j3+Wu(OU>#tckkNTwkL!g zVPG;hL(Ogpv3BVC_;l?MU=hq{Wy=MaHLYf`0jKM;cJGDXw)f1S_fdT|7xJY>tCi1Y zYYTap8P${O)NhdY34}mLB`RHm=aqxfcvitn&xT$s?||Xj@ZRWmVQQC3jfHHpn1vbF zXx7Tv>a(A<}N#m%Vt5=_M%MJ}>GPlb7p^CKh$sCrK zkTJ9lH{vqfi0L}?^K816&6a`9RW&vD9%dYQQ+sY)n%)pHH$QWSj?^RvMmr+Mg%(r+ zC@r;&+bCyq)mvs{ON{ySmeAg<3GF@L&9^=Gyp3=1IA9LVX1SCr=Aa>#(#2+Lq58c2 zUZZ|L&!3iIVN6JN`+^yDO9PNSfBQtMiLg4Okhd+Ke-MEPcX2siZ>4Jst#lpqnXWZk z)!P$nj^Kv3<%MzF&~N0vXgBjSg6B*4wk_tDX)d%1aPz>&0e>h|&=<3Lh6d@jf5|j% zT1{zxVJ6>kaBxctNm|w4c_{d>%Wt0dzH1+!GCWG#4R!C3#k>bzZ0@@co5g&q>w7pY z#PMs*m(HjwubG!|2zVp!5%*bQ*4xVo;SI^(zBh(TsqpRukA;l#z332Hzr&054&VXq zo5{D}qjgJ}R=QcP-Z`loL}#_POARkm+)=|Dlpl@;-wI(`!%3s2KG*1U-T8TZ*}Dt1 zv|DTU5o_Ze%gxXGH5eS`2X8RMT&-AWxlpz^|@0m z{qnK7_A8eMANlKiYC^uCLAa_eX_2l?%copLzFAlO4971z30;a9uz{>ij77Wr?@r1) z!x!I87*5w7lW7`DR6hHY@)B_Gxtoa|1Z8vSY};LJo}ScxQK0Ub1PzBJ^@WssCgcqc zI6r+>U2^Z*E2iaB?<+U2+y%R4z6uu8b;~8URLB*|aOy&rxp(*Z6+1f`r6Q&LUd82d z9;aXD5qZp|OQj-M@qL%98-Fo5n_L}HfM&j&M(P53tlqzS#d_!2DE+LsTrX#lgVc1> z^>n#Z%(~Tcm#hb}6wCqsM|YtmQ(l_&QpT-5AR0SR%lV9QGhFREvWm;Knu{!&5d^6_<-ex{2T!&u8n!TD@LBX>i3D2 zON0oSA&(RYv{-OqOqFT{H(iF&TYa6lRB0)h!WK7Fak)__!K0FILeFc}8<~3b_2QL} zVR9GbdV*4uE~eA)^}#yM6-xPh^+ECU9eOXU)%eZu{6-f!vmjBJ5Xd+Ln2Pl>{C2JC z_lt|O%mQ}O4#ouZzy&0c)Jye7x$b7*k*)rK*h!buBhI8;lFO2RRH^H=RK~ z&_WZA)i;QpJo2L&$qyQ2v}zTtLI)^k5eO`o3JY#NQ>xCduAW|-FCj!)ER@rQ9F%XE z%eg}4jp7YdV9H8=QJRNJo}Qn-O(sz@`RyglL^D+snJT&^jT26)E}}ZHy}o1p46X)M zQ9YA~E+bzqZOXS8XAW#Gi-3Cx+H4*P7}YmDp~K0STG<5%-BP2Oqh(rs^AkFpVkwsc z<(C%_w8@mgK7UZWMfo^YUXWTXD8Bd-&-^@+2Jx#v{_JDbF|AmpRL-YcO`vXmpGfYlwg&U-+$0{Ft)&ayCN@4f8xi~N+Z;bpm(X3Z-JB6tM3s1g><)F5GIpX{(UlE z$s`qFVnf7fw+qUAZnv}c7*mL=OtpMAY%iazv7JcvrLvA!+jUsf>$DNETZ);P>m3%& zwa_;hEmF7AU?XsGr<;vZzEpju_@f=CR8Uc#F-~cakNpzl0Pk*I&xNyq8N=q(O0^99 zA<+Etg@ppb=+$?L=k2gqLFIfXhE9>oU}HlKZm@g~N=B~w!xx}-n{x>0LP4J~bPTvd z>M)CnF2W40Qla`D@v@7WOrRIvw*cLwnZXA&;bSPnjL1~qD=yBZA83Aye-@ zm-0D;;XoYaMxpv+;yW+6U6~N3vV{DGOPNBmfe=BioL_)ylC3^0Ua;GSx9-q^^4@Af zr@-8cAd6x%UwxnW_KRvSN2`$R4r#H9=VE z1x5|3?-wt+s1{3}R}PI9vdBS)icoC0E$E%)>IcM5X{UjO55Q4{$Yh48bhpEG*`epZ zj4(qMur1bH7)a&j_9*ug;)NGAog$oJOdv*QZp&>TOweRcW#s%+Ke!|7++j8{%`b?{ zIY0>b*1!_vVJbAM9}+LUr~#`C$039+mI~$4Lb}|{F4PgGNLPPSOmkxN|F}zF0zW0D zrGMJH|FAg3_uA)YMFV>@4oVPp}5_eXX%^7WLAN7Ge=F=F*vFx=_kh zKPC#>*DInPQ@&9VI()X=Ea%)DZN+-7`qSs@5Hmx)EG%^H20)y(g%pfp_2VLnR`K!^ z@&d#9q=@N$en!NX5LLjqv$2wG?OXa^0|gUs+_WGcywjCY2uuO@!6HJMNdP_c7<>^s z9ho?C67$kafqU^8S%QF@7#UD@J~Fa`HZ~^98tRz?cQz_$Bqy5z_j&<`6BLYqxmMTE zK-a)nlR<|ccq=$4_R>-lft%1%i#hK}Oy1>D80OpK7 A3IG5A literal 82997 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 BVZi_Z diff --git a/examples/proxy_wasmtime_example/src/runtime.rs b/examples/proxy_wasmtime_example/src/runtime.rs index c687181a5..789dbf057 100644 --- a/examples/proxy_wasmtime_example/src/runtime.rs +++ b/examples/proxy_wasmtime_example/src/runtime.rs @@ -12,9 +12,8 @@ use wasmtime_wasi::preview2::{ }; use crate::stream::{ - InputStream, OutputStream, {RequestMsg, ResponseMsg}, + HostInputStreamBox, HostOutputStreamBox, {RequestMsg, ResponseMsg}, }; - struct Ctx { wasi: WasiCtx, table: Table, @@ -65,17 +64,16 @@ impl Runtime { let mut linker = Linker::new(&self.engine); command::sync::add_to_linker(&mut linker).unwrap(); - let mut table = Table::new(); let mut wasi = WasiCtxBuilder::new(); wasi.inherit_stderr(); let (tx_in, rx_in) = flume::unbounded(); let (tx_out, rx_out) = flume::unbounded(); - let input_stream = InputStream { + let input_stream = HostInputStreamBox { tasks: Default::default(), }; - let output_stream = OutputStream { tx: tx_out }; + let output_stream = HostOutputStreamBox { tx: tx_out }; let rx = rx_in.clone(); let tasks = input_stream.tasks.clone(); @@ -85,10 +83,11 @@ impl Runtime { } }); - wasi.stdin(input_stream, wasmtime_wasi::preview2::IsATTY::No); - wasi.stdout(output_stream, wasmtime_wasi::preview2::IsATTY::No); + wasi.stdin(input_stream); + wasi.stdout(output_stream); - let wasi = wasi.build(&mut table).unwrap(); + let wasi = wasi.build(); + let table = Table::new(); let store = Store::new(&self.engine, Ctx { wasi, table }); Ok(Runner { diff --git a/examples/proxy_wasmtime_example/src/stream.rs b/examples/proxy_wasmtime_example/src/stream.rs index 4f3f77e46..517ad0643 100644 --- a/examples/proxy_wasmtime_example/src/stream.rs +++ b/examples/proxy_wasmtime_example/src/stream.rs @@ -1,11 +1,12 @@ -use anyhow::Result; use bytes::Bytes; use flume::Sender; use serde::{Deserialize, Serialize}; use std::sync::{Arc, Mutex}; use sea_orm::ProxyExecResult; -use wasmtime_wasi::preview2::{HostInputStream, HostOutputStream, OutputStreamError, StreamState}; +use wasmtime_wasi::preview2::{ + HostInputStream, HostOutputStream, StdinStream, StdoutStream, StreamResult, Subscribe, +}; #[derive(Clone, Debug, Serialize, Deserialize)] pub enum RequestMsg { @@ -27,9 +28,14 @@ pub struct InputStream { pub tasks: Arc>>, } +#[async_trait::async_trait] +impl Subscribe for InputStream { + async fn ready(&mut self) {} +} + #[async_trait::async_trait] impl HostInputStream for InputStream { - fn read(&mut self, _size: usize) -> Result<(Bytes, StreamState)> { + fn read(&mut self, _size: usize) -> StreamResult { loop { { let mut tasks = self.tasks.lock().unwrap(); @@ -38,41 +44,70 @@ impl HostInputStream for InputStream { let ret = serde_json::to_string(&ret).unwrap() + "\n"; let ret = Bytes::from(ret); - return Ok((ret, StreamState::Open)); + return Ok(ret); } } std::thread::sleep(std::time::Duration::from_millis(100)); } } - - async fn ready(&mut self) -> Result<()> { - Ok(()) - } } pub struct OutputStream { pub tx: Sender, } +#[async_trait::async_trait] +impl Subscribe for OutputStream { + async fn ready(&mut self) {} +} + #[async_trait::async_trait] impl HostOutputStream for OutputStream { - fn write(&mut self, bytes: Bytes) -> Result<(), OutputStreamError> { - let msg = - String::from_utf8(bytes.to_vec()).map_err(|e| OutputStreamError::Trap(e.into()))?; - let msg = serde_json::from_str::(&msg) - .map_err(|e| OutputStreamError::Trap(e.into()))?; - - self.tx - .send(msg) - .map_err(|e| OutputStreamError::Trap(e.into()))?; + fn write(&mut self, bytes: Bytes) -> StreamResult<()> { + let msg = String::from_utf8(bytes.to_vec()).expect("Failed to parse message"); + let msg = serde_json::from_str::(&msg).expect("Failed to parse message"); + + self.tx.send(msg).expect("Failed to send message"); Ok(()) } - fn flush(&mut self) -> Result<(), OutputStreamError> { + fn flush(&mut self) -> StreamResult<()> { Ok(()) } - async fn write_ready(&mut self) -> Result { + fn check_write(&mut self) -> StreamResult { Ok(8192) } } + +pub struct HostInputStreamBox { + pub tasks: Arc>>, +} + +impl StdinStream for HostInputStreamBox { + fn stream(&self) -> Box { + Box::new(InputStream { + tasks: self.tasks.clone(), + }) + } + + fn isatty(&self) -> bool { + false + } +} + +pub struct HostOutputStreamBox { + pub tx: Sender, +} + +impl StdoutStream for HostOutputStreamBox { + fn stream(&self) -> Box { + Box::new(OutputStream { + tx: self.tx.clone(), + }) + } + + fn isatty(&self) -> bool { + false + } +} From f0d374103dab1a63dafbcdf79d52dcf661d09569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Sat, 21 Oct 2023 16:35:58 +0800 Subject: [PATCH 31/49] fix: Now we can use async traits on wasmtime. The solution is add the current thread tag to tokio-wasi. --- examples/proxy_wasmtime_example/module/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/proxy_wasmtime_example/module/src/main.rs b/examples/proxy_wasmtime_example/module/src/main.rs index 1f9b3d5c7..cc8d7e4b4 100644 --- a/examples/proxy_wasmtime_example/module/src/main.rs +++ b/examples/proxy_wasmtime_example/module/src/main.rs @@ -167,7 +167,7 @@ impl ProxyDatabaseTrait for ProxyDb { } } -#[tokio::main] +#[tokio::main(flavor = "current_thread")] async fn main() { let db = Database::connect_proxy(DbBackend::MySql, Arc::new(Mutex::new(Box::new(ProxyDb {})))) .await From defa6bf809c650eb4b6305d15babb34468b7a444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Sun, 22 Oct 2023 00:24:46 +0800 Subject: [PATCH 32/49] build: Use build.rs instead of dynamic command. --- examples/proxy_wasmtime_example/Cargo.toml | 1 + examples/proxy_wasmtime_example/README.md | 1 + examples/proxy_wasmtime_example/build.rs | 16 ++++++++++++++++ examples/proxy_wasmtime_example/src/main.rs | 14 +------------- 4 files changed, 19 insertions(+), 13 deletions(-) create mode 100644 examples/proxy_wasmtime_example/build.rs diff --git a/examples/proxy_wasmtime_example/Cargo.toml b/examples/proxy_wasmtime_example/Cargo.toml index 83f40db05..a3145e35e 100644 --- a/examples/proxy_wasmtime_example/Cargo.toml +++ b/examples/proxy_wasmtime_example/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" authors = ["Langyo "] edition = "2021" publish = false +build = "build.rs" [workspace] members = [".", "module"] diff --git a/examples/proxy_wasmtime_example/README.md b/examples/proxy_wasmtime_example/README.md index faa0a4980..4735d6d2a 100644 --- a/examples/proxy_wasmtime_example/README.md +++ b/examples/proxy_wasmtime_example/README.md @@ -3,5 +3,6 @@ Run this demo by [wasmtime](https://wasmtime.dev/) with the following command: ```bash +cargo build --target wasm32-wasi --package module --release cargo run ``` diff --git a/examples/proxy_wasmtime_example/build.rs b/examples/proxy_wasmtime_example/build.rs new file mode 100644 index 000000000..55bbf1f74 --- /dev/null +++ b/examples/proxy_wasmtime_example/build.rs @@ -0,0 +1,16 @@ +use std::{env, path::Path, process::Command}; + +fn main() { + // Build the wasm component binary + let pwd = Path::new(env!("CARGO_MANIFEST_DIR")).to_path_buf(); + Command::new("cargo") + .current_dir(pwd.clone()) + .arg("build") + .arg("--target") + .arg("wasm32-wasi") + .arg("--package") + .arg("module") + .arg("--release") + .status() + .unwrap(); +} diff --git a/examples/proxy_wasmtime_example/src/main.rs b/examples/proxy_wasmtime_example/src/main.rs index 59765a959..0eb4dd99d 100644 --- a/examples/proxy_wasmtime_example/src/main.rs +++ b/examples/proxy_wasmtime_example/src/main.rs @@ -14,21 +14,9 @@ use { }; fn main() -> Result<()> { - // Build the wasm component binary - let pwd = Path::new(env!("CARGO_MANIFEST_DIR")).to_path_buf(); - Command::new("cargo") - .current_dir(pwd.clone()) - .arg("build") - .arg("--target") - .arg("wasm32-wasi") - .arg("--package") - .arg("module") - .arg("--release") - .status() - .unwrap(); - // Transfer the wasm binary to wasm component binary let adapter = include_bytes!("../res/wasi_snapshot_preview1.command.wasm"); + let pwd = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).to_path_buf(); let component = pwd.join("target/wasm32-wasi/release/module.wasm"); let component = std::fs::read(component)?; let component = &ComponentEncoder::default() From dc2faa4d85fc05a1b264226682b08e7cff8a2eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Sun, 22 Oct 2023 04:16:51 +0800 Subject: [PATCH 33/49] feat: Add the execute result's transfer logic. --- Cargo.toml | 2 +- examples/proxy_wasmtime_example/build.rs | 2 + .../proxy_wasmtime_example/module/src/main.rs | 89 +-------------- examples/proxy_wasmtime_example/src/main.rs | 46 +++++++- src/database/mock.rs | 3 +- src/database/proxy.rs | 107 +++++++++++++++++- 6 files changed, 151 insertions(+), 98 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 140363658..a04bbb942 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,7 +104,7 @@ default = [ ] macros = ["sea-orm-macros/derive"] mock = [] -proxy = [] +proxy = ["serde_json"] with-json = [ "serde_json", "sea-query/with-json", diff --git a/examples/proxy_wasmtime_example/build.rs b/examples/proxy_wasmtime_example/build.rs index 55bbf1f74..28f3a5440 100644 --- a/examples/proxy_wasmtime_example/build.rs +++ b/examples/proxy_wasmtime_example/build.rs @@ -13,4 +13,6 @@ fn main() { .arg("--release") .status() .unwrap(); + + println!("cargo:rerun-if-changed=module/**/*"); } diff --git a/examples/proxy_wasmtime_example/module/src/main.rs b/examples/proxy_wasmtime_example/module/src/main.rs index cc8d7e4b4..c7cc2673a 100644 --- a/examples/proxy_wasmtime_example/module/src/main.rs +++ b/examples/proxy_wasmtime_example/module/src/main.rs @@ -40,94 +40,7 @@ impl ProxyDatabaseTrait for ProxyDb { fn query(&self, statement: Statement) -> Result, DbErr> { let sql = statement.sql.clone(); - // Surrealdb's grammar is not compatible with sea-orm's - // so we need to remove the extra clauses - // from "SELECT `from`.`col` FROM `from` WHERE `from`.`col` = xx" - // to "SELECT `col` FROM `from` WHERE `col` = xx" - - // Get the first index of "FROM" - let from_index = sql.find("FROM").unwrap(); - // Get the name after "FROM" - let from_name = sql[from_index + 5..].split(' ').next().unwrap(); - // Delete the name before all the columns - let new_sql = sql.replace(&format!("{}.", from_name), ""); - - // Send the query to stdout - let msg = RequestMsg::Query(new_sql); - let msg = serde_json::to_string(&msg).unwrap(); - println!("{}", msg); - - // Get the result from stdin - let mut input = String::new(); - std::io::stdin().read_line(&mut input).unwrap(); - - // Convert the result to sea-orm's format - let ret: ResponseMsg = serde_json::from_str(&input).unwrap(); - let ret = match ret { - ResponseMsg::Query(v) => v, - _ => unreachable!(), - }; - let ret = ret - .iter() - .map(|row| { - let mut map = serde_json::Map::new(); - for (k, v) in row.as_object().unwrap().iter() { - if k == "id" { - // Get `tb` and `id` columns from surrealdb - // and convert them to sea-orm's `id` - let tb = v.as_object().unwrap().get("tb").unwrap().to_string(); - let id = v - .as_object() - .unwrap() - .get("id") - .unwrap() - .get("String") - .unwrap(); - - // Remove the quotes - let tb = tb.to_string().replace("\"", ""); - let id = id.to_string().replace("\"", ""); - - map.insert("id".to_owned(), format!("{}:{}", tb, id).into()); - continue; - } - - map.insert(k.to_owned(), v.to_owned()); - } - serde_json::Value::Object(map) - }) - .map(|v: serde_json::Value| { - let mut ret: BTreeMap = BTreeMap::new(); - for (k, v) in v.as_object().unwrap().iter() { - ret.insert( - k.to_owned(), - match v { - serde_json::Value::Bool(b) => { - sea_orm::Value::TinyInt(if *b { Some(1) } else { Some(0) }) - } - serde_json::Value::Number(n) => { - if n.is_i64() { - sea_orm::Value::BigInt(Some(n.as_i64().unwrap())) - } else if n.is_u64() { - sea_orm::Value::BigUnsigned(Some(n.as_u64().unwrap())) - } else if n.is_f64() { - sea_orm::Value::Double(Some(n.as_f64().unwrap())) - } else { - unreachable!() - } - } - serde_json::Value::String(s) => { - sea_orm::Value::String(Some(Box::new(s.to_owned()))) - } - _ => sea_orm::Value::Json(Some(Box::new(v.to_owned()))), - }, - ); - } - ProxyRow { values: ret } - }) - .collect::>(); - - Ok(ret) + Ok(vec![]) } fn execute(&self, statement: Statement) -> Result { diff --git a/examples/proxy_wasmtime_example/src/main.rs b/examples/proxy_wasmtime_example/src/main.rs index 0eb4dd99d..ed5282fff 100644 --- a/examples/proxy_wasmtime_example/src/main.rs +++ b/examples/proxy_wasmtime_example/src/main.rs @@ -1,7 +1,7 @@ use anyhow::Result; use bytes::Bytes; -use std::{env, path::Path, process::Command}; +use sea_orm::{ConnectionTrait, Database, DatabaseBackend, ProxyExecResult, ProxyRow, Statement}; use wasmtime::{Config, Engine}; use wit_component::ComponentEncoder; @@ -13,7 +13,8 @@ use { stream::{RequestMsg, ResponseMsg}, }; -fn main() -> Result<()> { +#[async_std::main] +async fn main() -> Result<()> { // Transfer the wasm binary to wasm component binary let adapter = include_bytes!("../res/wasi_snapshot_preview1.command.wasm"); let pwd = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).to_path_buf(); @@ -33,8 +34,22 @@ fn main() -> Result<()> { let cwasm = engine.precompile_component(component)?; let cwasm = Bytes::from(cwasm); - // Run the prototype demo + // Create the database connection + println!("Creating database connection..."); + let db = Database::connect("sqlite::memory:").await?; + db.execute(Statement::from_string( + DatabaseBackend::Sqlite, + r#" + CREATE TABLE IF NOT EXISTS posts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + text TEXT NOT NULL + ) + "#, + )) + .await?; + // Run the prototype demo println!("Running prototype demo..."); let mut runner = Runtime::new(cwasm).init()?; @@ -47,9 +62,28 @@ fn main() -> Result<()> { loop { let msg = rx.recv()?; - println!("Received on main: {:?}", msg); - // TODO - Send the result - loop {} + match msg { + RequestMsg::Execute(sql) => { + let ret: ProxyExecResult = db + .execute(Statement::from_string(DatabaseBackend::Sqlite, sql)) + .await? + .into(); + println!("Execute result: {:?}", ret); + let ret = ResponseMsg::Execute(ret); + tx.send(ret)?; + } + RequestMsg::Query(sql) => { + let ret: Vec = db + .query_all(Statement::from_string(DatabaseBackend::Sqlite, sql)) + .await? + .iter() + .map(|r| r.into()) + .collect(); + } + RequestMsg::Debug(msg) => { + println!("VM Debug: {}", msg); + } + } } } diff --git a/src/database/mock.rs b/src/database/mock.rs index e760ceaf7..6462766f2 100644 --- a/src/database/mock.rs +++ b/src/database/mock.rs @@ -30,7 +30,8 @@ pub struct MockExecResult { /// which is just a [BTreeMap]<[String], [Value]> #[derive(Clone, Debug)] pub struct MockRow { - values: BTreeMap, + /// The values of the single row + pub values: BTreeMap, } /// A trait to get a [MockRow] from a type useful for testing in the [MockDatabase] diff --git a/src/database/proxy.rs b/src/database/proxy.rs index e22c951fe..ef9615df8 100644 --- a/src/database/proxy.rs +++ b/src/database/proxy.rs @@ -68,8 +68,27 @@ impl From for ExecResult { impl From for ProxyExecResult { fn from(result: ExecResult) -> Self { match result.result { + #[cfg(feature = "sqlx-mysql")] + ExecResultHolder::SqlxMySql(result) => Self { + last_insert_id: result.last_insert_id() as u64, + rows_affected: result.rows_affected(), + }, + #[cfg(feature = "sqlx-postgres")] + ExecResultHolder::SqlxPostgres(result) => Self { + last_insert_id: result.last_insert_rowid() as u64, + rows_affected: result.rows_affected(), + }, + #[cfg(feature = "sqlx-sqlite")] + ExecResultHolder::SqlxSqlite(result) => Self { + last_insert_id: result.last_insert_rowid() as u64, + rows_affected: result.rows_affected(), + }, + #[cfg(feature = "mock")] + ExecResultHolder::Mock(result) => Self { + last_insert_id: result.last_insert_id, + rows_affected: result.rows_affected, + }, ExecResultHolder::Proxy(result) => result, - _ => unreachable!("Cannot convert ExecResult to ProxyExecResult"), } } } @@ -134,8 +153,92 @@ impl From for QueryResult { impl From for ProxyRow { fn from(result: QueryResult) -> Self { match result.row { + #[cfg(feature = "sqlx-mysql")] + QueryResultRow::SqlxMySql(row) => row.into(), + #[cfg(feature = "sqlx-postgres")] + QueryResultRow::SqlxPostgres(row) => row.into(), + #[cfg(feature = "sqlx-sqlite")] + QueryResultRow::SqlxSqlite(row) => row.into(), + #[cfg(feature = "mock")] + QueryResultRow::Mock(row) => row.into(), QueryResultRow::Proxy(row) => row, - _ => unreachable!("Cannot convert QueryResult to ProxyRow"), + } + } +} + +// #[cfg(all(feature = "proxy", feature = "sqlx-mysql"))] +// impl From for ProxyRow { +// fn from(row: sqlx::mysql::MySqlRow) -> Self { +// use sqlx::{Column, Row, TypeInfo}; +// Self { +// values: row +// .columns() +// .iter() +// .map(|c| { +// ( +// c.name().to_string(), +// match c.type_info().name() { +// "MEDIUMINT" | "INT" | "INTEGER" => Value::Int(Some( +// row.try_get(c.ordinal()) +// .expect("Failed to get value from row"), +// )), +// "BIGINT" => Value::BigInt(Some( +// row.try_get(c.ordinal()) +// .expect("Failed to get value from row"), +// )), +// "FLOAT" => Value::Float(Some( +// row.try_get(c.ordinal()) +// .expect("Failed to get value from row"), +// )), +// "DOUBLE" => Value::Double(Some( +// row.try_get(c.ordinal()) +// .expect("Failed to get value from row"), +// )), + +// #[cfg(feature = "with-bigdecimal")] +// "DECIMAL" => Value::BigDecimal(Some(Box::new( +// row.try_get(c.ordinal()) +// .expect("Failed to get value from row"), +// ))), + +// #[cfg(feature = "with-chrono")] +// "DATE" => Value::ChronoDate(Some(Box::new( +// row.try_get(c.ordinal()) +// .expect("Failed to get value from row"), +// ))), +// #[cfg(feature = "with-chrono")] +// "TIME" => Value::ChronoTime(Some(Box::new( +// row.try_get(c.ordinal()) +// .expect("Failed to get value from row"), +// ))), +// #[cfg(feature = "with-chrono")] +// "DATETIME" => Value::ChronoDateTime(Some(Box::new( +// row.try_get(c.ordinal()) +// .expect("Failed to get value from row"), +// ))), + +// "CHAR" | "VARCHAR" | "TINYTEXT" | "TEXT" | "MEDIUMTEXT" +// | "LONGTEXT" => Value::String(Some(Box::new( +// row.try_get(c.ordinal()) +// .expect("Failed to get value from row"), +// ))), +// _ => Value::String(Some(Box::new( +// row.try_get(c.ordinal()) +// .expect("Failed to get value from row"), +// ))), +// }, +// ) +// }) +// .collect(), +// } +// } +// } + +#[cfg(all(feature = "proxy", feature = "mock"))] +impl From for ProxyRow { + fn from(row: crate::MockRow) -> Self { + Self { + values: row.values.into_iter().collect(), } } } From cda01be72932397d39fc2a8745a4a30d80ef2d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Mon, 23 Oct 2023 00:35:50 +0800 Subject: [PATCH 34/49] fix: Convert sqlx query result for sea-query. --- Cargo.toml | 2 + examples/proxy_wasmtime_example/src/main.rs | 4 + src/database/proxy.rs | 756 ++++++++++++++++++-- 3 files changed, 694 insertions(+), 68 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a04bbb942..8213599e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -144,6 +144,8 @@ with-time = [ "sea-query-binder?/with-time", "sqlx?/time", ] +with-ipnetwork = ["sea-query/with-ipnetwork"] +with-mac_address = ["sea-query/with-mac_address"] postgres-array = [ "sea-query/postgres-array", "sea-query-binder?/postgres-array", diff --git a/examples/proxy_wasmtime_example/src/main.rs b/examples/proxy_wasmtime_example/src/main.rs index ed5282fff..d2c4049d7 100644 --- a/examples/proxy_wasmtime_example/src/main.rs +++ b/examples/proxy_wasmtime_example/src/main.rs @@ -80,6 +80,10 @@ async fn main() -> Result<()> { .iter() .map(|r| r.into()) .collect(); + let ret: Vec = ret.iter().map(|r| r.into()).collect(); + println!("Query result: {:?}", ret); + let ret = ResponseMsg::Query(ret); + tx.send(ret)?; } RequestMsg::Debug(msg) => { println!("VM Debug: {}", msg); diff --git a/src/database/proxy.rs b/src/database/proxy.rs index ef9615df8..4d1260dd3 100644 --- a/src/database/proxy.rs +++ b/src/database/proxy.rs @@ -75,7 +75,7 @@ impl From for ProxyExecResult { }, #[cfg(feature = "sqlx-postgres")] ExecResultHolder::SqlxPostgres(result) => Self { - last_insert_id: result.last_insert_rowid() as u64, + last_insert_id: 0, rows_affected: result.rows_affected(), }, #[cfg(feature = "sqlx-sqlite")] @@ -149,6 +149,16 @@ impl From for QueryResult { } } +#[cfg(all(feature = "proxy", feature = "with-json"))] +impl Into for ProxyRow { + fn into(self) -> serde_json::Value { + self.values + .into_iter() + .map(|(k, v)| (k, sea_query::sea_value_to_json_value(&v))) + .collect() + } +} + #[cfg(feature = "proxy")] impl From for ProxyRow { fn from(result: QueryResult) -> Self { @@ -166,73 +176,683 @@ impl From for ProxyRow { } } -// #[cfg(all(feature = "proxy", feature = "sqlx-mysql"))] -// impl From for ProxyRow { -// fn from(row: sqlx::mysql::MySqlRow) -> Self { -// use sqlx::{Column, Row, TypeInfo}; -// Self { -// values: row -// .columns() -// .iter() -// .map(|c| { -// ( -// c.name().to_string(), -// match c.type_info().name() { -// "MEDIUMINT" | "INT" | "INTEGER" => Value::Int(Some( -// row.try_get(c.ordinal()) -// .expect("Failed to get value from row"), -// )), -// "BIGINT" => Value::BigInt(Some( -// row.try_get(c.ordinal()) -// .expect("Failed to get value from row"), -// )), -// "FLOAT" => Value::Float(Some( -// row.try_get(c.ordinal()) -// .expect("Failed to get value from row"), -// )), -// "DOUBLE" => Value::Double(Some( -// row.try_get(c.ordinal()) -// .expect("Failed to get value from row"), -// )), - -// #[cfg(feature = "with-bigdecimal")] -// "DECIMAL" => Value::BigDecimal(Some(Box::new( -// row.try_get(c.ordinal()) -// .expect("Failed to get value from row"), -// ))), - -// #[cfg(feature = "with-chrono")] -// "DATE" => Value::ChronoDate(Some(Box::new( -// row.try_get(c.ordinal()) -// .expect("Failed to get value from row"), -// ))), -// #[cfg(feature = "with-chrono")] -// "TIME" => Value::ChronoTime(Some(Box::new( -// row.try_get(c.ordinal()) -// .expect("Failed to get value from row"), -// ))), -// #[cfg(feature = "with-chrono")] -// "DATETIME" => Value::ChronoDateTime(Some(Box::new( -// row.try_get(c.ordinal()) -// .expect("Failed to get value from row"), -// ))), - -// "CHAR" | "VARCHAR" | "TINYTEXT" | "TEXT" | "MEDIUMTEXT" -// | "LONGTEXT" => Value::String(Some(Box::new( -// row.try_get(c.ordinal()) -// .expect("Failed to get value from row"), -// ))), -// _ => Value::String(Some(Box::new( -// row.try_get(c.ordinal()) -// .expect("Failed to get value from row"), -// ))), -// }, -// ) -// }) -// .collect(), -// } -// } -// } +#[cfg(all(feature = "proxy", feature = "sqlx-mysql"))] +impl From for ProxyRow { + fn from(row: sqlx::mysql::MySqlRow) -> Self { + // https://docs.rs/sqlx-mysql/0.7.2/src/sqlx_mysql/protocol/text/column.rs.html + // https://docs.rs/sqlx-mysql/0.7.2/sqlx_mysql/types/index.html + use sqlx::{Column, Row, TypeInfo}; + Self { + values: row + .columns() + .iter() + .map(|c| { + ( + c.name().to_string(), + match c.type_info().name() { + "TINYINT(1)" | "BOOLEAN" => Value::Bool(Some( + row.try_get(c.ordinal()).expect("Failed to get boolean"), + )), + "TINYINT UNSIGNED" => Value::TinyUnsigned(Some( + row.try_get(c.ordinal()) + .expect("Failed to get unsigned tiny integer"), + )), + "SMALLINT UNSIGNED" => Value::SmallUnsigned(Some( + row.try_get(c.ordinal()) + .expect("Failed to get unsigned small integer"), + )), + "INT UNSIGNED" => Value::Unsigned(Some( + row.try_get(c.ordinal()) + .expect("Failed to get unsigned integer"), + )), + "MEDIUMINT UNSIGNED" | "BIGINT UNSIGNED" => Value::BigUnsigned(Some( + row.try_get(c.ordinal()) + .expect("Failed to get unsigned big integer"), + )), + "TINYINT" => Value::TinyInt(Some( + row.try_get(c.ordinal()) + .expect("Failed to get tiny integer"), + )), + "SMALLINT" => Value::SmallInt(Some( + row.try_get(c.ordinal()) + .expect("Failed to get small integer"), + )), + "INT" => Value::Int(Some( + row.try_get(c.ordinal()).expect("Failed to get integer"), + )), + "MEDIUMINT" | "BIGINT" => Value::BigInt(Some( + row.try_get(c.ordinal()).expect("Failed to get big integer"), + )), + "FLOAT" => Value::Float(Some( + row.try_get(c.ordinal()).expect("Failed to get float"), + )), + "DOUBLE" => Value::Double(Some( + row.try_get(c.ordinal()).expect("Failed to get double"), + )), + + "BIT" | "BINARY" | "VARBINARY" | "TINYBLOB" | "BLOB" | "MEDIUMBLOB" + | "LONGBLOB" => Value::Bytes(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get bytes"), + ))), + + "CHAR" | "VARCHAR" | "TINYTEXT" | "TEXT" | "MEDIUMTEXT" + | "LONGTEXT" => Value::String(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get string"), + ))), + + #[cfg(feature = "with-chrono")] + "TIMESTAMP" => Value::ChronoDateTimeUtc(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get timestamp"), + ))), + #[cfg(feature = "with-time")] + "TIMESTAMP" => Value::TimeDateTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get timestamp"), + ))), + + #[cfg(feature = "with-chrono")] + "DATE" => Value::ChronoDate(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get date"), + ))), + #[cfg(feature = "with-time")] + "DATE" => Value::TimeDate(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get date"), + ))), + + #[cfg(feature = "with-chrono")] + "TIME" => Value::ChronoTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get time"), + ))), + #[cfg(feature = "with-time")] + "TIME" => Value::TimeTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get time"), + ))), + + #[cfg(feature = "with-chrono")] + "DATETIME" => Value::ChronoDateTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get datetime"), + ))), + #[cfg(feature = "with-time")] + "DATETIME" => Value::TimeDateTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get datetime"), + ))), + + #[cfg(feature = "with-chrono")] + "YEAR" => Value::ChronoDate(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get year"), + ))), + #[cfg(feature = "with-time")] + "YEAR" => Value::TimeDate(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get year"), + ))), + + "ENUM" | "SET" | "GEOMETRY" => Value::String(Some(Box::new( + row.try_get(c.ordinal()) + .expect("Failed to get serialized string"), + ))), + + #[cfg(feature = "with-bigdecimal")] + "DECIMAL" => Value::BigDecimal(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get decimal"), + ))), + #[cfg(all( + feature = "with-rust_decimal", + not(feature = "with-bigdecimal") + ))] + "DECIMAL" => Value::Decimal(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get decimal"), + ))), + + #[cfg(feature = "with-json")] + "JSON" => Value::Json(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get json"), + ))), + + _ => unreachable!("Unknown column type: {}", c.type_info().name()), + }, + ) + }) + .collect(), + } + } +} + +#[cfg(all(feature = "proxy", feature = "sqlx-postgres"))] +impl From for ProxyRow { + fn from(row: sqlx::postgres::PgRow) -> Self { + // https://docs.rs/sqlx-postgres/0.7.2/src/sqlx_postgres/type_info.rs.html + // https://docs.rs/sqlx-postgres/0.7.2/sqlx_postgres/types/index.html + use sqlx::{Column, Row, TypeInfo}; + Self { + values: row + .columns() + .iter() + .map(|c| { + ( + c.name().to_string(), + match c.type_info().name() { + "BOOL" => Value::Bool(Some( + row.try_get(c.ordinal()).expect("Failed to get boolean"), + )), + #[cfg(feature = "postgres-array")] + "BOOL[]" => Value::Array( + sea_query::ArrayType::Bool, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get boolean array") + .iter() + .map(|val| Value::Bool(Some(*val))) + .collect(), + )), + ), + + "\"CHAR\"" => Value::TinyInt(Some( + row.try_get(c.ordinal()) + .expect("Failed to get small integer"), + )), + #[cfg(feature = "postgres-array")] + "\"CHAR\"[]" => Value::Array( + sea_query::ArrayType::TinyInt, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get small integer array") + .iter() + .map(|val| Value::TinyInt(Some(*val))) + .collect(), + )), + ), + + "SMALLINT" | "SMALLSERIAL" | "INT2" => Value::SmallInt(Some( + row.try_get(c.ordinal()) + .expect("Failed to get small integer"), + )), + #[cfg(feature = "postgres-array")] + "SMALLINT[]" | "SMALLSERIAL[]" | "INT2[]" => Value::Array( + sea_query::ArrayType::SmallInt, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get small integer array") + .iter() + .map(|val| Value::SmallInt(Some(*val))) + .collect(), + )), + ), + + "INT" | "SERIAL" | "INT4" => Value::Int(Some( + row.try_get(c.ordinal()).expect("Failed to get integer"), + )), + #[cfg(feature = "postgres-array")] + "INT[]" | "SERIAL[]" | "INT4[]" => Value::Array( + sea_query::ArrayType::Int, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get integer array") + .iter() + .map(|val| Value::Int(Some(*val))) + .collect(), + )), + ), + + "BIGINT" | "BIGSERIAL" | "INT8" => Value::BigInt(Some( + row.try_get(c.ordinal()).expect("Failed to get big integer"), + )), + #[cfg(feature = "postgres-array")] + "BIGINT[]" | "BIGSERIAL[]" | "INT8[]" => Value::Array( + sea_query::ArrayType::BigInt, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get big integer array") + .iter() + .map(|val| Value::BigInt(Some(*val))) + .collect(), + )), + ), + + "FLOAT4" | "REAL" => Value::Float(Some( + row.try_get(c.ordinal()).expect("Failed to get float"), + )), + #[cfg(feature = "postgres-array")] + "FLOAT4[]" | "REAL[]" => Value::Array( + sea_query::ArrayType::Float, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get float array") + .iter() + .map(|val| Value::Float(Some(*val))) + .collect(), + )), + ), + + "FLOAT8" | "DOUBLE PRECISION" => Value::Double(Some( + row.try_get(c.ordinal()).expect("Failed to get double"), + )), + #[cfg(feature = "postgres-array")] + "FLOAT8[]" | "DOUBLE PRECISION[]" => Value::Array( + sea_query::ArrayType::Double, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get double array") + .iter() + .map(|val| Value::Double(Some(*val))) + .collect(), + )), + ), + + "VARCHAR" | "CHAR" | "TEXT" | "NAME" => Value::String(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get string"), + ))), + #[cfg(feature = "postgres-array")] + "VARCHAR[]" | "CHAR[]" | "TEXT[]" | "NAME[]" => Value::Array( + sea_query::ArrayType::String, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get string array") + .iter() + .map(|val| Value::String(Some(Box::new(val.clone())))) + .collect(), + )), + ), + + "BYTEA" => Value::Bytes(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get bytes"), + ))), + #[cfg(feature = "postgres-array")] + "BYTEA[]" => Value::Array( + sea_query::ArrayType::Bytes, + Some(Box::new( + row.try_get::>, _>(c.ordinal()) + .expect("Failed to get bytes array") + .iter() + .map(|val| Value::Bytes(Some(Box::new(val.clone())))) + .collect(), + )), + ), + + #[cfg(feature = "with-bigdecimal")] + "NUMERIC" => Value::BigDecimal(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get numeric"), + ))), + #[cfg(all( + feature = "with-rust_decimal", + not(feature = "with-bigdecimal") + ))] + "NUMERIC" => Value::Decimal(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get numeric"), + ))), + + #[cfg(all(feature = "with-bigdecimal", feature = "postgres-array"))] + "NUMERIC[]" => Value::Array( + sea_query::ArrayType::BigDecimal, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get numeric array") + .iter() + .map(|val| Value::BigDecimal(Some(Box::new(val.clone())))) + .collect(), + )), + ), + #[cfg(all( + feature = "with-rust_decimal", + not(feature = "with-bigdecimal"), + feature = "postgres-array" + ))] + "NUMERIC[]" => Value::Array( + sea_query::ArrayType::Decimal, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get numeric array") + .iter() + .map(|val| Value::Decimal(Some(Box::new(val.clone())))) + .collect(), + )), + ), + + "OID" => Value::BigInt(Some( + row.try_get(c.ordinal()).expect("Failed to get oid"), + )), + #[cfg(feature = "postgres-array")] + "OID[]" => Value::Array( + sea_query::ArrayType::BigInt, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get oid array") + .iter() + .map(|val| Value::BigInt(Some(*val))) + .collect(), + )), + ), + + "JSON" | "JSONB" => Value::Json(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get json"), + ))), + #[cfg(any(feature = "json-array", feature = "postgres-array"))] + "JSON[]" | "JSONB[]" => Value::Array( + sea_query::ArrayType::Json, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get json array") + .iter() + .map(|val| Value::Json(Some(Box::new(val.clone())))) + .collect(), + )), + ), + + #[cfg(feature = "with-ipnetwork")] + "INET" | "CIDR" => Value::IpNetwork(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get ip address"), + ))), + #[cfg(feature = "with-ipnetwork")] + "INET[]" | "CIDR[]" => Value::Array( + sea_query::ArrayType::IpNetwork, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get ip address array") + .iter() + .map(|val| Value::IpNetwork(Some(Box::new(val.clone())))) + .collect(), + )), + ), + + #[cfg(feature = "with-mac_address")] + "MACADDR" | "MACADDR8" => Value::MacAddress(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get mac address"), + ))), + #[cfg(all(feature = "with-mac_address", feature = "postgres-array"))] + "MACADDR[]" | "MACADDR8[]" => Value::Array( + sea_query::ArrayType::MacAddress, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get mac address array") + .iter() + .map(|val| Value::MacAddress(Some(Box::new(val.clone())))) + .collect(), + )), + ), + + #[cfg(feature = "with-chrono")] + "TIMESTAMP" => Value::ChronoDateTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get timestamp"), + ))), + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] + "TIMESTAMP" => Value::TimeDateTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get timestamp"), + ))), + + #[cfg(all(feature = "with-chrono", feature = "postgres-array"))] + "TIMESTAMP[]" => Value::Array( + sea_query::ArrayType::ChronoDateTime, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get timestamp array") + .iter() + .map(|val| { + Value::ChronoDateTime(Some(Box::new(val.clone()))) + }) + .collect(), + )), + ), + #[cfg(all( + feature = "with-time", + not(feature = "with-chrono"), + feature = "postgres-array" + ))] + "TIMESTAMP[]" => Value::Array( + sea_query::ArrayType::TimeDateTime, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get timestamp array") + .iter() + .map(|val| Value::TimeDateTime(Some(Box::new(val.clone())))) + .collect(), + )), + ), + + #[cfg(feature = "with-chrono")] + "DATE" => Value::ChronoDate(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get date"), + ))), + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] + "DATE" => Value::TimeDate(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get date"), + ))), + + #[cfg(all(feature = "with-chrono", feature = "postgres-array"))] + "DATE[]" => Value::Array( + sea_query::ArrayType::ChronoDate, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get date array") + .iter() + .map(|val| Value::ChronoDate(Some(Box::new(val.clone())))) + .collect(), + )), + ), + #[cfg(all( + feature = "with-time", + not(feature = "with-chrono"), + feature = "postgres-array" + ))] + "DATE[]" => Value::Array( + sea_query::ArrayType::TimeDate, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get date array") + .iter() + .map(|val| Value::TimeDate(Some(Box::new(val.clone())))) + .collect(), + )), + ), + + #[cfg(feature = "with-chrono")] + "TIME" => Value::ChronoTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get time"), + ))), + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] + "TIME" => Value::TimeTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get time"), + ))), + + #[cfg(all(feature = "with-chrono", feature = "postgres-array"))] + "TIME[]" => Value::Array( + sea_query::ArrayType::ChronoTime, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get time array") + .iter() + .map(|val| Value::ChronoTime(Some(Box::new(val.clone())))) + .collect(), + )), + ), + #[cfg(all( + feature = "with-time", + not(feature = "with-chrono"), + feature = "postgres-array" + ))] + "TIME[]" => Value::Array( + sea_query::ArrayType::TimeTime, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get time array") + .iter() + .map(|val| Value::TimeTime(Some(Box::new(val.clone())))) + .collect(), + )), + ), + + #[cfg(feature = "with-chrono")] + "TIMESTAMPTZ" => Value::ChronoDateTimeUtc(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get timestamptz"), + ))), + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] + "TIMESTAMPTZ" => Value::TimeDateTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get timestamptz"), + ))), + + #[cfg(all(feature = "with-chrono", feature = "postgres-array"))] + "TIMESTAMPTZ[]" => Value::Array( + sea_query::ArrayType::ChronoDateTimeUtc, + Some(Box::new( + row.try_get::>, _>( + c.ordinal(), + ) + .expect("Failed to get timestamptz array") + .iter() + .map(|val| { + Value::ChronoDateTimeUtc(Some(Box::new(val.clone()))) + }) + .collect(), + )), + ), + #[cfg(all( + feature = "with-time", + not(feature = "with-chrono"), + feature = "postgres-array" + ))] + "TIMESTAMPTZ[]" => Value::Array( + sea_query::ArrayType::TimeDateTime, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get timestamptz array") + .iter() + .map(|val| Value::TimeDateTime(Some(Box::new(val.clone())))) + .collect(), + )), + ), + + #[cfg(feature = "with-chrono")] + "TIMETZ" => Value::ChronoTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get timetz"), + ))), + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] + "TIMETZ" => Value::TimeTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get timetz"), + ))), + + #[cfg(all(feature = "with-chrono", feature = "postgres-array"))] + "TIMETZ[]" => Value::Array( + sea_query::ArrayType::ChronoTime, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get timetz array") + .iter() + .map(|val| Value::ChronoTime(Some(Box::new(val.clone())))) + .collect(), + )), + ), + #[cfg(all( + feature = "with-time", + not(feature = "with-chrono"), + feature = "postgres-array" + ))] + "TIMETZ[]" => Value::Array( + sea_query::ArrayType::TimeTime, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get timetz array") + .iter() + .map(|val| Value::TimeTime(Some(Box::new(val.clone())))) + .collect(), + )), + ), + + #[cfg(feature = "with-uuid")] + "UUID" => Value::Uuid(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get uuid"), + ))), + + #[cfg(all(feature = "with-uuid", feature = "postgres-array"))] + "UUID[]" => Value::Array( + sea_query::ArrayType::Uuid, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get uuid array") + .iter() + .map(|val| Value::Uuid(Some(Box::new(val.clone())))) + .collect(), + )), + ), + + _ => unreachable!("Unknown column type: {}", c.type_info().name()), + }, + ) + }) + .collect(), + } + } +} + +#[cfg(all(feature = "proxy", feature = "sqlx-sqlite"))] +impl From for ProxyRow { + fn from(row: sqlx::sqlite::SqliteRow) -> Self { + // https://docs.rs/sqlx-sqlite/0.7.2/src/sqlx_sqlite/type_info.rs.html + // https://docs.rs/sqlx-sqlite/0.7.2/sqlx_sqlite/types/index.html + use sqlx::{Column, Row, TypeInfo}; + Self { + values: row + .columns() + .iter() + .map(|c| { + ( + c.name().to_string(), + match c.type_info().name() { + "BOOLEAN" => Value::Bool(Some( + row.try_get(c.ordinal()).expect("Failed to get boolean"), + )), + + "INTEGER" => Value::Int(Some( + row.try_get(c.ordinal()).expect("Failed to get integer"), + )), + + "BIGINT" | "INT8" => Value::BigInt(Some( + row.try_get(c.ordinal()).expect("Failed to get big integer"), + )), + + "REAL" => Value::Double(Some( + row.try_get(c.ordinal()).expect("Failed to get double"), + )), + + "TEXT" => Value::String(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get string"), + ))), + + "BLOB" => Value::Bytes(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get bytes"), + ))), + + #[cfg(feature = "with-chrono")] + "DATETIME" => Value::ChronoDateTimeUtc(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get timestamp"), + ))), + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] + "DATETIME" => Value::TimeDateTimeWithTimeZone(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get timestamp"), + ))), + + #[cfg(feature = "with-chrono")] + "DATE" => Value::ChronoDate(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get date"), + ))), + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] + "DATE" => Value::TimeDate(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get date"), + ))), + + #[cfg(feature = "with-chrono")] + "TIME" => Value::ChronoTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get time"), + ))), + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] + "TIME" => Value::TimeTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get time"), + ))), + + _ => unreachable!("Unknown column type: {}", c.type_info().name()), + }, + ) + }) + .collect(), + } + } +} #[cfg(all(feature = "proxy", feature = "mock"))] impl From for ProxyRow { From 6b762f341197bf0987a584a8678420b218502282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Mon, 23 Oct 2023 14:19:17 +0800 Subject: [PATCH 35/49] fix: Now we can transfer wasm's query to outside. --- .../module/src/entity/post.rs | 2 +- .../proxy_wasmtime_example/module/src/main.rs | 33 ++++++++++++++++++- examples/proxy_wasmtime_example/src/main.rs | 16 +++++---- src/database/proxy.rs | 27 ++++----------- 4 files changed, 48 insertions(+), 30 deletions(-) diff --git a/examples/proxy_wasmtime_example/module/src/entity/post.rs b/examples/proxy_wasmtime_example/module/src/entity/post.rs index 1fde0c4cc..868846046 100644 --- a/examples/proxy_wasmtime_example/module/src/entity/post.rs +++ b/examples/proxy_wasmtime_example/module/src/entity/post.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; #[sea_orm(table_name = "posts")] pub struct Model { #[sea_orm(primary_key)] - pub id: String, + pub id: i64, pub title: String, pub text: String, diff --git a/examples/proxy_wasmtime_example/module/src/main.rs b/examples/proxy_wasmtime_example/module/src/main.rs index c7cc2673a..8ca4c8a90 100644 --- a/examples/proxy_wasmtime_example/module/src/main.rs +++ b/examples/proxy_wasmtime_example/module/src/main.rs @@ -39,8 +39,39 @@ struct ProxyDb {} impl ProxyDatabaseTrait for ProxyDb { fn query(&self, statement: Statement) -> Result, DbErr> { let sql = statement.sql.clone(); + println!( + "{}", + serde_json::to_string(&RequestMsg::Query(sql)).unwrap() + ); - Ok(vec![]) + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + let ret: ResponseMsg = serde_json::from_str(&input).unwrap(); + let ret = match ret { + ResponseMsg::Query(v) => v, + _ => unreachable!("Not a query result"), + }; + + let mut rows: Vec = vec![]; + for row in ret { + let mut map: BTreeMap = BTreeMap::new(); + for (k, v) in row.as_object().unwrap().iter() { + map.insert(k.to_owned(), { + if v.is_string() { + sea_orm::Value::String(Some(Box::new(v.as_str().unwrap().to_string()))) + } else if v.is_number() { + sea_orm::Value::BigInt(Some(v.as_i64().unwrap())) + } else if v.is_boolean() { + sea_orm::Value::Bool(Some(v.as_bool().unwrap())) + } else { + unreachable!("Unknown json type") + } + }); + } + rows.push(ProxyRow { values: map }); + } + + Ok(rows) } fn execute(&self, statement: Statement) -> Result { diff --git a/examples/proxy_wasmtime_example/src/main.rs b/examples/proxy_wasmtime_example/src/main.rs index d2c4049d7..698d45e79 100644 --- a/examples/proxy_wasmtime_example/src/main.rs +++ b/examples/proxy_wasmtime_example/src/main.rs @@ -1,7 +1,7 @@ use anyhow::Result; use bytes::Bytes; -use sea_orm::{ConnectionTrait, Database, DatabaseBackend, ProxyExecResult, ProxyRow, Statement}; +use sea_orm::{ConnectionTrait, Database, DatabaseBackend, ProxyExecResult, Statement}; use wasmtime::{Config, Engine}; use wit_component::ComponentEncoder; @@ -60,9 +60,7 @@ async fn main() -> Result<()> { runner.run().unwrap(); }); - loop { - let msg = rx.recv()?; - + while let Ok(msg) = rx.recv() { match msg { RequestMsg::Execute(sql) => { let ret: ProxyExecResult = db @@ -74,14 +72,16 @@ async fn main() -> Result<()> { tx.send(ret)?; } RequestMsg::Query(sql) => { - let ret: Vec = db + use sea_orm::FromQueryResult; + + let ret: Vec = db .query_all(Statement::from_string(DatabaseBackend::Sqlite, sql)) .await? .iter() - .map(|r| r.into()) + .map(|r| sea_orm::query::JsonValue::from_query_result(&r, "").unwrap()) .collect(); - let ret: Vec = ret.iter().map(|r| r.into()).collect(); println!("Query result: {:?}", ret); + let ret = ResponseMsg::Query(ret); tx.send(ret)?; } @@ -90,4 +90,6 @@ async fn main() -> Result<()> { } } } + + Ok(()) } diff --git a/src/database/proxy.rs b/src/database/proxy.rs index 4d1260dd3..61b2e1ee7 100644 --- a/src/database/proxy.rs +++ b/src/database/proxy.rs @@ -3,7 +3,6 @@ use crate::{error::*, ExecResult, ExecResultHolder, QueryResult, QueryResultRow, use sea_query::{Value, ValueType}; use std::{collections::BTreeMap, fmt::Debug}; -#[cfg(feature = "proxy")] /// Defines the [ProxyDatabaseTrait] to save the functions pub trait ProxyDatabaseTrait: Send + Sync + std::fmt::Debug { /// Execute a query in the [ProxyDatabase], and return the query results @@ -28,7 +27,6 @@ pub trait ProxyDatabaseTrait: Send + Sync + std::fmt::Debug { } /// Defines the results obtained from a [ProxyDatabase] -#[cfg(feature = "proxy")] #[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] pub struct ProxyExecResult { /// The last inserted id on auto-increment @@ -37,7 +35,6 @@ pub struct ProxyExecResult { pub rows_affected: u64, } -#[cfg(feature = "proxy")] impl ProxyExecResult { /// Create a new [ProxyExecResult] from the last inserted id and the number of rows affected pub fn new(last_insert_id: u64, rows_affected: u64) -> Self { @@ -48,14 +45,12 @@ impl ProxyExecResult { } } -#[cfg(feature = "proxy")] impl Default for ExecResultHolder { fn default() -> Self { Self::Proxy(ProxyExecResult::default()) } } -#[cfg(feature = "proxy")] impl From for ExecResult { fn from(result: ProxyExecResult) -> Self { Self { @@ -64,7 +59,6 @@ impl From for ExecResult { } } -#[cfg(feature = "proxy")] impl From for ProxyExecResult { fn from(result: ExecResult) -> Self { match result.result { @@ -95,14 +89,12 @@ impl From for ProxyExecResult { /// Defines the structure of a Row for the [ProxyDatabase] /// which is just a [BTreeMap]<[String], [Value]> -#[cfg(feature = "proxy")] #[derive(Clone, Debug)] pub struct ProxyRow { /// The values of the single row pub values: BTreeMap, } -#[cfg(feature = "proxy")] impl ProxyRow { /// Create a new [ProxyRow] from a [BTreeMap]<[String], [Value]> pub fn new(values: BTreeMap) -> Self { @@ -110,7 +102,6 @@ impl ProxyRow { } } -#[cfg(feature = "proxy")] impl Default for ProxyRow { fn default() -> Self { Self { @@ -119,28 +110,24 @@ impl Default for ProxyRow { } } -#[cfg(feature = "proxy")] impl From> for ProxyRow { fn from(values: BTreeMap) -> Self { Self { values } } } -#[cfg(feature = "proxy")] impl From for BTreeMap { fn from(row: ProxyRow) -> Self { row.values } } -#[cfg(feature = "proxy")] impl From for Vec<(String, Value)> { fn from(row: ProxyRow) -> Self { row.values.into_iter().collect() } } -#[cfg(feature = "proxy")] impl From for QueryResult { fn from(row: ProxyRow) -> Self { QueryResult { @@ -149,7 +136,7 @@ impl From for QueryResult { } } -#[cfg(all(feature = "proxy", feature = "with-json"))] +#[cfg(feature = "with-json")] impl Into for ProxyRow { fn into(self) -> serde_json::Value { self.values @@ -159,7 +146,6 @@ impl Into for ProxyRow { } } -#[cfg(feature = "proxy")] impl From for ProxyRow { fn from(result: QueryResult) -> Self { match result.row { @@ -176,7 +162,7 @@ impl From for ProxyRow { } } -#[cfg(all(feature = "proxy", feature = "sqlx-mysql"))] +#[cfg(feature = "sqlx-mysql")] impl From for ProxyRow { fn from(row: sqlx::mysql::MySqlRow) -> Self { // https://docs.rs/sqlx-mysql/0.7.2/src/sqlx_mysql/protocol/text/column.rs.html @@ -316,7 +302,7 @@ impl From for ProxyRow { } } -#[cfg(all(feature = "proxy", feature = "sqlx-postgres"))] +#[cfg(feature = "sqlx-postgres")] impl From for ProxyRow { fn from(row: sqlx::postgres::PgRow) -> Self { // https://docs.rs/sqlx-postgres/0.7.2/src/sqlx_postgres/type_info.rs.html @@ -780,7 +766,7 @@ impl From for ProxyRow { } } -#[cfg(all(feature = "proxy", feature = "sqlx-sqlite"))] +#[cfg(feature = "sqlx-sqlite")] impl From for ProxyRow { fn from(row: sqlx::sqlite::SqliteRow) -> Self { // https://docs.rs/sqlx-sqlite/0.7.2/src/sqlx_sqlite/type_info.rs.html @@ -854,7 +840,7 @@ impl From for ProxyRow { } } -#[cfg(all(feature = "proxy", feature = "mock"))] +#[cfg(feature = "mock")] impl From for ProxyRow { fn from(row: crate::MockRow) -> Self { Self { @@ -896,7 +882,6 @@ impl ProxyRow { } #[cfg(test)] -#[cfg(feature = "proxy")] mod tests { use crate::{ entity::*, tests_cfg::*, Database, DbBackend, DbErr, ProxyDatabaseTrait, ProxyExecResult, @@ -924,7 +909,7 @@ mod tests { #[smol_potat::test] async fn create_proxy_conn() { - let db = + let _db = Database::connect_proxy(DbBackend::MySql, Arc::new(Mutex::new(Box::new(ProxyDb {})))) .await .unwrap(); From a36dfe6079ac015e788e07988aaf98fdb69c7172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Mon, 23 Oct 2023 17:01:42 +0800 Subject: [PATCH 36/49] refactor: Convert to ProxyRow first. It's the solution to know the type information about the value. --- examples/proxy_wasmtime_example/src/main.rs | 10 +- src/database/proxy.rs | 1309 +++++++++---------- 2 files changed, 653 insertions(+), 666 deletions(-) diff --git a/examples/proxy_wasmtime_example/src/main.rs b/examples/proxy_wasmtime_example/src/main.rs index 698d45e79..5be4d679f 100644 --- a/examples/proxy_wasmtime_example/src/main.rs +++ b/examples/proxy_wasmtime_example/src/main.rs @@ -72,13 +72,17 @@ async fn main() -> Result<()> { tx.send(ret)?; } RequestMsg::Query(sql) => { - use sea_orm::FromQueryResult; - let ret: Vec = db .query_all(Statement::from_string(DatabaseBackend::Sqlite, sql)) .await? .iter() - .map(|r| sea_orm::query::JsonValue::from_query_result(&r, "").unwrap()) + .map(|r| sea_orm::from_query_result_to_proxy_row(&r)) + .map(|r| { + // This demo only converts it to json value currently. + // But it can be converted to any other format that includes the type information. + // You can use 'match' to deal the type of the value on sea_orm::Value. + r.into() + }) .collect(); println!("Query result: {:?}", ret); diff --git a/src/database/proxy.rs b/src/database/proxy.rs index 61b2e1ee7..db3c7f388 100644 --- a/src/database/proxy.rs +++ b/src/database/proxy.rs @@ -146,706 +146,689 @@ impl Into for ProxyRow { } } -impl From for ProxyRow { - fn from(result: QueryResult) -> Self { - match result.row { - #[cfg(feature = "sqlx-mysql")] - QueryResultRow::SqlxMySql(row) => row.into(), - #[cfg(feature = "sqlx-postgres")] - QueryResultRow::SqlxPostgres(row) => row.into(), - #[cfg(feature = "sqlx-sqlite")] - QueryResultRow::SqlxSqlite(row) => row.into(), - #[cfg(feature = "mock")] - QueryResultRow::Mock(row) => row.into(), - QueryResultRow::Proxy(row) => row, - } +/// Convert [QueryResult] to [ProxyRow] +pub fn from_query_result_to_proxy_row(result: &QueryResult) -> ProxyRow { + match &result.row { + #[cfg(feature = "sqlx-mysql")] + QueryResultRow::SqlxMySql(row) => from_sqlx_mysql_row_to_proxy_row(&row), + #[cfg(feature = "sqlx-postgres")] + QueryResultRow::SqlxPostgres(row) => from_sqlx_postgres_row_to_proxy_row(&row), + #[cfg(feature = "sqlx-sqlite")] + QueryResultRow::SqlxSqlite(row) => from_sqlx_sqlite_row_to_proxy_row(&row), + #[cfg(feature = "mock")] + QueryResultRow::Mock(row) => ProxyRow { + values: row.values.clone(), + }, + QueryResultRow::Proxy(row) => row.to_owned(), } } #[cfg(feature = "sqlx-mysql")] -impl From for ProxyRow { - fn from(row: sqlx::mysql::MySqlRow) -> Self { - // https://docs.rs/sqlx-mysql/0.7.2/src/sqlx_mysql/protocol/text/column.rs.html - // https://docs.rs/sqlx-mysql/0.7.2/sqlx_mysql/types/index.html - use sqlx::{Column, Row, TypeInfo}; - Self { - values: row - .columns() - .iter() - .map(|c| { - ( - c.name().to_string(), - match c.type_info().name() { - "TINYINT(1)" | "BOOLEAN" => Value::Bool(Some( - row.try_get(c.ordinal()).expect("Failed to get boolean"), +pub(crate) fn from_sqlx_mysql_row_to_proxy_row(row: &sqlx::mysql::MySqlRow) -> ProxyRow { + // https://docs.rs/sqlx-mysql/0.7.2/src/sqlx_mysql/protocol/text/column.rs.html + // https://docs.rs/sqlx-mysql/0.7.2/sqlx_mysql/types/index.html + use sqlx::{Column, Row, TypeInfo}; + ProxyRow { + values: row + .columns() + .iter() + .map(|c| { + ( + c.name().to_string(), + match c.type_info().name() { + "TINYINT(1)" | "BOOLEAN" => Value::Bool(Some( + row.try_get(c.ordinal()).expect("Failed to get boolean"), + )), + "TINYINT UNSIGNED" => Value::TinyUnsigned(Some( + row.try_get(c.ordinal()) + .expect("Failed to get unsigned tiny integer"), + )), + "SMALLINT UNSIGNED" => Value::SmallUnsigned(Some( + row.try_get(c.ordinal()) + .expect("Failed to get unsigned small integer"), + )), + "INT UNSIGNED" => Value::Unsigned(Some( + row.try_get(c.ordinal()) + .expect("Failed to get unsigned integer"), + )), + "MEDIUMINT UNSIGNED" | "BIGINT UNSIGNED" => Value::BigUnsigned(Some( + row.try_get(c.ordinal()) + .expect("Failed to get unsigned big integer"), + )), + "TINYINT" => Value::TinyInt(Some( + row.try_get(c.ordinal()) + .expect("Failed to get tiny integer"), + )), + "SMALLINT" => Value::SmallInt(Some( + row.try_get(c.ordinal()) + .expect("Failed to get small integer"), + )), + "INT" => Value::Int(Some( + row.try_get(c.ordinal()).expect("Failed to get integer"), + )), + "MEDIUMINT" | "BIGINT" => Value::BigInt(Some( + row.try_get(c.ordinal()).expect("Failed to get big integer"), + )), + "FLOAT" => Value::Float(Some( + row.try_get(c.ordinal()).expect("Failed to get float"), + )), + "DOUBLE" => Value::Double(Some( + row.try_get(c.ordinal()).expect("Failed to get double"), + )), + + "BIT" | "BINARY" | "VARBINARY" | "TINYBLOB" | "BLOB" | "MEDIUMBLOB" + | "LONGBLOB" => Value::Bytes(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get bytes"), + ))), + + "CHAR" | "VARCHAR" | "TINYTEXT" | "TEXT" | "MEDIUMTEXT" | "LONGTEXT" => { + Value::String(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get string"), + ))) + } + + #[cfg(feature = "with-chrono")] + "TIMESTAMP" => Value::ChronoDateTimeUtc(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get timestamp"), + ))), + #[cfg(feature = "with-time")] + "TIMESTAMP" => Value::TimeDateTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get timestamp"), + ))), + + #[cfg(feature = "with-chrono")] + "DATE" => Value::ChronoDate(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get date"), + ))), + #[cfg(feature = "with-time")] + "DATE" => Value::TimeDate(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get date"), + ))), + + #[cfg(feature = "with-chrono")] + "TIME" => Value::ChronoTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get time"), + ))), + #[cfg(feature = "with-time")] + "TIME" => Value::TimeTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get time"), + ))), + + #[cfg(feature = "with-chrono")] + "DATETIME" => Value::ChronoDateTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get datetime"), + ))), + #[cfg(feature = "with-time")] + "DATETIME" => Value::TimeDateTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get datetime"), + ))), + + #[cfg(feature = "with-chrono")] + "YEAR" => Value::ChronoDate(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get year"), + ))), + #[cfg(feature = "with-time")] + "YEAR" => Value::TimeDate(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get year"), + ))), + + "ENUM" | "SET" | "GEOMETRY" => Value::String(Some(Box::new( + row.try_get(c.ordinal()) + .expect("Failed to get serialized string"), + ))), + + #[cfg(feature = "with-bigdecimal")] + "DECIMAL" => Value::BigDecimal(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get decimal"), + ))), + #[cfg(all( + feature = "with-rust_decimal", + not(feature = "with-bigdecimal") + ))] + "DECIMAL" => Value::Decimal(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get decimal"), + ))), + + #[cfg(feature = "with-json")] + "JSON" => Value::Json(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get json"), + ))), + + _ => unreachable!("Unknown column type: {}", c.type_info().name()), + }, + ) + }) + .collect(), + } +} + +#[cfg(feature = "sqlx-postgres")] +pub(crate) fn from_sqlx_postgres_row_to_proxy_row(row: &sqlx::postgres::PgRow) -> ProxyRow { + // https://docs.rs/sqlx-postgres/0.7.2/src/sqlx_postgres/type_info.rs.html + // https://docs.rs/sqlx-postgres/0.7.2/sqlx_postgres/types/index.html + use sqlx::{Column, Row, TypeInfo}; + ProxyRow { + values: row + .columns() + .iter() + .map(|c| { + ( + c.name().to_string(), + match c.type_info().name() { + "BOOL" => Value::Bool(Some( + row.try_get(c.ordinal()).expect("Failed to get boolean"), + )), + #[cfg(feature = "postgres-array")] + "BOOL[]" => Value::Array( + sea_query::ArrayType::Bool, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get boolean array") + .iter() + .map(|val| Value::Bool(Some(*val))) + .collect(), )), - "TINYINT UNSIGNED" => Value::TinyUnsigned(Some( - row.try_get(c.ordinal()) - .expect("Failed to get unsigned tiny integer"), + ), + + "\"CHAR\"" => Value::TinyInt(Some( + row.try_get(c.ordinal()) + .expect("Failed to get small integer"), + )), + #[cfg(feature = "postgres-array")] + "\"CHAR\"[]" => Value::Array( + sea_query::ArrayType::TinyInt, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get small integer array") + .iter() + .map(|val| Value::TinyInt(Some(*val))) + .collect(), )), - "SMALLINT UNSIGNED" => Value::SmallUnsigned(Some( - row.try_get(c.ordinal()) - .expect("Failed to get unsigned small integer"), + ), + + "SMALLINT" | "SMALLSERIAL" | "INT2" => Value::SmallInt(Some( + row.try_get(c.ordinal()) + .expect("Failed to get small integer"), + )), + #[cfg(feature = "postgres-array")] + "SMALLINT[]" | "SMALLSERIAL[]" | "INT2[]" => Value::Array( + sea_query::ArrayType::SmallInt, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get small integer array") + .iter() + .map(|val| Value::SmallInt(Some(*val))) + .collect(), )), - "INT UNSIGNED" => Value::Unsigned(Some( - row.try_get(c.ordinal()) - .expect("Failed to get unsigned integer"), + ), + + "INT" | "SERIAL" | "INT4" => Value::Int(Some( + row.try_get(c.ordinal()).expect("Failed to get integer"), + )), + #[cfg(feature = "postgres-array")] + "INT[]" | "SERIAL[]" | "INT4[]" => Value::Array( + sea_query::ArrayType::Int, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get integer array") + .iter() + .map(|val| Value::Int(Some(*val))) + .collect(), )), - "MEDIUMINT UNSIGNED" | "BIGINT UNSIGNED" => Value::BigUnsigned(Some( - row.try_get(c.ordinal()) - .expect("Failed to get unsigned big integer"), + ), + + "BIGINT" | "BIGSERIAL" | "INT8" => Value::BigInt(Some( + row.try_get(c.ordinal()).expect("Failed to get big integer"), + )), + #[cfg(feature = "postgres-array")] + "BIGINT[]" | "BIGSERIAL[]" | "INT8[]" => Value::Array( + sea_query::ArrayType::BigInt, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get big integer array") + .iter() + .map(|val| Value::BigInt(Some(*val))) + .collect(), )), - "TINYINT" => Value::TinyInt(Some( - row.try_get(c.ordinal()) - .expect("Failed to get tiny integer"), + ), + + "FLOAT4" | "REAL" => Value::Float(Some( + row.try_get(c.ordinal()).expect("Failed to get float"), + )), + #[cfg(feature = "postgres-array")] + "FLOAT4[]" | "REAL[]" => Value::Array( + sea_query::ArrayType::Float, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get float array") + .iter() + .map(|val| Value::Float(Some(*val))) + .collect(), )), - "SMALLINT" => Value::SmallInt(Some( - row.try_get(c.ordinal()) - .expect("Failed to get small integer"), + ), + + "FLOAT8" | "DOUBLE PRECISION" => Value::Double(Some( + row.try_get(c.ordinal()).expect("Failed to get double"), + )), + #[cfg(feature = "postgres-array")] + "FLOAT8[]" | "DOUBLE PRECISION[]" => Value::Array( + sea_query::ArrayType::Double, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get double array") + .iter() + .map(|val| Value::Double(Some(*val))) + .collect(), )), - "INT" => Value::Int(Some( - row.try_get(c.ordinal()).expect("Failed to get integer"), + ), + + "VARCHAR" | "CHAR" | "TEXT" | "NAME" => Value::String(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get string"), + ))), + #[cfg(feature = "postgres-array")] + "VARCHAR[]" | "CHAR[]" | "TEXT[]" | "NAME[]" => Value::Array( + sea_query::ArrayType::String, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get string array") + .iter() + .map(|val| Value::String(Some(Box::new(val.clone())))) + .collect(), )), - "MEDIUMINT" | "BIGINT" => Value::BigInt(Some( - row.try_get(c.ordinal()).expect("Failed to get big integer"), + ), + + "BYTEA" => Value::Bytes(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get bytes"), + ))), + #[cfg(feature = "postgres-array")] + "BYTEA[]" => Value::Array( + sea_query::ArrayType::Bytes, + Some(Box::new( + row.try_get::>, _>(c.ordinal()) + .expect("Failed to get bytes array") + .iter() + .map(|val| Value::Bytes(Some(Box::new(val.clone())))) + .collect(), )), - "FLOAT" => Value::Float(Some( - row.try_get(c.ordinal()).expect("Failed to get float"), + ), + + #[cfg(feature = "with-bigdecimal")] + "NUMERIC" => Value::BigDecimal(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get numeric"), + ))), + #[cfg(all( + feature = "with-rust_decimal", + not(feature = "with-bigdecimal") + ))] + "NUMERIC" => Value::Decimal(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get numeric"), + ))), + + #[cfg(all(feature = "with-bigdecimal", feature = "postgres-array"))] + "NUMERIC[]" => Value::Array( + sea_query::ArrayType::BigDecimal, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get numeric array") + .iter() + .map(|val| Value::BigDecimal(Some(Box::new(val.clone())))) + .collect(), )), - "DOUBLE" => Value::Double(Some( - row.try_get(c.ordinal()).expect("Failed to get double"), + ), + #[cfg(all( + feature = "with-rust_decimal", + not(feature = "with-bigdecimal"), + feature = "postgres-array" + ))] + "NUMERIC[]" => Value::Array( + sea_query::ArrayType::Decimal, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get numeric array") + .iter() + .map(|val| Value::Decimal(Some(Box::new(val.clone())))) + .collect(), )), - - "BIT" | "BINARY" | "VARBINARY" | "TINYBLOB" | "BLOB" | "MEDIUMBLOB" - | "LONGBLOB" => Value::Bytes(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get bytes"), - ))), - - "CHAR" | "VARCHAR" | "TINYTEXT" | "TEXT" | "MEDIUMTEXT" - | "LONGTEXT" => Value::String(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get string"), - ))), - - #[cfg(feature = "with-chrono")] - "TIMESTAMP" => Value::ChronoDateTimeUtc(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get timestamp"), - ))), - #[cfg(feature = "with-time")] - "TIMESTAMP" => Value::TimeDateTime(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get timestamp"), - ))), - - #[cfg(feature = "with-chrono")] - "DATE" => Value::ChronoDate(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get date"), - ))), - #[cfg(feature = "with-time")] - "DATE" => Value::TimeDate(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get date"), - ))), - - #[cfg(feature = "with-chrono")] - "TIME" => Value::ChronoTime(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get time"), - ))), - #[cfg(feature = "with-time")] - "TIME" => Value::TimeTime(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get time"), - ))), - - #[cfg(feature = "with-chrono")] - "DATETIME" => Value::ChronoDateTime(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get datetime"), - ))), - #[cfg(feature = "with-time")] - "DATETIME" => Value::TimeDateTime(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get datetime"), - ))), - - #[cfg(feature = "with-chrono")] - "YEAR" => Value::ChronoDate(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get year"), - ))), - #[cfg(feature = "with-time")] - "YEAR" => Value::TimeDate(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get year"), - ))), - - "ENUM" | "SET" | "GEOMETRY" => Value::String(Some(Box::new( - row.try_get(c.ordinal()) - .expect("Failed to get serialized string"), - ))), - - #[cfg(feature = "with-bigdecimal")] - "DECIMAL" => Value::BigDecimal(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get decimal"), - ))), - #[cfg(all( - feature = "with-rust_decimal", - not(feature = "with-bigdecimal") - ))] - "DECIMAL" => Value::Decimal(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get decimal"), - ))), - - #[cfg(feature = "with-json")] - "JSON" => Value::Json(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get json"), - ))), - - _ => unreachable!("Unknown column type: {}", c.type_info().name()), - }, - ) - }) - .collect(), - } - } -} - -#[cfg(feature = "sqlx-postgres")] -impl From for ProxyRow { - fn from(row: sqlx::postgres::PgRow) -> Self { - // https://docs.rs/sqlx-postgres/0.7.2/src/sqlx_postgres/type_info.rs.html - // https://docs.rs/sqlx-postgres/0.7.2/sqlx_postgres/types/index.html - use sqlx::{Column, Row, TypeInfo}; - Self { - values: row - .columns() - .iter() - .map(|c| { - ( - c.name().to_string(), - match c.type_info().name() { - "BOOL" => Value::Bool(Some( - row.try_get(c.ordinal()).expect("Failed to get boolean"), + ), + + "OID" => Value::BigInt(Some( + row.try_get(c.ordinal()).expect("Failed to get oid"), + )), + #[cfg(feature = "postgres-array")] + "OID[]" => Value::Array( + sea_query::ArrayType::BigInt, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get oid array") + .iter() + .map(|val| Value::BigInt(Some(*val))) + .collect(), )), - #[cfg(feature = "postgres-array")] - "BOOL[]" => Value::Array( - sea_query::ArrayType::Bool, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get boolean array") - .iter() - .map(|val| Value::Bool(Some(*val))) - .collect(), - )), - ), - - "\"CHAR\"" => Value::TinyInt(Some( - row.try_get(c.ordinal()) - .expect("Failed to get small integer"), + ), + + "JSON" | "JSONB" => Value::Json(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get json"), + ))), + #[cfg(any(feature = "json-array", feature = "postgres-array"))] + "JSON[]" | "JSONB[]" => Value::Array( + sea_query::ArrayType::Json, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get json array") + .iter() + .map(|val| Value::Json(Some(Box::new(val.clone())))) + .collect(), )), - #[cfg(feature = "postgres-array")] - "\"CHAR\"[]" => Value::Array( - sea_query::ArrayType::TinyInt, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get small integer array") - .iter() - .map(|val| Value::TinyInt(Some(*val))) - .collect(), - )), - ), - - "SMALLINT" | "SMALLSERIAL" | "INT2" => Value::SmallInt(Some( - row.try_get(c.ordinal()) - .expect("Failed to get small integer"), + ), + + #[cfg(feature = "with-ipnetwork")] + "INET" | "CIDR" => Value::IpNetwork(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get ip address"), + ))), + #[cfg(feature = "with-ipnetwork")] + "INET[]" | "CIDR[]" => Value::Array( + sea_query::ArrayType::IpNetwork, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get ip address array") + .iter() + .map(|val| Value::IpNetwork(Some(Box::new(val.clone())))) + .collect(), )), - #[cfg(feature = "postgres-array")] - "SMALLINT[]" | "SMALLSERIAL[]" | "INT2[]" => Value::Array( - sea_query::ArrayType::SmallInt, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get small integer array") - .iter() - .map(|val| Value::SmallInt(Some(*val))) - .collect(), - )), - ), - - "INT" | "SERIAL" | "INT4" => Value::Int(Some( - row.try_get(c.ordinal()).expect("Failed to get integer"), + ), + + #[cfg(feature = "with-mac_address")] + "MACADDR" | "MACADDR8" => Value::MacAddress(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get mac address"), + ))), + #[cfg(all(feature = "with-mac_address", feature = "postgres-array"))] + "MACADDR[]" | "MACADDR8[]" => Value::Array( + sea_query::ArrayType::MacAddress, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get mac address array") + .iter() + .map(|val| Value::MacAddress(Some(Box::new(val.clone())))) + .collect(), )), - #[cfg(feature = "postgres-array")] - "INT[]" | "SERIAL[]" | "INT4[]" => Value::Array( - sea_query::ArrayType::Int, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get integer array") - .iter() - .map(|val| Value::Int(Some(*val))) - .collect(), - )), - ), - - "BIGINT" | "BIGSERIAL" | "INT8" => Value::BigInt(Some( - row.try_get(c.ordinal()).expect("Failed to get big integer"), + ), + + #[cfg(feature = "with-chrono")] + "TIMESTAMP" => Value::ChronoDateTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get timestamp"), + ))), + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] + "TIMESTAMP" => Value::TimeDateTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get timestamp"), + ))), + + #[cfg(all(feature = "with-chrono", feature = "postgres-array"))] + "TIMESTAMP[]" => Value::Array( + sea_query::ArrayType::ChronoDateTime, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get timestamp array") + .iter() + .map(|val| Value::ChronoDateTime(Some(Box::new(val.clone())))) + .collect(), )), - #[cfg(feature = "postgres-array")] - "BIGINT[]" | "BIGSERIAL[]" | "INT8[]" => Value::Array( - sea_query::ArrayType::BigInt, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get big integer array") - .iter() - .map(|val| Value::BigInt(Some(*val))) - .collect(), - )), - ), - - "FLOAT4" | "REAL" => Value::Float(Some( - row.try_get(c.ordinal()).expect("Failed to get float"), + ), + #[cfg(all( + feature = "with-time", + not(feature = "with-chrono"), + feature = "postgres-array" + ))] + "TIMESTAMP[]" => Value::Array( + sea_query::ArrayType::TimeDateTime, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get timestamp array") + .iter() + .map(|val| Value::TimeDateTime(Some(Box::new(val.clone())))) + .collect(), )), - #[cfg(feature = "postgres-array")] - "FLOAT4[]" | "REAL[]" => Value::Array( - sea_query::ArrayType::Float, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get float array") - .iter() - .map(|val| Value::Float(Some(*val))) - .collect(), - )), - ), - - "FLOAT8" | "DOUBLE PRECISION" => Value::Double(Some( - row.try_get(c.ordinal()).expect("Failed to get double"), + ), + + #[cfg(feature = "with-chrono")] + "DATE" => Value::ChronoDate(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get date"), + ))), + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] + "DATE" => Value::TimeDate(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get date"), + ))), + + #[cfg(all(feature = "with-chrono", feature = "postgres-array"))] + "DATE[]" => Value::Array( + sea_query::ArrayType::ChronoDate, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get date array") + .iter() + .map(|val| Value::ChronoDate(Some(Box::new(val.clone())))) + .collect(), )), - #[cfg(feature = "postgres-array")] - "FLOAT8[]" | "DOUBLE PRECISION[]" => Value::Array( - sea_query::ArrayType::Double, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get double array") - .iter() - .map(|val| Value::Double(Some(*val))) - .collect(), - )), - ), - - "VARCHAR" | "CHAR" | "TEXT" | "NAME" => Value::String(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get string"), - ))), - #[cfg(feature = "postgres-array")] - "VARCHAR[]" | "CHAR[]" | "TEXT[]" | "NAME[]" => Value::Array( - sea_query::ArrayType::String, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get string array") - .iter() - .map(|val| Value::String(Some(Box::new(val.clone())))) - .collect(), - )), - ), - - "BYTEA" => Value::Bytes(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get bytes"), - ))), - #[cfg(feature = "postgres-array")] - "BYTEA[]" => Value::Array( - sea_query::ArrayType::Bytes, - Some(Box::new( - row.try_get::>, _>(c.ordinal()) - .expect("Failed to get bytes array") - .iter() - .map(|val| Value::Bytes(Some(Box::new(val.clone())))) - .collect(), - )), - ), - - #[cfg(feature = "with-bigdecimal")] - "NUMERIC" => Value::BigDecimal(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get numeric"), - ))), - #[cfg(all( - feature = "with-rust_decimal", - not(feature = "with-bigdecimal") - ))] - "NUMERIC" => Value::Decimal(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get numeric"), - ))), - - #[cfg(all(feature = "with-bigdecimal", feature = "postgres-array"))] - "NUMERIC[]" => Value::Array( - sea_query::ArrayType::BigDecimal, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get numeric array") - .iter() - .map(|val| Value::BigDecimal(Some(Box::new(val.clone())))) - .collect(), - )), - ), - #[cfg(all( - feature = "with-rust_decimal", - not(feature = "with-bigdecimal"), - feature = "postgres-array" - ))] - "NUMERIC[]" => Value::Array( - sea_query::ArrayType::Decimal, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get numeric array") - .iter() - .map(|val| Value::Decimal(Some(Box::new(val.clone())))) - .collect(), - )), - ), - - "OID" => Value::BigInt(Some( - row.try_get(c.ordinal()).expect("Failed to get oid"), + ), + #[cfg(all( + feature = "with-time", + not(feature = "with-chrono"), + feature = "postgres-array" + ))] + "DATE[]" => Value::Array( + sea_query::ArrayType::TimeDate, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get date array") + .iter() + .map(|val| Value::TimeDate(Some(Box::new(val.clone())))) + .collect(), + )), + ), + + #[cfg(feature = "with-chrono")] + "TIME" => Value::ChronoTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get time"), + ))), + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] + "TIME" => Value::TimeTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get time"), + ))), + + #[cfg(all(feature = "with-chrono", feature = "postgres-array"))] + "TIME[]" => Value::Array( + sea_query::ArrayType::ChronoTime, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get time array") + .iter() + .map(|val| Value::ChronoTime(Some(Box::new(val.clone())))) + .collect(), + )), + ), + #[cfg(all( + feature = "with-time", + not(feature = "with-chrono"), + feature = "postgres-array" + ))] + "TIME[]" => Value::Array( + sea_query::ArrayType::TimeTime, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get time array") + .iter() + .map(|val| Value::TimeTime(Some(Box::new(val.clone())))) + .collect(), )), - #[cfg(feature = "postgres-array")] - "OID[]" => Value::Array( - sea_query::ArrayType::BigInt, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get oid array") - .iter() - .map(|val| Value::BigInt(Some(*val))) - .collect(), - )), - ), - - "JSON" | "JSONB" => Value::Json(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get json"), - ))), - #[cfg(any(feature = "json-array", feature = "postgres-array"))] - "JSON[]" | "JSONB[]" => Value::Array( - sea_query::ArrayType::Json, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get json array") - .iter() - .map(|val| Value::Json(Some(Box::new(val.clone())))) - .collect(), - )), - ), - - #[cfg(feature = "with-ipnetwork")] - "INET" | "CIDR" => Value::IpNetwork(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get ip address"), - ))), - #[cfg(feature = "with-ipnetwork")] - "INET[]" | "CIDR[]" => Value::Array( - sea_query::ArrayType::IpNetwork, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get ip address array") - .iter() - .map(|val| Value::IpNetwork(Some(Box::new(val.clone())))) - .collect(), - )), - ), - - #[cfg(feature = "with-mac_address")] - "MACADDR" | "MACADDR8" => Value::MacAddress(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get mac address"), - ))), - #[cfg(all(feature = "with-mac_address", feature = "postgres-array"))] - "MACADDR[]" | "MACADDR8[]" => Value::Array( - sea_query::ArrayType::MacAddress, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get mac address array") - .iter() - .map(|val| Value::MacAddress(Some(Box::new(val.clone())))) - .collect(), - )), - ), - - #[cfg(feature = "with-chrono")] - "TIMESTAMP" => Value::ChronoDateTime(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get timestamp"), - ))), - #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] - "TIMESTAMP" => Value::TimeDateTime(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get timestamp"), - ))), - - #[cfg(all(feature = "with-chrono", feature = "postgres-array"))] - "TIMESTAMP[]" => Value::Array( - sea_query::ArrayType::ChronoDateTime, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get timestamp array") - .iter() - .map(|val| { - Value::ChronoDateTime(Some(Box::new(val.clone()))) - }) - .collect(), - )), - ), - #[cfg(all( - feature = "with-time", - not(feature = "with-chrono"), - feature = "postgres-array" - ))] - "TIMESTAMP[]" => Value::Array( - sea_query::ArrayType::TimeDateTime, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get timestamp array") - .iter() - .map(|val| Value::TimeDateTime(Some(Box::new(val.clone())))) - .collect(), - )), - ), - - #[cfg(feature = "with-chrono")] - "DATE" => Value::ChronoDate(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get date"), - ))), - #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] - "DATE" => Value::TimeDate(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get date"), - ))), - - #[cfg(all(feature = "with-chrono", feature = "postgres-array"))] - "DATE[]" => Value::Array( - sea_query::ArrayType::ChronoDate, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get date array") - .iter() - .map(|val| Value::ChronoDate(Some(Box::new(val.clone())))) - .collect(), - )), - ), - #[cfg(all( - feature = "with-time", - not(feature = "with-chrono"), - feature = "postgres-array" - ))] - "DATE[]" => Value::Array( - sea_query::ArrayType::TimeDate, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get date array") - .iter() - .map(|val| Value::TimeDate(Some(Box::new(val.clone())))) - .collect(), - )), - ), - - #[cfg(feature = "with-chrono")] - "TIME" => Value::ChronoTime(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get time"), - ))), - #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] - "TIME" => Value::TimeTime(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get time"), - ))), - - #[cfg(all(feature = "with-chrono", feature = "postgres-array"))] - "TIME[]" => Value::Array( - sea_query::ArrayType::ChronoTime, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get time array") - .iter() - .map(|val| Value::ChronoTime(Some(Box::new(val.clone())))) - .collect(), - )), - ), - #[cfg(all( - feature = "with-time", - not(feature = "with-chrono"), - feature = "postgres-array" - ))] - "TIME[]" => Value::Array( - sea_query::ArrayType::TimeTime, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get time array") - .iter() - .map(|val| Value::TimeTime(Some(Box::new(val.clone())))) - .collect(), - )), - ), - - #[cfg(feature = "with-chrono")] - "TIMESTAMPTZ" => Value::ChronoDateTimeUtc(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get timestamptz"), - ))), - #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] - "TIMESTAMPTZ" => Value::TimeDateTime(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get timestamptz"), - ))), - - #[cfg(all(feature = "with-chrono", feature = "postgres-array"))] - "TIMESTAMPTZ[]" => Value::Array( - sea_query::ArrayType::ChronoDateTimeUtc, - Some(Box::new( - row.try_get::>, _>( - c.ordinal(), - ) + ), + + #[cfg(feature = "with-chrono")] + "TIMESTAMPTZ" => Value::ChronoDateTimeUtc(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get timestamptz"), + ))), + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] + "TIMESTAMPTZ" => Value::TimeDateTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get timestamptz"), + ))), + + #[cfg(all(feature = "with-chrono", feature = "postgres-array"))] + "TIMESTAMPTZ[]" => Value::Array( + sea_query::ArrayType::ChronoDateTimeUtc, + Some(Box::new( + row.try_get::>, _>(c.ordinal()) .expect("Failed to get timestamptz array") .iter() .map(|val| { Value::ChronoDateTimeUtc(Some(Box::new(val.clone()))) }) .collect(), - )), - ), - #[cfg(all( - feature = "with-time", - not(feature = "with-chrono"), - feature = "postgres-array" - ))] - "TIMESTAMPTZ[]" => Value::Array( - sea_query::ArrayType::TimeDateTime, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get timestamptz array") - .iter() - .map(|val| Value::TimeDateTime(Some(Box::new(val.clone())))) - .collect(), - )), - ), - - #[cfg(feature = "with-chrono")] - "TIMETZ" => Value::ChronoTime(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get timetz"), - ))), - #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] - "TIMETZ" => Value::TimeTime(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get timetz"), - ))), - - #[cfg(all(feature = "with-chrono", feature = "postgres-array"))] - "TIMETZ[]" => Value::Array( - sea_query::ArrayType::ChronoTime, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get timetz array") - .iter() - .map(|val| Value::ChronoTime(Some(Box::new(val.clone())))) - .collect(), - )), - ), - #[cfg(all( - feature = "with-time", - not(feature = "with-chrono"), - feature = "postgres-array" - ))] - "TIMETZ[]" => Value::Array( - sea_query::ArrayType::TimeTime, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get timetz array") - .iter() - .map(|val| Value::TimeTime(Some(Box::new(val.clone())))) - .collect(), - )), - ), - - #[cfg(feature = "with-uuid")] - "UUID" => Value::Uuid(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get uuid"), - ))), - - #[cfg(all(feature = "with-uuid", feature = "postgres-array"))] - "UUID[]" => Value::Array( - sea_query::ArrayType::Uuid, - Some(Box::new( - row.try_get::, _>(c.ordinal()) - .expect("Failed to get uuid array") - .iter() - .map(|val| Value::Uuid(Some(Box::new(val.clone())))) - .collect(), - )), - ), - - _ => unreachable!("Unknown column type: {}", c.type_info().name()), - }, - ) - }) - .collect(), - } - } -} - -#[cfg(feature = "sqlx-sqlite")] -impl From for ProxyRow { - fn from(row: sqlx::sqlite::SqliteRow) -> Self { - // https://docs.rs/sqlx-sqlite/0.7.2/src/sqlx_sqlite/type_info.rs.html - // https://docs.rs/sqlx-sqlite/0.7.2/sqlx_sqlite/types/index.html - use sqlx::{Column, Row, TypeInfo}; - Self { - values: row - .columns() - .iter() - .map(|c| { - ( - c.name().to_string(), - match c.type_info().name() { - "BOOLEAN" => Value::Bool(Some( - row.try_get(c.ordinal()).expect("Failed to get boolean"), )), - - "INTEGER" => Value::Int(Some( - row.try_get(c.ordinal()).expect("Failed to get integer"), + ), + #[cfg(all( + feature = "with-time", + not(feature = "with-chrono"), + feature = "postgres-array" + ))] + "TIMESTAMPTZ[]" => Value::Array( + sea_query::ArrayType::TimeDateTime, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get timestamptz array") + .iter() + .map(|val| Value::TimeDateTime(Some(Box::new(val.clone())))) + .collect(), )), - - "BIGINT" | "INT8" => Value::BigInt(Some( - row.try_get(c.ordinal()).expect("Failed to get big integer"), + ), + + #[cfg(feature = "with-chrono")] + "TIMETZ" => Value::ChronoTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get timetz"), + ))), + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] + "TIMETZ" => Value::TimeTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get timetz"), + ))), + + #[cfg(all(feature = "with-chrono", feature = "postgres-array"))] + "TIMETZ[]" => Value::Array( + sea_query::ArrayType::ChronoTime, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get timetz array") + .iter() + .map(|val| Value::ChronoTime(Some(Box::new(val.clone())))) + .collect(), )), - - "REAL" => Value::Double(Some( - row.try_get(c.ordinal()).expect("Failed to get double"), + ), + #[cfg(all( + feature = "with-time", + not(feature = "with-chrono"), + feature = "postgres-array" + ))] + "TIMETZ[]" => Value::Array( + sea_query::ArrayType::TimeTime, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get timetz array") + .iter() + .map(|val| Value::TimeTime(Some(Box::new(val.clone())))) + .collect(), + )), + ), + + #[cfg(feature = "with-uuid")] + "UUID" => Value::Uuid(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get uuid"), + ))), + + #[cfg(all(feature = "with-uuid", feature = "postgres-array"))] + "UUID[]" => Value::Array( + sea_query::ArrayType::Uuid, + Some(Box::new( + row.try_get::, _>(c.ordinal()) + .expect("Failed to get uuid array") + .iter() + .map(|val| Value::Uuid(Some(Box::new(val.clone())))) + .collect(), )), + ), - "TEXT" => Value::String(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get string"), - ))), - - "BLOB" => Value::Bytes(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get bytes"), - ))), - - #[cfg(feature = "with-chrono")] - "DATETIME" => Value::ChronoDateTimeUtc(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get timestamp"), - ))), - #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] - "DATETIME" => Value::TimeDateTimeWithTimeZone(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get timestamp"), - ))), - - #[cfg(feature = "with-chrono")] - "DATE" => Value::ChronoDate(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get date"), - ))), - #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] - "DATE" => Value::TimeDate(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get date"), - ))), - - #[cfg(feature = "with-chrono")] - "TIME" => Value::ChronoTime(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get time"), - ))), - #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] - "TIME" => Value::TimeTime(Some(Box::new( - row.try_get(c.ordinal()).expect("Failed to get time"), - ))), - - _ => unreachable!("Unknown column type: {}", c.type_info().name()), - }, - ) - }) - .collect(), - } + _ => unreachable!("Unknown column type: {}", c.type_info().name()), + }, + ) + }) + .collect(), } } -#[cfg(feature = "mock")] -impl From for ProxyRow { - fn from(row: crate::MockRow) -> Self { - Self { - values: row.values.into_iter().collect(), - } +#[cfg(feature = "sqlx-sqlite")] +pub(crate) fn from_sqlx_sqlite_row_to_proxy_row(row: &sqlx::sqlite::SqliteRow) -> ProxyRow { + // https://docs.rs/sqlx-sqlite/0.7.2/src/sqlx_sqlite/type_info.rs.html + // https://docs.rs/sqlx-sqlite/0.7.2/sqlx_sqlite/types/index.html + use sqlx::{Column, Row, TypeInfo}; + ProxyRow { + values: row + .columns() + .iter() + .map(|c| { + ( + c.name().to_string(), + match c.type_info().name() { + "BOOLEAN" => Value::Bool(Some( + row.try_get(c.ordinal()).expect("Failed to get boolean"), + )), + + "INTEGER" => Value::Int(Some( + row.try_get(c.ordinal()).expect("Failed to get integer"), + )), + + "BIGINT" | "INT8" => Value::BigInt(Some( + row.try_get(c.ordinal()).expect("Failed to get big integer"), + )), + + "REAL" => Value::Double(Some( + row.try_get(c.ordinal()).expect("Failed to get double"), + )), + + "TEXT" => Value::String(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get string"), + ))), + + "BLOB" => Value::Bytes(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get bytes"), + ))), + + #[cfg(feature = "with-chrono")] + "DATETIME" => Value::ChronoDateTimeUtc(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get timestamp"), + ))), + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] + "DATETIME" => Value::TimeDateTimeWithTimeZone(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get timestamp"), + ))), + + #[cfg(feature = "with-chrono")] + "DATE" => Value::ChronoDate(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get date"), + ))), + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] + "DATE" => Value::TimeDate(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get date"), + ))), + + #[cfg(feature = "with-chrono")] + "TIME" => Value::ChronoTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get time"), + ))), + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] + "TIME" => Value::TimeTime(Some(Box::new( + row.try_get(c.ordinal()).expect("Failed to get time"), + ))), + + _ => unreachable!("Unknown column type: {}", c.type_info().name()), + }, + ) + }) + .collect(), } } From 2c90866be79fc594b3e94750703ac32465be54be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Mon, 23 Oct 2023 22:47:21 +0800 Subject: [PATCH 37/49] fix: Multiple time library reference. --- src/database/proxy.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/database/proxy.rs b/src/database/proxy.rs index db3c7f388..470810670 100644 --- a/src/database/proxy.rs +++ b/src/database/proxy.rs @@ -231,7 +231,7 @@ pub(crate) fn from_sqlx_mysql_row_to_proxy_row(row: &sqlx::mysql::MySqlRow) -> P "TIMESTAMP" => Value::ChronoDateTimeUtc(Some(Box::new( row.try_get(c.ordinal()).expect("Failed to get timestamp"), ))), - #[cfg(feature = "with-time")] + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] "TIMESTAMP" => Value::TimeDateTime(Some(Box::new( row.try_get(c.ordinal()).expect("Failed to get timestamp"), ))), @@ -240,7 +240,7 @@ pub(crate) fn from_sqlx_mysql_row_to_proxy_row(row: &sqlx::mysql::MySqlRow) -> P "DATE" => Value::ChronoDate(Some(Box::new( row.try_get(c.ordinal()).expect("Failed to get date"), ))), - #[cfg(feature = "with-time")] + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] "DATE" => Value::TimeDate(Some(Box::new( row.try_get(c.ordinal()).expect("Failed to get date"), ))), @@ -249,7 +249,7 @@ pub(crate) fn from_sqlx_mysql_row_to_proxy_row(row: &sqlx::mysql::MySqlRow) -> P "TIME" => Value::ChronoTime(Some(Box::new( row.try_get(c.ordinal()).expect("Failed to get time"), ))), - #[cfg(feature = "with-time")] + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] "TIME" => Value::TimeTime(Some(Box::new( row.try_get(c.ordinal()).expect("Failed to get time"), ))), @@ -258,7 +258,7 @@ pub(crate) fn from_sqlx_mysql_row_to_proxy_row(row: &sqlx::mysql::MySqlRow) -> P "DATETIME" => Value::ChronoDateTime(Some(Box::new( row.try_get(c.ordinal()).expect("Failed to get datetime"), ))), - #[cfg(feature = "with-time")] + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] "DATETIME" => Value::TimeDateTime(Some(Box::new( row.try_get(c.ordinal()).expect("Failed to get datetime"), ))), @@ -267,7 +267,7 @@ pub(crate) fn from_sqlx_mysql_row_to_proxy_row(row: &sqlx::mysql::MySqlRow) -> P "YEAR" => Value::ChronoDate(Some(Box::new( row.try_get(c.ordinal()).expect("Failed to get year"), ))), - #[cfg(feature = "with-time")] + #[cfg(all(feature = "with-time", not(feature = "with-chrono")))] "YEAR" => Value::TimeDate(Some(Box::new( row.try_get(c.ordinal()).expect("Failed to get year"), ))), From ecbc2f3564aa08a81ede5778ec30c730cac4f8ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Tue, 24 Oct 2023 01:11:28 +0800 Subject: [PATCH 38/49] feat: Add a new proxy example which uses GlueSQL. --- examples/proxy_gluesql_example/Cargo.toml | 29 ++++ examples/proxy_gluesql_example/README.md | 7 + .../proxy_gluesql_example/src/entity/mod.rs | 1 + .../proxy_gluesql_example/src/entity/post.rs | 17 +++ examples/proxy_gluesql_example/src/main.rs | 138 ++++++++++++++++++ 5 files changed, 192 insertions(+) create mode 100644 examples/proxy_gluesql_example/Cargo.toml create mode 100644 examples/proxy_gluesql_example/README.md create mode 100644 examples/proxy_gluesql_example/src/entity/mod.rs create mode 100644 examples/proxy_gluesql_example/src/entity/post.rs create mode 100644 examples/proxy_gluesql_example/src/main.rs diff --git a/examples/proxy_gluesql_example/Cargo.toml b/examples/proxy_gluesql_example/Cargo.toml new file mode 100644 index 000000000..9e21bee18 --- /dev/null +++ b/examples/proxy_gluesql_example/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "sea-orm-proxy-gluesql-example" +version = "0.1.0" +authors = ["Langyo "] +edition = "2021" +publish = false + +[workspace] + +[dependencies] +async-std = { version = "1.12", features = ["attributes", "tokio1"] } +serde_json = { version = "1" } +serde = { version = "1" } +futures = { version = "0.3" } +async-stream = { version = "0.3" } +futures-util = { version = "0.3" } + +sea-orm = { path = "../../", features = [ + "sqlx-all", + "proxy", + "runtime-async-std-native-tls", + "debug-print", +] } +# Since it's newer version (0.14.0) locked the chrono's version to 0.4.23, +# we need to lock it on older version too. +# Related to https://github.com/gluesql/gluesql/pull/1427 +gluesql = { version = "0.13", default-features = false, features = [ + "memory-storage", +] } diff --git a/examples/proxy_gluesql_example/README.md b/examples/proxy_gluesql_example/README.md new file mode 100644 index 000000000..2269c471e --- /dev/null +++ b/examples/proxy_gluesql_example/README.md @@ -0,0 +1,7 @@ +# SeaORM Proxy Demo for GlueSQL + +Run this demo for [GlueSQL](https://gluesql.org/) with the following command: + +```bash +cargo run +``` diff --git a/examples/proxy_gluesql_example/src/entity/mod.rs b/examples/proxy_gluesql_example/src/entity/mod.rs new file mode 100644 index 000000000..e8b6291ac --- /dev/null +++ b/examples/proxy_gluesql_example/src/entity/mod.rs @@ -0,0 +1 @@ +pub mod post; diff --git a/examples/proxy_gluesql_example/src/entity/post.rs b/examples/proxy_gluesql_example/src/entity/post.rs new file mode 100644 index 000000000..868846046 --- /dev/null +++ b/examples/proxy_gluesql_example/src/entity/post.rs @@ -0,0 +1,17 @@ +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Deserialize, Serialize)] +#[sea_orm(table_name = "posts")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + + pub title: String, + pub text: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/proxy_gluesql_example/src/main.rs b/examples/proxy_gluesql_example/src/main.rs new file mode 100644 index 000000000..942efd462 --- /dev/null +++ b/examples/proxy_gluesql_example/src/main.rs @@ -0,0 +1,138 @@ +//! Proxy connection example. + +#![deny(missing_docs)] + +mod entity; + +use std::{ + collections::BTreeMap, + sync::{Arc, Mutex}, +}; + +use gluesql::{memory_storage::MemoryStorage, prelude::Glue}; +use sea_orm::{ + ActiveValue::Set, Database, DbBackend, DbErr, EntityTrait, ProxyDatabaseTrait, ProxyExecResult, + ProxyRow, Statement, +}; + +use entity::post::{ActiveModel, Entity}; + +struct ProxyDb { + mem: Mutex>, +} + +impl std::fmt::Debug for ProxyDb { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ProxyDb").finish() + } +} + +impl ProxyDatabaseTrait for ProxyDb { + fn query(&self, statement: Statement) -> Result, DbErr> { + println!("SQL query: {:?}", statement); + let sql = statement.sql.clone(); + + let mut ret: Vec = vec![]; + for payload in self.mem.lock().unwrap().execute(sql).unwrap().iter() { + match payload { + gluesql::prelude::Payload::Select { labels, rows } => { + for row in rows.iter() { + let mut map = BTreeMap::new(); + for (label, column) in labels.iter().zip(row.iter()) { + map.insert( + label.to_owned(), + match column { + gluesql::prelude::Value::I64(val) => { + sea_orm::Value::BigInt(Some(*val)) + } + gluesql::prelude::Value::Str(val) => { + sea_orm::Value::String(Some(Box::new(val.to_owned()))) + } + _ => unreachable!("Unsupported value: {:?}", column), + }, + ); + } + ret.push(map.into()); + } + } + _ => unreachable!("Unsupported payload: {:?}", payload), + } + } + + Ok(ret) + } + + fn execute(&self, statement: Statement) -> Result { + if let Some(values) = statement.values { + // Replace all the '?' with the statement values + let mut new_sql = statement.sql.clone(); + let mark_count = new_sql.matches('?').count(); + for (i, v) in values.0.iter().enumerate() { + if i >= mark_count { + break; + } + new_sql = new_sql.replacen('?', &v.to_string(), 1); + } + println!("SQL execute: {}", new_sql); + + self.mem.lock().unwrap().execute(new_sql).unwrap(); + } else { + self.mem.lock().unwrap().execute(statement.sql).unwrap(); + } + + Ok(ProxyExecResult { + last_insert_id: 1, + rows_affected: 1, + }) + } +} + +#[async_std::main] +async fn main() { + let mem = MemoryStorage::default(); + let mut glue = Glue::new(mem); + + glue.execute( + r#" + CREATE TABLE IF NOT EXISTS posts ( + id INTEGER PRIMARY KEY, + title TEXT NOT NULL, + text TEXT NOT NULL + ) + "#, + ) + .unwrap(); + + let db = Database::connect_proxy( + DbBackend::Sqlite, + Arc::new(Mutex::new(Box::new(ProxyDb { + mem: Mutex::new(glue), + }))), + ) + .await + .unwrap(); + + println!("Initialized"); + + let data = ActiveModel { + id: Set(11), + title: Set("Homo".to_owned()), + text: Set("いいよ、来いよ".to_owned()), + }; + Entity::insert(data).exec(&db).await.unwrap(); + let data = ActiveModel { + id: Set(45), + title: Set("Homo".to_owned()), + text: Set("そうだよ".to_owned()), + }; + Entity::insert(data).exec(&db).await.unwrap(); + let data = ActiveModel { + id: Set(14), + title: Set("Homo".to_owned()), + text: Set("悔い改めて".to_owned()), + }; + Entity::insert(data).exec(&db).await.unwrap(); + + let list = Entity::find().all(&db).await.unwrap().to_vec(); + println!("Result: {:?}", list); +} From c482b364ccc1418f7d5ef48dd517211522649628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Tue, 24 Oct 2023 01:18:52 +0800 Subject: [PATCH 39/49] test: Add the test cases for three new examples. Just try to run once... --- examples/proxy_gluesql_example/Cargo.toml | 4 ++++ examples/proxy_gluesql_example/src/main.rs | 8 ++++++++ examples/proxy_surrealdb_example/Cargo.toml | 4 ++++ examples/proxy_surrealdb_example/src/main.rs | 8 ++++++++ examples/proxy_wasmtime_example/Cargo.toml | 4 ++++ examples/proxy_wasmtime_example/src/main.rs | 10 ++++++++++ 6 files changed, 38 insertions(+) diff --git a/examples/proxy_gluesql_example/Cargo.toml b/examples/proxy_gluesql_example/Cargo.toml index 9e21bee18..1b30009e8 100644 --- a/examples/proxy_gluesql_example/Cargo.toml +++ b/examples/proxy_gluesql_example/Cargo.toml @@ -27,3 +27,7 @@ sea-orm = { path = "../../", features = [ gluesql = { version = "0.13", default-features = false, features = [ "memory-storage", ] } + +[dev-dependencies] +smol = { version = "1.2" } +smol-potat = { version = "1.1" } diff --git a/examples/proxy_gluesql_example/src/main.rs b/examples/proxy_gluesql_example/src/main.rs index 942efd462..616b612fa 100644 --- a/examples/proxy_gluesql_example/src/main.rs +++ b/examples/proxy_gluesql_example/src/main.rs @@ -136,3 +136,11 @@ async fn main() { let list = Entity::find().all(&db).await.unwrap().to_vec(); println!("Result: {:?}", list); } + +#[cfg(test)] +mod tests { + #[smol_potat::test] + async fn try_run() { + crate::main() + } +} diff --git a/examples/proxy_surrealdb_example/Cargo.toml b/examples/proxy_surrealdb_example/Cargo.toml index bd9106a71..9dc5b5456 100644 --- a/examples/proxy_surrealdb_example/Cargo.toml +++ b/examples/proxy_surrealdb_example/Cargo.toml @@ -22,3 +22,7 @@ sea-orm = { path = "../../", features = [ "debug-print", ] } surrealdb = { version = "1", features = ["kv-mem"] } + +[dev-dependencies] +smol = { version = "1.2" } +smol-potat = { version = "1.1" } diff --git a/examples/proxy_surrealdb_example/src/main.rs b/examples/proxy_surrealdb_example/src/main.rs index 4b94bcb6a..90610c18b 100644 --- a/examples/proxy_surrealdb_example/src/main.rs +++ b/examples/proxy_surrealdb_example/src/main.rs @@ -176,3 +176,11 @@ async fn main() { let list = Entity::find().all(&db).await.unwrap().to_vec(); println!("Result: {:?}", list); } + +#[cfg(test)] +mod tests { + #[smol_potat::test] + async fn try_run() { + crate::main() + } +} diff --git a/examples/proxy_wasmtime_example/Cargo.toml b/examples/proxy_wasmtime_example/Cargo.toml index a3145e35e..1076e85b4 100644 --- a/examples/proxy_wasmtime_example/Cargo.toml +++ b/examples/proxy_wasmtime_example/Cargo.toml @@ -30,3 +30,7 @@ sea-orm = { path = "../..", features = [ "sqlx-sqlite", "runtime-async-std-rustls", ] } + +[dev-dependencies] +smol = { version = "1.2" } +smol-potat = { version = "1.1" } diff --git a/examples/proxy_wasmtime_example/src/main.rs b/examples/proxy_wasmtime_example/src/main.rs index 5be4d679f..544b56abd 100644 --- a/examples/proxy_wasmtime_example/src/main.rs +++ b/examples/proxy_wasmtime_example/src/main.rs @@ -97,3 +97,13 @@ async fn main() -> Result<()> { Ok(()) } + +#[cfg(test)] +mod tests { + use anyhow::Result; + + #[smol_potat::test] + async fn try_run() -> Result<()> { + crate::main() + } +} From b1274dcef2e18d385bc17df0ea91c79bf384a5ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Tue, 24 Oct 2023 09:29:55 +0800 Subject: [PATCH 40/49] ci: Add wasm component's compiler for unit test. --- examples/proxy_wasmtime_example/src/main.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/examples/proxy_wasmtime_example/src/main.rs b/examples/proxy_wasmtime_example/src/main.rs index 544b56abd..e4c25333e 100644 --- a/examples/proxy_wasmtime_example/src/main.rs +++ b/examples/proxy_wasmtime_example/src/main.rs @@ -104,6 +104,19 @@ mod tests { #[smol_potat::test] async fn try_run() -> Result<()> { + // Build the wasm component binary + use std::{env, path::Path, process::Command}; + let pwd = Path::new(env!("CARGO_MANIFEST_DIR")).to_path_buf(); + Command::new("cargo") + .current_dir(pwd.clone()) + .arg("build") + .arg("--target") + .arg("wasm32-wasi") + .arg("--package") + .arg("module") + .arg("--release") + .status()?; + crate::main() } } From c90d0d07853b7ce10b71e21c9e5ad3fa2f5e08fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Tue, 24 Oct 2023 09:44:50 +0800 Subject: [PATCH 41/49] ci: Add wasi target. --- .github/workflows/rust.yml | 47 +++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6e3a523bd..4c8ea2cef 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,15 +1,15 @@ # GitHub Actions with Conditional Job Running Based on Commit Message -# +# # -------------------------------------------------------------------------------- -# +# # Following jobs will always run -# +# # - `clippy` # - `test` # - `examples` -# +# # Following jobs will be run when no keywords were found in commit message) -# +# # - `compile-sqlite` # - `sqlite` # - `compile-mysql` @@ -17,37 +17,36 @@ # - `mariadb` # - `compile-postgres` # - `postgres` -# +# # Following jobs will be run if keywords `[issues]` were found in commit message -# +# # - Jobs that will always run # - `issues` -# +# # Following jobs will be run if keywords `[cli]` were found in commit message -# +# # - Jobs that will always run # - `cli` -# +# # Following jobs will be run if keywords `[sqlite]` were found in commit message -# +# # - Jobs that will always run # - `compile-sqlite` # - `sqlite` -# +# # Following jobs will be run if keywords `[mysql]` were found in commit message -# +# # - Jobs that will always run # - `compile-mysql` # - `mysql` # - `mariadb` -# +# # Following jobs will be run if keywords `[postgres]` were found in commit message -# +# # - Jobs that will always run # - `compile-postgres` # - `postgres` - name: tests on: @@ -73,7 +72,6 @@ env: CARGO_TERM_COLOR: always jobs: - init: name: Init runs-on: ubuntu-latest @@ -252,6 +250,7 @@ jobs: with: toolchain: nightly components: rustfmt + - run: rustup target add wasm32-wasi - run: find ${{ matrix.path }} -type f -name 'Cargo.toml' -print0 | xargs -t -0 -I {} cargo fmt --manifest-path {} -- --check - uses: dtolnay/rust-toolchain@stable - run: find ${{ matrix.path }} -type f -name 'Cargo.toml' -print0 | xargs -t -0 -I {} cargo build --manifest-path {} @@ -299,7 +298,7 @@ jobs: }} runs-on: ubuntu-latest env: - DATABASE_URL: "sqlite::memory:" + DATABASE_URL: 'sqlite::memory:' strategy: fail-fast: false matrix: @@ -331,7 +330,7 @@ jobs: }} runs-on: ubuntu-latest env: - DATABASE_URL: "mysql://root:@localhost" + DATABASE_URL: 'mysql://root:@localhost' strategy: fail-fast: false matrix: @@ -349,7 +348,7 @@ jobs: MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_ROOT_PASSWORD: ports: - - "3306:3306" + - '3306:3306' options: >- --health-cmd="mysqladmin ping" --health-interval=10s @@ -381,7 +380,7 @@ jobs: }} runs-on: ubuntu-latest env: - DATABASE_URL: "mysql://root:@localhost" + DATABASE_URL: 'mysql://root:@localhost' strategy: fail-fast: false matrix: @@ -399,7 +398,7 @@ jobs: MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_ROOT_PASSWORD: ports: - - "3306:3306" + - '3306:3306' options: >- --health-cmd="mysqladmin ping" --health-interval=10s @@ -430,7 +429,7 @@ jobs: }} runs-on: ubuntu-latest env: - DATABASE_URL: "postgres://root:root@localhost" + DATABASE_URL: 'postgres://root:root@localhost' strategy: fail-fast: false matrix: @@ -445,7 +444,7 @@ jobs: POSTGRES_USER: root POSTGRES_PASSWORD: root ports: - - "5432:5432" + - '5432:5432' options: >- --health-cmd pg_isready --health-interval 10s From 3282413340b973f5b017e423704b8884e3431ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Tue, 24 Oct 2023 09:56:07 +0800 Subject: [PATCH 42/49] ci: It may needs wasi target twice... --- .github/workflows/rust.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 4c8ea2cef..9371ed643 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -253,6 +253,7 @@ jobs: - run: rustup target add wasm32-wasi - run: find ${{ matrix.path }} -type f -name 'Cargo.toml' -print0 | xargs -t -0 -I {} cargo fmt --manifest-path {} -- --check - uses: dtolnay/rust-toolchain@stable + - run: rustup target add wasm32-wasi - run: find ${{ matrix.path }} -type f -name 'Cargo.toml' -print0 | xargs -t -0 -I {} cargo build --manifest-path {} - run: find ${{ matrix.path }} -type f -name 'Cargo.toml' -print0 | xargs -t -0 -I {} cargo test --manifest-path {} - run: ${{'! '}}${{ '[ -d "' }}${{ matrix.path }}${{ '/service" ]' }} || find ${{ matrix.path }}/service -type f -name 'Cargo.toml' -print0 | xargs -t -0 -I {} cargo test --manifest-path {} --features mock From 0cb42c2cd35387a336b48d590729ed8d0d0a1ff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Fri, 27 Oct 2023 17:13:23 +0800 Subject: [PATCH 43/49] feat: Add more keys for proxy execute result. To transfer the fully information of the execute result. --- examples/proxy_gluesql_example/src/main.rs | 5 +-- examples/proxy_surrealdb_example/src/main.rs | 5 +-- src/database/proxy.rs | 33 +++++++++++++++----- src/executor/execute.rs | 11 ++++++- 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/examples/proxy_gluesql_example/src/main.rs b/examples/proxy_gluesql_example/src/main.rs index 616b612fa..59efb7d1c 100644 --- a/examples/proxy_gluesql_example/src/main.rs +++ b/examples/proxy_gluesql_example/src/main.rs @@ -4,6 +4,7 @@ mod entity; +use serde_json::json; use std::{ collections::BTreeMap, sync::{Arc, Mutex}, @@ -12,7 +13,7 @@ use std::{ use gluesql::{memory_storage::MemoryStorage, prelude::Glue}; use sea_orm::{ ActiveValue::Set, Database, DbBackend, DbErr, EntityTrait, ProxyDatabaseTrait, ProxyExecResult, - ProxyRow, Statement, + ProxyInsertResult, ProxyRow, Statement, }; use entity::post::{ActiveModel, Entity}; @@ -81,7 +82,7 @@ impl ProxyDatabaseTrait for ProxyDb { } Ok(ProxyExecResult { - last_insert_id: 1, + last_insert_id: ProxyInsertResult::Inserted(vec![json!(1)]), rows_affected: 1, }) } diff --git a/examples/proxy_surrealdb_example/src/main.rs b/examples/proxy_surrealdb_example/src/main.rs index 90610c18b..28a2bdc0d 100644 --- a/examples/proxy_surrealdb_example/src/main.rs +++ b/examples/proxy_surrealdb_example/src/main.rs @@ -4,6 +4,7 @@ mod entity; +use serde_json::json; use std::{ collections::BTreeMap, sync::{Arc, Mutex}, @@ -11,7 +12,7 @@ use std::{ use sea_orm::{ ActiveValue::Set, Database, DbBackend, DbErr, EntityTrait, ProxyDatabaseTrait, ProxyExecResult, - ProxyRow, Statement, + ProxyInsertResult, ProxyRow, Statement, }; use surrealdb::{ engine::local::{Db, Mem}, @@ -134,7 +135,7 @@ impl ProxyDatabaseTrait for ProxyDb { .unwrap(); Ok(ProxyExecResult { - last_insert_id: 1, + last_insert_id: ProxyInsertResult::Inserted(vec![json!(1)]), rows_affected: 1, }) } diff --git a/src/database/proxy.rs b/src/database/proxy.rs index 470810670..0cbec48d5 100644 --- a/src/database/proxy.rs +++ b/src/database/proxy.rs @@ -1,6 +1,7 @@ use crate::{error::*, ExecResult, ExecResultHolder, QueryResult, QueryResultRow, Statement}; use sea_query::{Value, ValueType}; +use serde_json::json; use std::{collections::BTreeMap, fmt::Debug}; /// Defines the [ProxyDatabaseTrait] to save the functions @@ -26,18 +27,30 @@ pub trait ProxyDatabaseTrait: Send + Sync + std::fmt::Debug { } } +/// The types of results for a proxy INSERT operation +#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] +pub enum ProxyInsertResult { + /// The INSERT statement did not have any value to insert + #[default] + Empty, + /// The INSERT operation did not insert any valid value + Conflicted, + /// Successfully inserted + Inserted(Vec), +} + /// Defines the results obtained from a [ProxyDatabase] #[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] pub struct ProxyExecResult { /// The last inserted id on auto-increment - pub last_insert_id: u64, + pub last_insert_id: ProxyInsertResult, /// The number of rows affected by the database operation pub rows_affected: u64, } impl ProxyExecResult { /// Create a new [ProxyExecResult] from the last inserted id and the number of rows affected - pub fn new(last_insert_id: u64, rows_affected: u64) -> Self { + pub fn new(last_insert_id: ProxyInsertResult, rows_affected: u64) -> Self { Self { last_insert_id, rows_affected, @@ -64,22 +77,26 @@ impl From for ProxyExecResult { match result.result { #[cfg(feature = "sqlx-mysql")] ExecResultHolder::SqlxMySql(result) => Self { - last_insert_id: result.last_insert_id() as u64, + last_insert_id: ProxyInsertResult::Inserted(vec![json!( + result.last_insert_id() as u64 + )]), rows_affected: result.rows_affected(), }, #[cfg(feature = "sqlx-postgres")] ExecResultHolder::SqlxPostgres(result) => Self { - last_insert_id: 0, + last_insert_id: ProxyInsertResult::Empty, rows_affected: result.rows_affected(), }, #[cfg(feature = "sqlx-sqlite")] ExecResultHolder::SqlxSqlite(result) => Self { - last_insert_id: result.last_insert_rowid() as u64, + last_insert_id: ProxyInsertResult::Inserted(vec![json!( + result.last_insert_rowid() as u64 + )]), rows_affected: result.rows_affected(), }, #[cfg(feature = "mock")] ExecResultHolder::Mock(result) => Self { - last_insert_id: result.last_insert_id, + last_insert_id: ProxyInsertResult::Inserted(vec![json!(result.last_insert_id)]), rows_affected: result.rows_affected, }, ExecResultHolder::Proxy(result) => result, @@ -866,6 +883,8 @@ impl ProxyRow { #[cfg(test)] mod tests { + use serde_json::json; + use crate::{ entity::*, tests_cfg::*, Database, DbBackend, DbErr, ProxyDatabaseTrait, ProxyExecResult, ProxyRow, Statement, @@ -884,7 +903,7 @@ mod tests { fn execute(&self, statement: Statement) -> Result { println!("SQL execute: {}", statement.sql); Ok(ProxyExecResult { - last_insert_id: 1, + last_insert_id: crate::ProxyInsertResult::Inserted(vec![json!(1)]), rows_affected: 1, }) } diff --git a/src/executor/execute.rs b/src/executor/execute.rs index 626dd81b8..b60a59c4e 100644 --- a/src/executor/execute.rs +++ b/src/executor/execute.rs @@ -1,3 +1,5 @@ +use crate::ProxyInsertResult; + /// Defines the result of executing an operation #[derive(Debug)] pub struct ExecResult { @@ -55,7 +57,14 @@ impl ExecResult { #[cfg(feature = "mock")] ExecResultHolder::Mock(result) => result.last_insert_id, #[cfg(feature = "proxy")] - ExecResultHolder::Proxy(result) => result.last_insert_id, + ExecResultHolder::Proxy(result) => match &result.last_insert_id { + ProxyInsertResult::Empty | ProxyInsertResult::Conflicted => 0, + ProxyInsertResult::Inserted(val) => val + .first() + .expect("Cannot get first value of proxy insert result") + .as_u64() + .expect("Cannot convert proxy id to u64"), + }, #[allow(unreachable_patterns)] _ => unreachable!(), } From 956dfa8e859d7e082aeb49e2777ace34ef1c3b42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Fri, 27 Oct 2023 18:54:28 +0800 Subject: [PATCH 44/49] fix: Use custom id type instead of json value. --- examples/proxy_gluesql_example/src/main.rs | 9 +- examples/proxy_surrealdb_example/src/main.rs | 9 +- src/database/proxy.rs | 97 ++++++++++++++------ src/executor/execute.rs | 13 +-- 4 files changed, 78 insertions(+), 50 deletions(-) diff --git a/examples/proxy_gluesql_example/src/main.rs b/examples/proxy_gluesql_example/src/main.rs index 59efb7d1c..712f2b312 100644 --- a/examples/proxy_gluesql_example/src/main.rs +++ b/examples/proxy_gluesql_example/src/main.rs @@ -13,7 +13,7 @@ use std::{ use gluesql::{memory_storage::MemoryStorage, prelude::Glue}; use sea_orm::{ ActiveValue::Set, Database, DbBackend, DbErr, EntityTrait, ProxyDatabaseTrait, ProxyExecResult, - ProxyInsertResult, ProxyRow, Statement, + ProxyExecResultIdType, ProxyRow, Statement, }; use entity::post::{ActiveModel, Entity}; @@ -81,10 +81,9 @@ impl ProxyDatabaseTrait for ProxyDb { self.mem.lock().unwrap().execute(statement.sql).unwrap(); } - Ok(ProxyExecResult { - last_insert_id: ProxyInsertResult::Inserted(vec![json!(1)]), - rows_affected: 1, - }) + Ok(ProxyExecResult::Inserted(vec![ + ProxyExecResultIdType::Integer(1), + ])) } } diff --git a/examples/proxy_surrealdb_example/src/main.rs b/examples/proxy_surrealdb_example/src/main.rs index 28a2bdc0d..fa6ac657b 100644 --- a/examples/proxy_surrealdb_example/src/main.rs +++ b/examples/proxy_surrealdb_example/src/main.rs @@ -12,7 +12,7 @@ use std::{ use sea_orm::{ ActiveValue::Set, Database, DbBackend, DbErr, EntityTrait, ProxyDatabaseTrait, ProxyExecResult, - ProxyInsertResult, ProxyRow, Statement, + ProxyExecResultIdType, ProxyRow, Statement, }; use surrealdb::{ engine::local::{Db, Mem}, @@ -134,10 +134,9 @@ impl ProxyDatabaseTrait for ProxyDb { }) .unwrap(); - Ok(ProxyExecResult { - last_insert_id: ProxyInsertResult::Inserted(vec![json!(1)]), - rows_affected: 1, - }) + Ok(ProxyExecResult::Inserted(vec![ + ProxyExecResultIdType::Integer(1), + ])) } } diff --git a/src/database/proxy.rs b/src/database/proxy.rs index 0cbec48d5..de7854f74 100644 --- a/src/database/proxy.rs +++ b/src/database/proxy.rs @@ -1,7 +1,6 @@ use crate::{error::*, ExecResult, ExecResultHolder, QueryResult, QueryResultRow, Statement}; use sea_query::{Value, ValueType}; -use serde_json::json; use std::{collections::BTreeMap, fmt::Debug}; /// Defines the [ProxyDatabaseTrait] to save the functions @@ -27,33 +26,75 @@ pub trait ProxyDatabaseTrait: Send + Sync + std::fmt::Debug { } } -/// The types of results for a proxy INSERT operation +/// The id type for [ProxyExecResult] +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub enum ProxyExecResultIdType { + /// Integer + Integer(u64), + /// UUID + Uuid(uuid::Uuid), + /// String + String(String), + /// Bytes + Bytes(Vec), +} + +impl Into for ProxyExecResultIdType { + fn into(self) -> u64 { + match self { + Self::Integer(val) => val, + Self::String(val) => val.parse().unwrap_or(0), + Self::Bytes(val) => { + // It would crash if it's longer than 8 bytes + if val.len() > 8 { + panic!("Bytes is longer than 8 bytes") + } + + let mut bytes = [0u8; 8]; + bytes.copy_from_slice(&val[..8]); + u64::from_le_bytes(bytes) + } + Self::Uuid(_) => panic!("Uuid cannot be converted to u64 that not lose precision"), + } + } +} + +/// Defines the results obtained from a [ProxyDatabase] #[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] -pub enum ProxyInsertResult { +pub enum ProxyExecResult { /// The INSERT statement did not have any value to insert #[default] Empty, /// The INSERT operation did not insert any valid value Conflicted, /// Successfully inserted - Inserted(Vec), -} - -/// Defines the results obtained from a [ProxyDatabase] -#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] -pub struct ProxyExecResult { - /// The last inserted id on auto-increment - pub last_insert_id: ProxyInsertResult, - /// The number of rows affected by the database operation - pub rows_affected: u64, + Inserted(Vec), } impl ProxyExecResult { - /// Create a new [ProxyExecResult] from the last inserted id and the number of rows affected - pub fn new(last_insert_id: ProxyInsertResult, rows_affected: u64) -> Self { - Self { - last_insert_id, - rows_affected, + /// Get the last id after `AUTOINCREMENT` is done on the primary key + /// + /// # Panics + /// + /// The proxy list is empty or the last value cannot convert to u64 + pub fn last_insert_id(&self) -> u64 { + match self { + Self::Empty | Self::Conflicted => 0, + Self::Inserted(val) => { + let ret = val + .last() + .expect("Cannot get last value of proxy insert result"); + let ret: u64 = ret.clone().into(); + ret + } + } + } + + /// Get the number of rows affected by the operation + pub fn rows_affected(&self) -> u64 { + match self { + Self::Empty | Self::Conflicted => 0, + Self::Inserted(val) => val.len() as u64, } } } @@ -95,10 +136,11 @@ impl From for ProxyExecResult { rows_affected: result.rows_affected(), }, #[cfg(feature = "mock")] - ExecResultHolder::Mock(result) => Self { - last_insert_id: ProxyInsertResult::Inserted(vec![json!(result.last_insert_id)]), - rows_affected: result.rows_affected, - }, + ExecResultHolder::Mock(result) => { + ProxyExecResult::Inserted(vec![ProxyExecResultIdType::Integer( + result.last_insert_id, + )]) + } ExecResultHolder::Proxy(result) => result, } } @@ -883,11 +925,9 @@ impl ProxyRow { #[cfg(test)] mod tests { - use serde_json::json; - use crate::{ entity::*, tests_cfg::*, Database, DbBackend, DbErr, ProxyDatabaseTrait, ProxyExecResult, - ProxyRow, Statement, + ProxyExecResultIdType, ProxyRow, Statement, }; use std::sync::{Arc, Mutex}; @@ -902,10 +942,9 @@ mod tests { fn execute(&self, statement: Statement) -> Result { println!("SQL execute: {}", statement.sql); - Ok(ProxyExecResult { - last_insert_id: crate::ProxyInsertResult::Inserted(vec![json!(1)]), - rows_affected: 1, - }) + Ok(ProxyExecResult::Inserted(vec![ + ProxyExecResultIdType::Integer(1), + ])) } } diff --git a/src/executor/execute.rs b/src/executor/execute.rs index b60a59c4e..ace9b637f 100644 --- a/src/executor/execute.rs +++ b/src/executor/execute.rs @@ -1,5 +1,3 @@ -use crate::ProxyInsertResult; - /// Defines the result of executing an operation #[derive(Debug)] pub struct ExecResult { @@ -57,14 +55,7 @@ impl ExecResult { #[cfg(feature = "mock")] ExecResultHolder::Mock(result) => result.last_insert_id, #[cfg(feature = "proxy")] - ExecResultHolder::Proxy(result) => match &result.last_insert_id { - ProxyInsertResult::Empty | ProxyInsertResult::Conflicted => 0, - ProxyInsertResult::Inserted(val) => val - .first() - .expect("Cannot get first value of proxy insert result") - .as_u64() - .expect("Cannot convert proxy id to u64"), - }, + ExecResultHolder::Proxy(result) => result.last_insert_id(), #[allow(unreachable_patterns)] _ => unreachable!(), } @@ -82,7 +73,7 @@ impl ExecResult { #[cfg(feature = "mock")] ExecResultHolder::Mock(result) => result.rows_affected, #[cfg(feature = "proxy")] - ExecResultHolder::Proxy(result) => result.rows_affected, + ExecResultHolder::Proxy(result) => result.rows_affected(), #[allow(unreachable_patterns)] _ => unreachable!(), } From 25662eaaeb064226ed400575069a1f3c2989c1aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Fri, 27 Oct 2023 19:09:21 +0800 Subject: [PATCH 45/49] fix: Wrong reference type. --- src/database/proxy.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/database/proxy.rs b/src/database/proxy.rs index de7854f74..f8898d754 100644 --- a/src/database/proxy.rs +++ b/src/database/proxy.rs @@ -118,21 +118,17 @@ impl From for ProxyExecResult { match result.result { #[cfg(feature = "sqlx-mysql")] ExecResultHolder::SqlxMySql(result) => Self { - last_insert_id: ProxyInsertResult::Inserted(vec![json!( - result.last_insert_id() as u64 - )]), + last_insert_id: result.last_insert_id(), rows_affected: result.rows_affected(), }, #[cfg(feature = "sqlx-postgres")] ExecResultHolder::SqlxPostgres(result) => Self { - last_insert_id: ProxyInsertResult::Empty, + last_insert_id: ProxyExecResult::Empty, rows_affected: result.rows_affected(), }, #[cfg(feature = "sqlx-sqlite")] ExecResultHolder::SqlxSqlite(result) => Self { - last_insert_id: ProxyInsertResult::Inserted(vec![json!( - result.last_insert_rowid() as u64 - )]), + last_insert_id: result.last_insert_id(), rows_affected: result.rows_affected(), }, #[cfg(feature = "mock")] From a1693cf802b202b71298b0118d53487811346778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Fri, 27 Oct 2023 19:20:31 +0800 Subject: [PATCH 46/49] fix: Rewrite the transformer. --- src/database/proxy.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/database/proxy.rs b/src/database/proxy.rs index f8898d754..46338e454 100644 --- a/src/database/proxy.rs +++ b/src/database/proxy.rs @@ -117,20 +117,19 @@ impl From for ProxyExecResult { fn from(result: ExecResult) -> Self { match result.result { #[cfg(feature = "sqlx-mysql")] - ExecResultHolder::SqlxMySql(result) => Self { - last_insert_id: result.last_insert_id(), - rows_affected: result.rows_affected(), - }, + ExecResultHolder::SqlxMySql(result) => { + ProxyExecResult::Inserted(vec![ProxyExecResultIdType::Integer( + result.last_insert_id().unwrap_or(0), + )]) + } #[cfg(feature = "sqlx-postgres")] - ExecResultHolder::SqlxPostgres(result) => Self { - last_insert_id: ProxyExecResult::Empty, - rows_affected: result.rows_affected(), - }, + ExecResultHolder::SqlxPostgres(result) => ProxyExecResult::Inserted(vec![]), #[cfg(feature = "sqlx-sqlite")] - ExecResultHolder::SqlxSqlite(result) => Self { - last_insert_id: result.last_insert_id(), - rows_affected: result.rows_affected(), - }, + ExecResultHolder::SqlxSqlite(result) => { + ProxyExecResult::Inserted(vec![ProxyExecResultIdType::Integer( + result.last_insert_rowid() as u64, + )]) + } #[cfg(feature = "mock")] ExecResultHolder::Mock(result) => { ProxyExecResult::Inserted(vec![ProxyExecResultIdType::Integer( From 9bac6e91ca9df04ccd8368906e1613cfc5b96218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Sat, 28 Oct 2023 02:10:59 +0800 Subject: [PATCH 47/49] perf: Add ToString trait for proxy exec result. --- src/database/proxy.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/database/proxy.rs b/src/database/proxy.rs index 46338e454..f2cacb252 100644 --- a/src/database/proxy.rs +++ b/src/database/proxy.rs @@ -59,6 +59,23 @@ impl Into for ProxyExecResultIdType { } } +impl Into for ProxyExecResultIdType { + fn into(self) -> String { + self.to_string() + } +} + +impl ToString for ProxyExecResultIdType { + fn to_string(&self) -> String { + match self { + Self::Integer(val) => val.to_string(), + Self::String(val) => val.to_owned(), + Self::Bytes(val) => String::from_utf8_lossy(&val).to_string(), + Self::Uuid(val) => val.to_string(), + } + } +} + /// Defines the results obtained from a [ProxyDatabase] #[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] pub enum ProxyExecResult { From 151eb1d0743a6f34004187443325d42c37b58ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Sat, 28 Oct 2023 22:15:43 +0800 Subject: [PATCH 48/49] revert: Again. Refs: 9bac6e91ca9df04ccd8368906e1613cfc5b96218 --- src/database/proxy.rs | 2 +- src/executor/insert.rs | 43 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/database/proxy.rs b/src/database/proxy.rs index f2cacb252..0d069c658 100644 --- a/src/database/proxy.rs +++ b/src/database/proxy.rs @@ -136,7 +136,7 @@ impl From for ProxyExecResult { #[cfg(feature = "sqlx-mysql")] ExecResultHolder::SqlxMySql(result) => { ProxyExecResult::Inserted(vec![ProxyExecResultIdType::Integer( - result.last_insert_id().unwrap_or(0), + result.last_insert_id(), )]) } #[cfg(feature = "sqlx-postgres")] diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 2f6aa74ce..bfd0692a6 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -1,7 +1,7 @@ use crate::{ - error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, Insert, IntoActiveModel, - Iterable, PrimaryKeyToColumn, PrimaryKeyTrait, SelectModel, SelectorRaw, Statement, TryFromU64, - TryInsert, + error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, ExecResultHolder, + Insert, IntoActiveModel, Iterable, PrimaryKeyToColumn, PrimaryKeyTrait, ProxyExecResult, + ProxyExecResultIdType, SelectModel, SelectorRaw, Statement, TryFromU64, TryInsert, }; use sea_query::{Expr, FromValueTuple, Iden, InsertStatement, IntoColumnRef, Query, ValueTuple}; use std::{future::Future, marker::PhantomData}; @@ -241,8 +241,41 @@ where if res.rows_affected() == 0 { return Err(DbErr::RecordNotInserted); } - let last_insert_id = res.last_insert_id(); - ValueTypeOf::::try_from_u64(last_insert_id).map_err(|_| DbErr::UnpackInsertId)? + match res.result { + ExecResultHolder::Proxy(val) => match val { + ProxyExecResult::Empty | ProxyExecResult::Conflicted => { + return Err(DbErr::RecordNotInserted); + } + ProxyExecResult::Inserted(val) => { + match val.last().ok_or(DbErr::UnpackInsertId)? { + ProxyExecResultIdType::Integer(val) => { + ValueTypeOf::::try_from_u64(*val) + .map_err(|_| DbErr::UnpackInsertId)? + } + ProxyExecResultIdType::String(val) => { + ValueTypeOf::::from_value_tuple(ValueTuple::One( + sea_query::Value::String(Some(Box::new(val.to_owned()))), + )) + } + ProxyExecResultIdType::Uuid(val) => { + ValueTypeOf::::from_value_tuple(ValueTuple::One( + sea_query::Value::Uuid(Some(Box::new(val.to_owned()))), + )) + } + ProxyExecResultIdType::Bytes(val) => { + ValueTypeOf::::from_value_tuple(ValueTuple::One( + sea_query::Value::Bytes(Some(Box::new(val.to_owned()))), + )) + } + } + } + }, + _ => { + let last_insert_id = res.last_insert_id(); + ValueTypeOf::::try_from_u64(last_insert_id) + .map_err(|_| DbErr::UnpackInsertId)? + } + } } }; From ba09d842eee4247fa7f1bbbe44d0814e49bdc108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Sat, 28 Oct 2023 22:45:37 +0800 Subject: [PATCH 49/49] revert: Back to the basic proxy exec result. Refs: e0330dde73a54d461d5f38c69eec5e13bcc928d4 --- examples/proxy_gluesql_example/src/main.rs | 10 +- examples/proxy_surrealdb_example/src/main.rs | 10 +- src/database/proxy.rs | 132 +++++-------------- src/executor/execute.rs | 4 +- src/executor/insert.rs | 43 +----- 5 files changed, 48 insertions(+), 151 deletions(-) diff --git a/examples/proxy_gluesql_example/src/main.rs b/examples/proxy_gluesql_example/src/main.rs index 712f2b312..616b612fa 100644 --- a/examples/proxy_gluesql_example/src/main.rs +++ b/examples/proxy_gluesql_example/src/main.rs @@ -4,7 +4,6 @@ mod entity; -use serde_json::json; use std::{ collections::BTreeMap, sync::{Arc, Mutex}, @@ -13,7 +12,7 @@ use std::{ use gluesql::{memory_storage::MemoryStorage, prelude::Glue}; use sea_orm::{ ActiveValue::Set, Database, DbBackend, DbErr, EntityTrait, ProxyDatabaseTrait, ProxyExecResult, - ProxyExecResultIdType, ProxyRow, Statement, + ProxyRow, Statement, }; use entity::post::{ActiveModel, Entity}; @@ -81,9 +80,10 @@ impl ProxyDatabaseTrait for ProxyDb { self.mem.lock().unwrap().execute(statement.sql).unwrap(); } - Ok(ProxyExecResult::Inserted(vec![ - ProxyExecResultIdType::Integer(1), - ])) + Ok(ProxyExecResult { + last_insert_id: 1, + rows_affected: 1, + }) } } diff --git a/examples/proxy_surrealdb_example/src/main.rs b/examples/proxy_surrealdb_example/src/main.rs index fa6ac657b..90610c18b 100644 --- a/examples/proxy_surrealdb_example/src/main.rs +++ b/examples/proxy_surrealdb_example/src/main.rs @@ -4,7 +4,6 @@ mod entity; -use serde_json::json; use std::{ collections::BTreeMap, sync::{Arc, Mutex}, @@ -12,7 +11,7 @@ use std::{ use sea_orm::{ ActiveValue::Set, Database, DbBackend, DbErr, EntityTrait, ProxyDatabaseTrait, ProxyExecResult, - ProxyExecResultIdType, ProxyRow, Statement, + ProxyRow, Statement, }; use surrealdb::{ engine::local::{Db, Mem}, @@ -134,9 +133,10 @@ impl ProxyDatabaseTrait for ProxyDb { }) .unwrap(); - Ok(ProxyExecResult::Inserted(vec![ - ProxyExecResultIdType::Integer(1), - ])) + Ok(ProxyExecResult { + last_insert_id: 1, + rows_affected: 1, + }) } } diff --git a/src/database/proxy.rs b/src/database/proxy.rs index 0d069c658..470810670 100644 --- a/src/database/proxy.rs +++ b/src/database/proxy.rs @@ -26,92 +26,21 @@ pub trait ProxyDatabaseTrait: Send + Sync + std::fmt::Debug { } } -/// The id type for [ProxyExecResult] -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] -pub enum ProxyExecResultIdType { - /// Integer - Integer(u64), - /// UUID - Uuid(uuid::Uuid), - /// String - String(String), - /// Bytes - Bytes(Vec), -} - -impl Into for ProxyExecResultIdType { - fn into(self) -> u64 { - match self { - Self::Integer(val) => val, - Self::String(val) => val.parse().unwrap_or(0), - Self::Bytes(val) => { - // It would crash if it's longer than 8 bytes - if val.len() > 8 { - panic!("Bytes is longer than 8 bytes") - } - - let mut bytes = [0u8; 8]; - bytes.copy_from_slice(&val[..8]); - u64::from_le_bytes(bytes) - } - Self::Uuid(_) => panic!("Uuid cannot be converted to u64 that not lose precision"), - } - } -} - -impl Into for ProxyExecResultIdType { - fn into(self) -> String { - self.to_string() - } -} - -impl ToString for ProxyExecResultIdType { - fn to_string(&self) -> String { - match self { - Self::Integer(val) => val.to_string(), - Self::String(val) => val.to_owned(), - Self::Bytes(val) => String::from_utf8_lossy(&val).to_string(), - Self::Uuid(val) => val.to_string(), - } - } -} - /// Defines the results obtained from a [ProxyDatabase] #[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] -pub enum ProxyExecResult { - /// The INSERT statement did not have any value to insert - #[default] - Empty, - /// The INSERT operation did not insert any valid value - Conflicted, - /// Successfully inserted - Inserted(Vec), +pub struct ProxyExecResult { + /// The last inserted id on auto-increment + pub last_insert_id: u64, + /// The number of rows affected by the database operation + pub rows_affected: u64, } impl ProxyExecResult { - /// Get the last id after `AUTOINCREMENT` is done on the primary key - /// - /// # Panics - /// - /// The proxy list is empty or the last value cannot convert to u64 - pub fn last_insert_id(&self) -> u64 { - match self { - Self::Empty | Self::Conflicted => 0, - Self::Inserted(val) => { - let ret = val - .last() - .expect("Cannot get last value of proxy insert result"); - let ret: u64 = ret.clone().into(); - ret - } - } - } - - /// Get the number of rows affected by the operation - pub fn rows_affected(&self) -> u64 { - match self { - Self::Empty | Self::Conflicted => 0, - Self::Inserted(val) => val.len() as u64, + /// Create a new [ProxyExecResult] from the last inserted id and the number of rows affected + pub fn new(last_insert_id: u64, rows_affected: u64) -> Self { + Self { + last_insert_id, + rows_affected, } } } @@ -134,25 +63,25 @@ impl From for ProxyExecResult { fn from(result: ExecResult) -> Self { match result.result { #[cfg(feature = "sqlx-mysql")] - ExecResultHolder::SqlxMySql(result) => { - ProxyExecResult::Inserted(vec![ProxyExecResultIdType::Integer( - result.last_insert_id(), - )]) - } + ExecResultHolder::SqlxMySql(result) => Self { + last_insert_id: result.last_insert_id() as u64, + rows_affected: result.rows_affected(), + }, #[cfg(feature = "sqlx-postgres")] - ExecResultHolder::SqlxPostgres(result) => ProxyExecResult::Inserted(vec![]), + ExecResultHolder::SqlxPostgres(result) => Self { + last_insert_id: 0, + rows_affected: result.rows_affected(), + }, #[cfg(feature = "sqlx-sqlite")] - ExecResultHolder::SqlxSqlite(result) => { - ProxyExecResult::Inserted(vec![ProxyExecResultIdType::Integer( - result.last_insert_rowid() as u64, - )]) - } + ExecResultHolder::SqlxSqlite(result) => Self { + last_insert_id: result.last_insert_rowid() as u64, + rows_affected: result.rows_affected(), + }, #[cfg(feature = "mock")] - ExecResultHolder::Mock(result) => { - ProxyExecResult::Inserted(vec![ProxyExecResultIdType::Integer( - result.last_insert_id, - )]) - } + ExecResultHolder::Mock(result) => Self { + last_insert_id: result.last_insert_id, + rows_affected: result.rows_affected, + }, ExecResultHolder::Proxy(result) => result, } } @@ -939,7 +868,7 @@ impl ProxyRow { mod tests { use crate::{ entity::*, tests_cfg::*, Database, DbBackend, DbErr, ProxyDatabaseTrait, ProxyExecResult, - ProxyExecResultIdType, ProxyRow, Statement, + ProxyRow, Statement, }; use std::sync::{Arc, Mutex}; @@ -954,9 +883,10 @@ mod tests { fn execute(&self, statement: Statement) -> Result { println!("SQL execute: {}", statement.sql); - Ok(ProxyExecResult::Inserted(vec![ - ProxyExecResultIdType::Integer(1), - ])) + Ok(ProxyExecResult { + last_insert_id: 1, + rows_affected: 1, + }) } } diff --git a/src/executor/execute.rs b/src/executor/execute.rs index ace9b637f..626dd81b8 100644 --- a/src/executor/execute.rs +++ b/src/executor/execute.rs @@ -55,7 +55,7 @@ impl ExecResult { #[cfg(feature = "mock")] ExecResultHolder::Mock(result) => result.last_insert_id, #[cfg(feature = "proxy")] - ExecResultHolder::Proxy(result) => result.last_insert_id(), + ExecResultHolder::Proxy(result) => result.last_insert_id, #[allow(unreachable_patterns)] _ => unreachable!(), } @@ -73,7 +73,7 @@ impl ExecResult { #[cfg(feature = "mock")] ExecResultHolder::Mock(result) => result.rows_affected, #[cfg(feature = "proxy")] - ExecResultHolder::Proxy(result) => result.rows_affected(), + ExecResultHolder::Proxy(result) => result.rows_affected, #[allow(unreachable_patterns)] _ => unreachable!(), } diff --git a/src/executor/insert.rs b/src/executor/insert.rs index bfd0692a6..2f6aa74ce 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -1,7 +1,7 @@ use crate::{ - error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, ExecResultHolder, - Insert, IntoActiveModel, Iterable, PrimaryKeyToColumn, PrimaryKeyTrait, ProxyExecResult, - ProxyExecResultIdType, SelectModel, SelectorRaw, Statement, TryFromU64, TryInsert, + error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, Insert, IntoActiveModel, + Iterable, PrimaryKeyToColumn, PrimaryKeyTrait, SelectModel, SelectorRaw, Statement, TryFromU64, + TryInsert, }; use sea_query::{Expr, FromValueTuple, Iden, InsertStatement, IntoColumnRef, Query, ValueTuple}; use std::{future::Future, marker::PhantomData}; @@ -241,41 +241,8 @@ where if res.rows_affected() == 0 { return Err(DbErr::RecordNotInserted); } - match res.result { - ExecResultHolder::Proxy(val) => match val { - ProxyExecResult::Empty | ProxyExecResult::Conflicted => { - return Err(DbErr::RecordNotInserted); - } - ProxyExecResult::Inserted(val) => { - match val.last().ok_or(DbErr::UnpackInsertId)? { - ProxyExecResultIdType::Integer(val) => { - ValueTypeOf::::try_from_u64(*val) - .map_err(|_| DbErr::UnpackInsertId)? - } - ProxyExecResultIdType::String(val) => { - ValueTypeOf::::from_value_tuple(ValueTuple::One( - sea_query::Value::String(Some(Box::new(val.to_owned()))), - )) - } - ProxyExecResultIdType::Uuid(val) => { - ValueTypeOf::::from_value_tuple(ValueTuple::One( - sea_query::Value::Uuid(Some(Box::new(val.to_owned()))), - )) - } - ProxyExecResultIdType::Bytes(val) => { - ValueTypeOf::::from_value_tuple(ValueTuple::One( - sea_query::Value::Bytes(Some(Box::new(val.to_owned()))), - )) - } - } - } - }, - _ => { - let last_insert_id = res.last_insert_id(); - ValueTypeOf::::try_from_u64(last_insert_id) - .map_err(|_| DbErr::UnpackInsertId)? - } - } + let last_insert_id = res.last_insert_id(); + ValueTypeOf::::try_from_u64(last_insert_id).map_err(|_| DbErr::UnpackInsertId)? } };