From 2fbc74a3f8c0e3a32f3249e8ab70297bc022b39b Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 13 Jan 2025 20:46:05 +0100 Subject: [PATCH 001/155] chore: Initial refactoring ideas for Canyon connection, crud and mapper --- .github/workflows/release.yml | 2 +- .../src/canyon_database_connector.rs | 42 +++++++++++++++++++ canyon_connection/src/lib.rs | 3 +- .../src/query_elements/query_builder.rs | 4 +- 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 91520071..0e0d093d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: Generate Canyon-SQL release on: push: - tags: + tags: - 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+rc[0-9]+' diff --git a/canyon_connection/src/canyon_database_connector.rs b/canyon_connection/src/canyon_database_connector.rs index 11530a7d..d82ea42c 100644 --- a/canyon_connection/src/canyon_database_connector.rs +++ b/canyon_connection/src/canyon_database_connector.rs @@ -57,6 +57,47 @@ pub struct MysqlConnection { pub client: Pool, } +/* + * NOTE: initial ideas for making a better codebase as well as we improve our public API in + * order to make the Canyon-SQL DAO code much more unit-testable for our end users + * + * 1- Type above are not *Connection, they are *Client. They also may be converted to unit structs. + * 2- DatabaseConnection is not again a Connection. Is an aggregate of Clients. + * 3- impl DatabaseClient (when renamed) should be refactored, since it's really bloated + * 4- Find a better way to handle the _()=> wildcard patterns on the matching ops + * + * 5- make public the function that retrieves the datasource from the global container, so + * users and macros can now fetch datasources, and macros fn params can be no ds => same + * behaviour, but calling the public api from the macro, avoiding to make public the global + * constant, only the function (that's really nice! :) ) and the *_datasource and *_ds functions + * can be now _conn or similar. Even we can decide to remove the default one and make always + * explicit the pass in of the connection + * + * 5.1- Make a new trait (this really can be a DatabaseConnection) that makes the execution of + * the queries a mockable entity by a third party library (ex: mockall). That would be an option + * over having *Connection + * + * 6- remove trait bounds that doesn't make sense. RowMapper shouldn't be :Transaction, + * QueryBuilder shouldn't be CrudOperations. There's for sure more + * + * 7- Consider if we need a new ` canyon_mapper` module to decouple the mapping data ideas from + * the CRUD idea + * + * 8- Review how the querybuilder fetches the connection, as we may acomplish the same goal as + * the one defined in 5, using datasources (or clients) + * + * 9- Abort the compilation process earlier if there's no datasource defined. Also, do the same + * for when there's no cfg feature selected (Canyon can't work without a database) and we may not + * write code that assumes a default NEVER + * + * 10- An idea for refactoring the global context of the datasources would be... to not to have + * it! That will force the user to manage the connection everytime, but produces less painful code + * without global contexts, but maybe we're able to find a better intermediate solution + * + * 11- Can we make the query executors being part of another different thing instead of CRUD? Can + * we made them into a better idea and execution of the same? + */ + /// The Canyon database connection handler. When the client's program /// starts, Canyon gets the information about the desired datasources, /// process them and generates a pool of 1 to 1 database connection for @@ -167,6 +208,7 @@ impl DatabaseConnection { Ok(DatabaseConnection::SqlServer(SqlServerConnection { client: Box::leak(Box::new( client.expect("A failure happened connecting to the database"), + // TODO: with details of the failure and the datasource that triggered them )), })) } diff --git a/canyon_connection/src/lib.rs b/canyon_connection/src/lib.rs index 5bd7a232..d0f4e973 100644 --- a/canyon_connection/src/lib.rs +++ b/canyon_connection/src/lib.rs @@ -47,7 +47,8 @@ fn find_canyon_config_file() -> PathBuf { .into_iter() .filter_map(|e| e.ok()) { - let filename = e.file_name().to_str().unwrap(); + let filename = e.file_name().to_str().unwrap(); // TODO: remove the .unwrap(). Use + // lowercase to allow Canyon.toml if e.metadata().unwrap().is_file() && filename.starts_with("canyon") && filename.ends_with(".toml") diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index 4d56401a..1294dbb8 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -337,7 +337,7 @@ where self } - /// Adds a *RIGHT JOIN* SQL statement to the underlying + /// Adds a *INNER JOIN* SQL statement to the underlying /// [`Query`] held by the [`QueryBuilder`], where: /// /// * `join_table` - The table target of the join operation @@ -494,7 +494,7 @@ where return self; } if self._inner.query.sql.contains("SET") { - panic!( + panic!( // TODO: this should return an Err and not panic! "\n{}", String::from("\t[PANIC!] - Don't use chained calls of the .set(...) method. ") + "\n\tPass all the values in a unique call within the 'columns' " From 2ecbddec2a99dd4edc2c014a206cf34cacbf7a07 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 14 Jan 2025 12:58:12 +0100 Subject: [PATCH 002/155] chore: Added a rust-analyzer specific config for the project, to enable all cfg features --- octocat.png | Bin 2468 -> 0 bytes rust-project.json | 11 +++++++++++ 2 files changed, 11 insertions(+) delete mode 100644 octocat.png create mode 100644 rust-project.json diff --git a/octocat.png b/octocat.png deleted file mode 100644 index f9050b935792512349e6f1ba0e6e55d99ecbf508..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2468 zcmdT_`8U)H8~>UyLoN;~FS1vbQdF{J8?J4NA)_*3EJd=#Ysq#qBaJ2d9!+*iOqN76 z(^xZO--od$2`LQ5i0|#b_pf+A=RBX~x96PabIucIjWQG9li&jYKmciOZ1Ygs5xr>DERy2_uF=WzDH z?*pQu$A67A2j97g#bUvYVfOAWxI||&X=%zQq$DMKdU`Ze6t`L1At50a78Yt3)UFz6 zLZMLb*EGl)1KE=xIDatIC?g}&*VotE+q>0TGCMo#=H})X6fiL{5$0xh-_CN4Quehz zAM0~(lvF`-vTbQ;;q=!Vo0$#_3`mKIZftB!PftfjM|*pFYiMe8baV_24S_!fIfD)0 zOq;K-?-9NudwYB10Y25?2~4K)K0N77Rnt?f zE1Yh}DvRH`ICrvj1Dmv1-U8)%QtNaV2D$%{knUKIrHY0|4MMNMl3W5NzqV zo86ST7|%~?E1`q<&UOw*uYY(ebJZ~|NOS7@W#sQnpDbYS_9KDp0cDm`BYpX)e{f9Fux9 zIBaHbgExwuw+dM73(IHL3{U;s7JF|gqtiLoF}NcIZQBUEcIl~CQ0y5axVBmuAZ{-A zAgTBPwfENu1Zj~_BHMQ|d!`7x1yBX$!(Zy8KA4gXmMyx3R4y zI5*C@cmz?}SVBEd-r68f(l4QqmpfUJ#~W+S%_&QRiZYcUj&;wwfTN+ZMi=}`nzhbL z7fP1)zQ(zz8R^NPv9KAGk*V;^T^}k#O58HIZxGIJV+N~4HH<*YqGgNuVn|1Fwgtmu zkS3PO%D0l8ROr*A!^7$1vke{((O2)#H*yfuqaM=*@4_hKWZxe73ONNt+%=_>|9|D zwpH@HH9r)ZtCRK#&k2I)Jv?!id=>bQs3+Gu5#X4NBW)2h^7_a;+`?oezT!HhMfcvD z><8mG&fOmsaf0FB-K)ibM?9q#hhM=L6KnPUP>D_{$bf-HSm<9(unyyeb zX#kQ;fX=PIrv7-0Xy=O7BH9+OeG@Vhr+$WUb1T`Zx~Qf{Cgu3%b&AMuNgMbYM2f1+ z!+|WN7BOin8eVH!U_3&#`);i*AZZTGV`OE1$wYX|XMHd#nQV8etCcSJ%Iu~lB zc5Aogv-&v;o;1t0C;N^S8w^NxnO zsS1<;NeAgBTIUI-iuy_43v|f9#yU|y$PCwz8e6-$S=;_a*wvpsh%SHXr?)?(u35XW zt{ECi;2jf*SI)jX85o>dSJe?Roo&v3V25WL z{Bh$!e*(toq=fQw(->1;4oh3IiSuTR(t^@#mKnUd!bkIES!ty zOiRQ~%m+a=f3uVn_ShKd;K@FhP?G@9yCs~obQSr?BL0?ezFa&v{q^5JSA_umycY#oGsD2MCI;jqMAX6hlh@>GIb7qHfpaMoXj zlbzvw+ApCJ{x4S0FtKXLb7>hIodmf&Qty1^yhGGI8>o9ks+Gg(IA#EZEn%LHKQYjK z8-ChZgt9}|(q2RA1_|dR@~A*BQ$EFu6y(pH3)Co3g*}bgmSe+|VvwRsl$lU>q0222 z`4z&d?l%N`sCtcZP7B}o43AcwD;#Ve9c)Fyu2|&%s0g4&e)1F1XV%ak|Nc1u(gbBp IGIEak8# Date: Thu, 16 Jan 2025 09:06:40 +0100 Subject: [PATCH 003/155] feat: Split the long create connection to database methods --- .../src/canyon_database_connector.rs | 527 +++++++++--------- canyon_connection/src/database_type.rs | 30 + canyon_connection/src/datasources.rs | 87 ++- canyon_connection/src/lib.rs | 19 +- 4 files changed, 384 insertions(+), 279 deletions(-) create mode 100644 canyon_connection/src/database_type.rs diff --git a/canyon_connection/src/canyon_database_connector.rs b/canyon_connection/src/canyon_database_connector.rs index d82ea42c..8cd37dc8 100644 --- a/canyon_connection/src/canyon_database_connector.rs +++ b/canyon_connection/src/canyon_database_connector.rs @@ -1,5 +1,3 @@ -use serde::Deserialize; - #[cfg(feature = "mssql")] use async_std::net::TcpStream; #[cfg(feature = "mysql")] @@ -9,34 +7,8 @@ use tiberius::{AuthMethod, Config}; #[cfg(feature = "postgres")] use tokio_postgres::{Client, NoTls}; -use crate::datasources::{Auth, DatasourceConfig}; - -/// Represents the current supported databases by Canyon -#[derive(Deserialize, Debug, Eq, PartialEq, Clone, Copy)] -pub enum DatabaseType { - #[serde(alias = "postgres", alias = "postgresql")] - #[cfg(feature = "postgres")] - PostgreSql, - #[serde(alias = "sqlserver", alias = "mssql")] - #[cfg(feature = "mssql")] - SqlServer, - #[serde(alias = "mysql")] - #[cfg(feature = "mysql")] - MySQL, -} - -impl From<&Auth> for DatabaseType { - fn from(value: &Auth) -> Self { - match value { - #[cfg(feature = "postgres")] - crate::datasources::Auth::Postgres(_) => DatabaseType::PostgreSql, - #[cfg(feature = "mssql")] - crate::datasources::Auth::SqlServer(_) => DatabaseType::SqlServer, - #[cfg(feature = "mysql")] - crate::datasources::Auth::MySQL(_) => DatabaseType::MySQL, - } - } -} +use crate::database_type::DatabaseType; +use crate::datasources::DatasourceConfig; /// A connection with a `PostgreSQL` database #[cfg(feature = "postgres")] @@ -57,54 +29,14 @@ pub struct MysqlConnection { pub client: Pool, } -/* - * NOTE: initial ideas for making a better codebase as well as we improve our public API in - * order to make the Canyon-SQL DAO code much more unit-testable for our end users - * - * 1- Type above are not *Connection, they are *Client. They also may be converted to unit structs. - * 2- DatabaseConnection is not again a Connection. Is an aggregate of Clients. - * 3- impl DatabaseClient (when renamed) should be refactored, since it's really bloated - * 4- Find a better way to handle the _()=> wildcard patterns on the matching ops - * - * 5- make public the function that retrieves the datasource from the global container, so - * users and macros can now fetch datasources, and macros fn params can be no ds => same - * behaviour, but calling the public api from the macro, avoiding to make public the global - * constant, only the function (that's really nice! :) ) and the *_datasource and *_ds functions - * can be now _conn or similar. Even we can decide to remove the default one and make always - * explicit the pass in of the connection - * - * 5.1- Make a new trait (this really can be a DatabaseConnection) that makes the execution of - * the queries a mockable entity by a third party library (ex: mockall). That would be an option - * over having *Connection - * - * 6- remove trait bounds that doesn't make sense. RowMapper shouldn't be :Transaction, - * QueryBuilder shouldn't be CrudOperations. There's for sure more - * - * 7- Consider if we need a new ` canyon_mapper` module to decouple the mapping data ideas from - * the CRUD idea - * - * 8- Review how the querybuilder fetches the connection, as we may acomplish the same goal as - * the one defined in 5, using datasources (or clients) - * - * 9- Abort the compilation process earlier if there's no datasource defined. Also, do the same - * for when there's no cfg feature selected (Canyon can't work without a database) and we may not - * write code that assumes a default NEVER - * - * 10- An idea for refactoring the global context of the datasources would be... to not to have - * it! That will force the user to manage the connection everytime, but produces less painful code - * without global contexts, but maybe we're able to find a better intermediate solution - * - * 11- Can we make the query executors being part of another different thing instead of CRUD? Can - * we made them into a better idea and execution of the same? - */ - /// The Canyon database connection handler. When the client's program /// starts, Canyon gets the information about the desired datasources, /// process them and generates a pool of 1 to 1 database connection for /// every datasource defined. pub enum DatabaseConnection { + // NOTE: is this a Datasource instead of a connection? #[cfg(feature = "postgres")] - Postgres(PostgreSqlConnection), + Postgres(PostgreSqlConnection), // NOTE: *Connection means *Client? #[cfg(feature = "mssql")] SqlServer(SqlServerConnection), #[cfg(feature = "mysql")] @@ -121,132 +53,16 @@ impl DatabaseConnection { match datasource.get_db_type() { #[cfg(feature = "postgres")] DatabaseType::PostgreSql => { - let (username, password) = match &datasource.auth { - crate::datasources::Auth::Postgres(postgres_auth) => match postgres_auth { - crate::datasources::PostgresAuth::Basic { username, password } => { - (username.as_str(), password.as_str()) - } - }, - #[cfg(feature = "mssql")] - crate::datasources::Auth::SqlServer(_) => { - panic!("Found SqlServer auth configuration for a PostgreSQL datasource") - } - #[cfg(feature = "mysql")] - crate::datasources::Auth::MySQL(_) => { - panic!("Found MySql auth configuration for a PostgreSQL datasource") - } - }; - let (new_client, new_connection) = tokio_postgres::connect( - &format!( - "postgres://{user}:{pswd}@{host}:{port}/{db}", - user = username, - pswd = password, - host = datasource.properties.host, - port = datasource.properties.port.unwrap_or_default(), - db = datasource.properties.db_name - )[..], - NoTls, - ) - .await?; - - tokio::spawn(async move { - if let Err(e) = new_connection.await { - eprintln!("An error occurred while trying to connect to the PostgreSQL database: {e}"); - } - }); - - Ok(DatabaseConnection::Postgres(PostgreSqlConnection { - client: new_client, - // connection: new_connection, - })) + connection_helpers::create_postgres_connection(datasource).await } + #[cfg(feature = "mssql")] DatabaseType::SqlServer => { - let mut config = Config::new(); - - config.host(&datasource.properties.host); - config.port(datasource.properties.port.unwrap_or_default()); - config.database(&datasource.properties.db_name); - - // Using SQL Server authentication. - config.authentication(match &datasource.auth { - #[cfg(feature = "postgres")] - crate::datasources::Auth::Postgres(_) => { - panic!("Found PostgreSQL auth configuration for a SqlServer database") - } - crate::datasources::Auth::SqlServer(sql_server_auth) => match sql_server_auth { - crate::datasources::SqlServerAuth::Basic { username, password } => { - AuthMethod::sql_server(username, password) - } - crate::datasources::SqlServerAuth::Integrated => AuthMethod::Integrated, - }, - #[cfg(feature = "mysql")] - crate::datasources::Auth::MySQL(_) => { - panic!("Found PostgreSQL auth configuration for a SqlServer database") - } - }); - - // on production, it is not a good idea to do this. We should upgrade - // Canyon in future versions to allow the user take care about this - // configuration - config.trust_cert(); - - // Taking the address from the configuration, using async-std's - // TcpStream to connect to the server. - let tcp = TcpStream::connect(config.get_addr()) - .await - .expect("Error instantiating the SqlServer TCP Stream"); - - // We'll disable the Nagle algorithm. Buffering is handled - // internally with a `Sink`. - tcp.set_nodelay(true) - .expect("Error in the SqlServer `nodelay` config"); - - // Handling TLS, login and other details related to the SQL Server. - let client = tiberius::Client::connect(config, tcp).await; - - Ok(DatabaseConnection::SqlServer(SqlServerConnection { - client: Box::leak(Box::new( - client.expect("A failure happened connecting to the database"), - // TODO: with details of the failure and the datasource that triggered them - )), - })) + connection_helpers::create_sqlserver_connection(datasource).await } - #[cfg(feature = "mysql")] - DatabaseType::MySQL => { - let (user, password) = match &datasource.auth { - #[cfg(feature = "mssql")] - crate::datasources::Auth::SqlServer(_) => { - panic!("Found SqlServer auth configuration for a PostgreSQL datasource") - } - #[cfg(feature = "postgres")] - crate::datasources::Auth::Postgres(_) => { - panic!("Found MySql auth configuration for a PostgreSQL datasource") - } - #[cfg(feature = "mysql")] - crate::datasources::Auth::MySQL(mysql_auth) => match mysql_auth { - crate::datasources::MySQLAuth::Basic { username, password } => { - (username, password) - } - }, - }; - - //TODO add options to optionals params in url - - let url = format!( - "mysql://{}:{}@{}:{}/{}", - user, - password, - datasource.properties.host, - datasource.properties.port.unwrap_or_default(), - datasource.properties.db_name - ); - let mysql_connection = Pool::from_url(url)?; - Ok(DatabaseConnection::MySQL(MysqlConnection { - client: { mysql_connection }, - })) - } + #[cfg(feature = "mysql")] + DatabaseType::MySQL => connection_helpers::create_mysql_connection(datasource).await, } } @@ -278,87 +94,256 @@ impl DatabaseConnection { } } -#[cfg(test)] -mod database_connection_handler { +pub mod connection_helpers { use super::*; - use crate::CanyonSqlConfig; - - /// Tests the behaviour of the `DatabaseType::from_datasource(...)` - #[test] - fn check_from_datasource() { - #[cfg(all(feature = "postgres", feature = "mssql", feature = "mysql"))] - { - const CONFIG_FILE_MOCK_ALT_ALL: &str = r#" - [canyon_sql] - datasources = [ - {name = 'PostgresDS', auth = { postgresql = { basic = { username = "postgres", password = "postgres" } } }, properties.host = 'localhost', properties.db_name = 'triforce', properties.migrations='enabled' }, - {name = 'SqlServerDS', auth = { sqlserver = { basic = { username = "sa", password = "SqlServer-10" } } }, properties.host = '192.168.0.250.1', properties.port = 3340, properties.db_name = 'triforce2', properties.migrations='disabled' }, - {name = 'MysqlDS', auth = { mysql = { basic = { username = "root", password = "root" } } }, properties.host = '192.168.0.250.1', properties.port = 3340, properties.db_name = 'triforce2', properties.migrations='disabled' } - ] - "#; - let config: CanyonSqlConfig = toml::from_str(CONFIG_FILE_MOCK_ALT_ALL) - .expect("A failure happened retrieving the [canyon_sql] section"); - assert_eq!( - config.canyon_sql.datasources[0].get_db_type(), - DatabaseType::PostgreSql - ); - assert_eq!( - config.canyon_sql.datasources[1].get_db_type(), - DatabaseType::SqlServer - ); - assert_eq!( - config.canyon_sql.datasources[2].get_db_type(), - DatabaseType::MySQL - ); - } - #[cfg(feature = "postgres")] - { - const CONFIG_FILE_MOCK_ALT_PG: &str = r#" - [canyon_sql] - datasources = [ - {name = 'PostgresDS', auth = { postgresql = { basic = { username = "postgres", password = "postgres" } } }, properties.host = 'localhost', properties.db_name = 'triforce', properties.migrations='enabled' }, - ] - "#; - let config: CanyonSqlConfig = toml::from_str(CONFIG_FILE_MOCK_ALT_PG) - .expect("A failure happened retrieving the [canyon_sql] section"); - assert_eq!( - config.canyon_sql.datasources[0].get_db_type(), - DatabaseType::PostgreSql - ); - } + #[cfg(feature = "postgres")] + pub async fn create_postgres_connection( + datasource: &DatasourceConfig, + ) -> Result> { + use crate::datasources::{Auth, PostgresAuth}; + + let (username, password) = match &datasource.auth { + Auth::Postgres(postgres_auth) + if matches!( + postgres_auth, + PostgresAuth::Basic { .. } + ) => + { + let PostgresAuth::Basic { username, password } = postgres_auth; + (username.as_str(), password.as_str()) + } + _ => panic!("Invalid auth configuration for a PostgreSQL datasource"), + }; + + let (new_client, new_connection) = tokio_postgres::connect( + &format!( + "postgres://{user}:{pswd}@{host}:{port}/{db}", + user = username, + pswd = password, + host = datasource.properties.host, + port = datasource.properties.port.unwrap_or_default(), + db = datasource.properties.db_name + ), + NoTls, + ) + .await?; + + tokio::spawn(async move { + if let Err(e) = new_connection.await { + eprintln!( + "An error occurred while trying to connect to the PostgreSQL database: {e}" + ); + } + }); - #[cfg(feature = "mssql")] - { - const CONFIG_FILE_MOCK_ALT_MSSQL: &str = r#" - [canyon_sql] - datasources = [ - {name = 'SqlServerDS', auth = { sqlserver = { basic = { username = "sa", password = "SqlServer-10" } } }, properties.host = '192.168.0.250.1', properties.port = 3340, properties.db_name = 'triforce2', properties.migrations='disabled' } - ] - "#; - let config: CanyonSqlConfig = toml::from_str(CONFIG_FILE_MOCK_ALT_MSSQL) - .expect("A failure happened retrieving the [canyon_sql] section"); - assert_eq!( - config.canyon_sql.datasources[0].get_db_type(), - DatabaseType::SqlServer - ); - } + Ok(DatabaseConnection::Postgres(PostgreSqlConnection { + client: new_client, + })) + } - #[cfg(feature = "mysql")] - { - const CONFIG_FILE_MOCK_ALT_MYSQL: &str = r#" - [canyon_sql] - datasources = [ - {name = 'MysqlDS', auth = { mysql = { basic = { username = "root", password = "root" } } }, properties.host = '192.168.0.250.1', properties.port = 3340, properties.db_name = 'triforce2', properties.migrations='disabled' } - ] - "#; - - let config: CanyonSqlConfig = toml::from_str(CONFIG_FILE_MOCK_ALT_MYSQL) - .expect("A failure happened retrieving the [canyon_sql] section"); - assert_eq!( - config.canyon_sql.datasources[0].get_db_type(), - DatabaseType::MySQL - ); - } + #[cfg(feature = "mssql")] + pub async fn create_sqlserver_connection( + datasource: &DatasourceConfig, + ) -> Result> { + let mut config = Config::new(); + + config.host(&datasource.properties.host); + config.port(datasource.properties.port.unwrap_or_default()); + config.database(&datasource.properties.db_name); + + config.authentication(match &datasource.auth { + crate::datasources::Auth::SqlServer(sql_server_auth) => match sql_server_auth { + crate::datasources::SqlServerAuth::Basic { username, password } => { + AuthMethod::sql_server(username, password) + } + crate::datasources::SqlServerAuth::Integrated => AuthMethod::Integrated, + }, + _ => panic!("Invalid auth configuration for a SqlServer datasource"), + }); + + config.trust_cert(); + + let tcp = TcpStream::connect(config.get_addr()) + .await + .expect("Error instantiating the SqlServer TCP Stream"); + + tcp.set_nodelay(true) + .expect("Error in the SqlServer `nodelay` config"); + + let client = tiberius::Client::connect(config, tcp).await?; + + Ok(DatabaseConnection::SqlServer(SqlServerConnection { + client: Box::leak(Box::new(client)), + })) + } + + #[cfg(feature = "mysql")] + pub async fn create_mysql_connection( + datasource: &DatasourceConfig, + ) -> Result> { + let (user, password) = match &datasource.auth { + crate::datasources::Auth::MySQL(crate::datasources::MySQLAuth::Basic { + username, + password, + }) => (username, password), + _ => panic!("Invalid auth configuration for a MySQL datasource"), + }; + + let url = format!( + "mysql://{}:{}@{}:{}/{}", + user, + password, + datasource.properties.host, + datasource.properties.port.unwrap_or_default(), + datasource.properties.db_name + ); + + let mysql_connection = Pool::from_url(url)?; + + Ok(DatabaseConnection::MySQL(MysqlConnection { + client: mysql_connection, + })) } } + +// NOTE: tests defined below should be integration tests, unfortunately, since they require a new connection to be made +// Or just to split them further, and just unit test the url string generation from the actual connection instantion +// #[cfg(test)] +// mod connection_tests { +// use tokio; +// use super::connection_helpers::*; +// use crate::{canyon_database_connector::DatabaseConnection, datasources::{Auth, DatasourceConfig, DatasourceProperties, PostgresAuth}}; + +// #[tokio::test] +// #[cfg(feature = "postgres")] +// async fn test_create_postgres_connection() { +// use crate::datasources::PostgresAuth; + +// let config = DatasourceConfig { +// name: "PostgresDs".to_string(), +// auth: Auth::Postgres(PostgresAuth::Basic { +// username: "test_user".to_string(), +// password: "test_password".to_string(), +// }), +// properties: DatasourceProperties { +// host: "localhost".to_string(), +// port: Some(5432), +// db_name: "test_db".to_string(), +// migrations: None +// }, +// }; + +// let result = create_postgres_connection(&config).await; +// assert!(result.is_ok()); +// } + +// #[tokio::test] +// #[cfg(feature = "mssql")] +// async fn test_create_sqlserver_connection() { +// use crate::datasources::SqlServerAuth; + +// let config = DatasourceConfig { +// name: "SqlServerDs".to_string(), +// auth: Auth::SqlServer(SqlServerAuth::Basic { +// username: "test_user".to_string(), +// password: "test_password".to_string(), +// }), +// properties: DatasourceProperties { +// host: "localhost".to_string(), +// port: Some(1433), +// db_name: "test_db".to_string(), +// migrations: None +// }, +// }; + +// let result = create_sqlserver_connection(&config).await; +// assert!(result.is_ok()); +// } + +// #[tokio::test] +// #[cfg(feature = "mysql")] +// async fn test_create_mysql_connection() { +// use crate::datasources::MySQLAuth; + +// let config = DatasourceConfig { +// name: "MySQLDs".to_string(), +// auth: Auth::MySQL(MySQLAuth::Basic { +// username: "test_user".to_string(), +// password: "test_password".to_string(), +// }), +// properties: DatasourceProperties { +// host: "localhost".to_string(), +// port: Some(3306), +// db_name: "test_db".to_string(), +// migrations: None, +// }, +// }; + +// let result = create_mysql_connection(&config).await; +// assert!(result.is_ok()); +// } + +// #[tokio::test] +// async fn test_database_connection_new() { +// #[cfg(feature = "postgres")] +// { +// use crate::datasources::PostgresAuth; + +// let config = DatasourceConfig { +// name: "PostgresDs".to_string(), +// auth: Auth::Postgres(PostgresAuth::Basic { +// username: "test_user".to_string(), +// password: "test_password".to_string(), +// }), +// properties: DatasourceProperties { +// host: "localhost".to_string(), +// port: Some(5432), +// db_name: "test_db".to_string(), +// migrations: None +// }, +// }; + +// let result = DatabaseConnection::new(&config).await; +// assert!(result.is_ok()); +// } + +// // #[cfg(feature = "mssql")] +// // { +// // let config = DatasourceConfig { +// // db_type: DatabaseType::SqlServer, +// // auth: Auth::SqlServer(SqlServerAuth::Basic { +// // username: "test_user".to_string(), +// // password: "test_password".to_string(), +// // }), +// // properties: crate::datasources::Properties { +// // host: "localhost".to_string(), +// // port: Some(1433), +// // db_name: "test_db".to_string(), +// // }, +// // }; + +// // let result = DatabaseConnection::new(&config).await; +// // assert!(result.is_ok()); +// // } + +// // #[cfg(feature = "mysql")] +// // { +// // let config = DatasourceConfig { +// // db_type: DatabaseType::MySQL, +// // auth: Auth::MySQL(MySQLAuth::Basic { +// // username: "test_user".to_string(), +// // password: "test_password".to_string(), +// // }), +// // properties: crate::datasources::Properties { +// // host: "localhost".to_string(), +// // port: Some(3306), +// // db_name: "test_db".to_string(), +// // }, +// // }; + +// // let result = DatabaseConnection::new(&config).await; +// // assert!(result.is_ok()); +// // } +// } +// } diff --git a/canyon_connection/src/database_type.rs b/canyon_connection/src/database_type.rs new file mode 100644 index 00000000..00153a28 --- /dev/null +++ b/canyon_connection/src/database_type.rs @@ -0,0 +1,30 @@ +use serde::Deserialize; + +use crate::datasources::Auth; + +/// Holds the current supported databases by Canyon-SQL +#[derive(Deserialize, Debug, Eq, PartialEq, Clone, Copy)] +pub enum DatabaseType { + #[cfg(feature = "postgres")] + #[serde(alias = "postgres", alias = "postgresql")] + PostgreSql, + #[cfg(feature = "mssql")] + #[serde(alias = "sqlserver", alias = "mssql")] + SqlServer, + #[cfg(feature = "mysql")] + #[serde(alias = "mysql")] + MySQL, +} + +impl From<&Auth> for DatabaseType { + fn from(value: &Auth) -> Self { + match value { + #[cfg(feature = "postgres")] + crate::datasources::Auth::Postgres(_) => DatabaseType::PostgreSql, + #[cfg(feature = "mssql")] + crate::datasources::Auth::SqlServer(_) => DatabaseType::SqlServer, + #[cfg(feature = "mysql")] + crate::datasources::Auth::MySQL(_) => DatabaseType::MySQL, + } + } +} \ No newline at end of file diff --git a/canyon_connection/src/datasources.rs b/canyon_connection/src/datasources.rs index 11edcd31..e118776c 100644 --- a/canyon_connection/src/datasources.rs +++ b/canyon_connection/src/datasources.rs @@ -1,6 +1,6 @@ use serde::Deserialize; -use crate::canyon_database_connector::DatabaseType; +use crate::database_type::DatabaseType; /// ``` #[test] @@ -175,3 +175,88 @@ pub enum Migrations { #[serde(alias = "Disabled", alias = "disabled")] Disabled, } + +#[cfg(test)] +mod datasources_tests { + use super::*; + use crate::CanyonSqlConfig; + + /// Tests the behaviour of the `DatabaseType::from_datasource(...)` + #[test] + fn check_from_datasource() { + #[cfg(all(feature = "postgres", feature = "mssql", feature = "mysql"))] + { + const CONFIG_FILE_MOCK_ALT_ALL: &str = r#" + [canyon_sql] + datasources = [ + {name = 'PostgresDS', auth = { postgresql = { basic = { username = "postgres", password = "postgres" } } }, properties.host = 'localhost', properties.db_name = 'triforce', properties.migrations='enabled' }, + {name = 'SqlServerDS', auth = { sqlserver = { basic = { username = "sa", password = "SqlServer-10" } } }, properties.host = '192.168.0.250.1', properties.port = 3340, properties.db_name = 'triforce2', properties.migrations='disabled' }, + {name = 'MysqlDS', auth = { mysql = { basic = { username = "root", password = "root" } } }, properties.host = '192.168.0.250.1', properties.port = 3340, properties.db_name = 'triforce2', properties.migrations='disabled' } + ] + "#; + let config: CanyonSqlConfig = toml::from_str(CONFIG_FILE_MOCK_ALT_ALL) + .expect("A failure happened retrieving the [canyon_sql] section"); + assert_eq!( + config.canyon_sql.datasources[0].get_db_type(), + DatabaseType::PostgreSql + ); + assert_eq!( + config.canyon_sql.datasources[1].get_db_type(), + DatabaseType::SqlServer + ); + assert_eq!( + config.canyon_sql.datasources[2].get_db_type(), + DatabaseType::MySQL + ); + } + + #[cfg(feature = "postgres")] + { + const CONFIG_FILE_MOCK_ALT_PG: &str = r#" + [canyon_sql] + datasources = [ + {name = 'PostgresDS', auth = { postgresql = { basic = { username = "postgres", password = "postgres" } } }, properties.host = 'localhost', properties.db_name = 'triforce', properties.migrations='enabled' }, + ] + "#; + let config: CanyonSqlConfig = toml::from_str(CONFIG_FILE_MOCK_ALT_PG) + .expect("A failure happened retrieving the [canyon_sql] section"); + assert_eq!( + config.canyon_sql.datasources[0].get_db_type(), + DatabaseType::PostgreSql + ); + } + + #[cfg(feature = "mssql")] + { + const CONFIG_FILE_MOCK_ALT_MSSQL: &str = r#" + [canyon_sql] + datasources = [ + {name = 'SqlServerDS', auth = { sqlserver = { basic = { username = "sa", password = "SqlServer-10" } } }, properties.host = '192.168.0.250.1', properties.port = 3340, properties.db_name = 'triforce2', properties.migrations='disabled' } + ] + "#; + let config: CanyonSqlConfig = toml::from_str(CONFIG_FILE_MOCK_ALT_MSSQL) + .expect("A failure happened retrieving the [canyon_sql] section"); + assert_eq!( + config.canyon_sql.datasources[0].get_db_type(), + DatabaseType::SqlServer + ); + } + + #[cfg(feature = "mysql")] + { + const CONFIG_FILE_MOCK_ALT_MYSQL: &str = r#" + [canyon_sql] + datasources = [ + {name = 'MysqlDS', auth = { mysql = { basic = { username = "root", password = "root" } } }, properties.host = '192.168.0.250.1', properties.port = 3340, properties.db_name = 'triforce2', properties.migrations='disabled' } + ] + "#; + + let config: CanyonSqlConfig = toml::from_str(CONFIG_FILE_MOCK_ALT_MYSQL) + .expect("A failure happened retrieving the [canyon_sql] section"); + assert_eq!( + config.canyon_sql.datasources[0].get_db_type(), + DatabaseType::MySQL + ); + } + } +} diff --git a/canyon_connection/src/lib.rs b/canyon_connection/src/lib.rs index d0f4e973..cc9997aa 100644 --- a/canyon_connection/src/lib.rs +++ b/canyon_connection/src/lib.rs @@ -1,17 +1,22 @@ +#[cfg(feature = "postgres")] +pub extern crate tokio_postgres; + #[cfg(feature = "mssql")] pub extern crate async_std; -pub extern crate futures; -pub extern crate lazy_static; -#[cfg(feature = "mysql")] -pub extern crate mysql_async; #[cfg(feature = "mssql")] pub extern crate tiberius; + +#[cfg(feature = "mysql")] +pub extern crate mysql_async; + +pub extern crate futures; +pub extern crate lazy_static; pub extern crate tokio; -#[cfg(feature = "postgres")] -pub extern crate tokio_postgres; pub extern crate tokio_util; pub mod canyon_database_connector; +pub mod database_type; + pub mod datasources; use std::fs; @@ -57,7 +62,7 @@ fn find_canyon_config_file() -> PathBuf { } } - panic!() + panic!() // TODO: get rid out of this panic and return Err instead } /// Convenient free function to initialize a kind of connection pool based on the datasources present defined From 818d0b70ebb10afe888201bad94b2e3eaeafe65e Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 16 Jan 2025 09:07:45 +0100 Subject: [PATCH 004/155] feat: Removed unnedeed trait bounds on the RowMapper trait --- canyon_crud/src/crud.rs | 3 ++- canyon_crud/src/lib.rs | 2 +- canyon_crud/src/mapper.rs | 3 +-- canyon_crud/src/query_elements/operators.rs | 2 +- canyon_crud/src/query_elements/query_builder.rs | 2 +- canyon_crud/src/rows.rs | 6 +----- 6 files changed, 7 insertions(+), 11 deletions(-) diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 981c24f1..ee53a43d 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -84,7 +84,7 @@ pub trait Transaction { #[async_trait] pub trait CrudOperations: Transaction where - T: CrudOperations + RowMapper, + T: CrudOperations + RowMapper, // TODO: do we need here the RowMapper bound? { async fn find_all<'a>() -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>>; @@ -198,6 +198,7 @@ mod sqlserver_query_launcher { Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, { // Re-generate de insert statement to adequate it to the SQL SERVER syntax to retrieve the PK value(s) after insert + // TODO: redo this branch into the generated queries, before the MACROS if stmt.contains("RETURNING") { let c = stmt.clone(); let temp = c.split_once("RETURNING").unwrap(); diff --git a/canyon_crud/src/lib.rs b/canyon_crud/src/lib.rs index cea474cb..20061096 100644 --- a/canyon_crud/src/lib.rs +++ b/canyon_crud/src/lib.rs @@ -9,5 +9,5 @@ pub mod rows; pub use query_elements::operators::*; -pub use canyon_connection::{canyon_database_connector::DatabaseType, datasources::*}; +pub use canyon_connection::{database_type::DatabaseType, datasources::*}; pub use chrono; diff --git a/canyon_crud/src/mapper.rs b/canyon_crud/src/mapper.rs index 252df1ce..9c23ff2e 100644 --- a/canyon_crud/src/mapper.rs +++ b/canyon_crud/src/mapper.rs @@ -5,12 +5,11 @@ use canyon_connection::tiberius; #[cfg(feature = "postgres")] use canyon_connection::tokio_postgres; -use crate::crud::Transaction; /// Declares functions that takes care to deserialize data incoming /// from some supported database in Canyon-SQL into a user's defined /// type `T` -pub trait RowMapper>: Sized { +pub trait RowMapper: Sized { #[cfg(feature = "postgres")] fn deserialize_postgresql(row: &tokio_postgres::Row) -> T; #[cfg(feature = "mssql")] diff --git a/canyon_crud/src/query_elements/operators.rs b/canyon_crud/src/query_elements/operators.rs index 015ced03..2b067d18 100644 --- a/canyon_crud/src/query_elements/operators.rs +++ b/canyon_crud/src/query_elements/operators.rs @@ -1,4 +1,4 @@ -use canyon_connection::canyon_database_connector::DatabaseType; +use canyon_connection::database_type::DatabaseType; pub trait Operator { fn as_str(&self, placeholder_counter: usize, datasource_type: &DatabaseType) -> String; diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index 1294dbb8..f0088dd5 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -1,7 +1,7 @@ use std::fmt::Debug; use canyon_connection::{ - canyon_database_connector::DatabaseType, get_database_config, DATASOURCES, + database_type::DatabaseType, get_database_config, DATASOURCES, }; use crate::{ diff --git a/canyon_crud/src/rows.rs b/canyon_crud/src/rows.rs index 517592a6..70a29222 100644 --- a/canyon_crud/src/rows.rs +++ b/canyon_crud/src/rows.rs @@ -1,4 +1,3 @@ -use crate::crud::Transaction; use crate::mapper::RowMapper; use std::marker::PhantomData; @@ -45,10 +44,7 @@ impl CanyonRows { } /// Consumes `self` and returns the wrapped [`std::vec::Vec`] with the instances of T - pub fn into_results>(self) -> Vec - where - T: Transaction, - { + pub fn into_results>(self) -> Vec { match self { #[cfg(feature = "postgres")] Self::Postgres(v) => v.iter().map(|row| Z::deserialize_postgresql(row)).collect(), From a0e9385df56a2f9d6a2fb665e22b6fa381b7b534 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 16 Jan 2025 09:08:27 +0100 Subject: [PATCH 005/155] feat: Publishing a .vscode folder with the correct configuration for the rust-analyzer LSP in Canyon-SQL --- .gitignore | 1 - .vscode/settings.json | 11 +++++++++++ rust-project.json | 11 ----------- 3 files changed, 11 insertions(+), 12 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 rust-project.json diff --git a/.gitignore b/.gitignore index 056b3728..a751ab5c 100755 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,5 @@ Cargo.lock /tester_canyon_sql/ canyon_tester/ macro_utils.rs -.vscode/ postgres-data/ mysql-data/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..1a510cc9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "rust-analyzer.cargo.features": ["postgres, mssql, mysql, migrations"], + "rust-analyzer.check.workspace": true, + "rust-analyzer.cargo.buildScripts.enable": true, + "rust-analyzer.procMacro.enable": true, + "rust-analyzer.diagnostics.disabled": ["unresolved-proc-macro"], + "rust-analyzer.linkedProjects": [ + "./Cargo.toml" + ] + } + diff --git a/rust-project.json b/rust-project.json deleted file mode 100644 index cbe5783b..00000000 --- a/rust-project.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "crates": [ - { - "root_module": "./src/lib.rs", - "edition": "2021", - "deps": [], - "cfg": ["all()"] - } - ] -} - From fab15de85a8dc8a86a8f43b9ef150f235f123cb8 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 16 Jan 2025 09:16:01 +0100 Subject: [PATCH 006/155] feat: returning Result::Err on errors produced while creating a database connection instead of panic! --- .../src/canyon_database_connector.rs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/canyon_connection/src/canyon_database_connector.rs b/canyon_connection/src/canyon_database_connector.rs index 8cd37dc8..66e7c22e 100644 --- a/canyon_connection/src/canyon_database_connector.rs +++ b/canyon_connection/src/canyon_database_connector.rs @@ -113,7 +113,7 @@ pub mod connection_helpers { let PostgresAuth::Basic { username, password } = postgres_auth; (username.as_str(), password.as_str()) } - _ => panic!("Invalid auth configuration for a PostgreSQL datasource"), + _ => return Err("Invalid auth configuration for a PostgreSQL datasource".into()), }; let (new_client, new_connection) = tokio_postgres::connect( @@ -146,6 +146,8 @@ pub mod connection_helpers { pub async fn create_sqlserver_connection( datasource: &DatasourceConfig, ) -> Result> { + use crate::datasources::{Auth, SqlServerAuth}; + let mut config = Config::new(); config.host(&datasource.properties.host); @@ -153,16 +155,16 @@ pub mod connection_helpers { config.database(&datasource.properties.db_name); config.authentication(match &datasource.auth { - crate::datasources::Auth::SqlServer(sql_server_auth) => match sql_server_auth { - crate::datasources::SqlServerAuth::Basic { username, password } => { + Auth::SqlServer(sql_server_auth) => match sql_server_auth { + SqlServerAuth::Basic { username, password } => { AuthMethod::sql_server(username, password) } - crate::datasources::SqlServerAuth::Integrated => AuthMethod::Integrated, + SqlServerAuth::Integrated => AuthMethod::Integrated, }, - _ => panic!("Invalid auth configuration for a SqlServer datasource"), + _ => return Err("Invalid auth configuration for a SqlServer datasource".into()), }); - config.trust_cert(); + config.trust_cert(); // TODO: this should be specificaly set via user input let tcp = TcpStream::connect(config.get_addr()) .await @@ -182,12 +184,14 @@ pub mod connection_helpers { pub async fn create_mysql_connection( datasource: &DatasourceConfig, ) -> Result> { + use crate::datasources::{Auth, MySQLAuth}; + let (user, password) = match &datasource.auth { - crate::datasources::Auth::MySQL(crate::datasources::MySQLAuth::Basic { + Auth::MySQL(MySQLAuth::Basic { username, password, }) => (username, password), - _ => panic!("Invalid auth configuration for a MySQL datasource"), + _ => return Err("Invalid auth configuration for a MySQL datasource".into()), }; let url = format!( @@ -207,7 +211,7 @@ pub mod connection_helpers { } } -// NOTE: tests defined below should be integration tests, unfortunately, since they require a new connection to be made +// TODO: && NOTE: tests defined below should be integration tests, unfortunately, since they require a new connection to be made // Or just to split them further, and just unit test the url string generation from the actual connection instantion // #[cfg(test)] // mod connection_tests { From b7cbe75b17eafe665523cf13c662fadbf2926446 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 16 Jan 2025 11:09:49 +0100 Subject: [PATCH 007/155] chore: more refactoring on the database connectors --- .../src/canyon_database_connector.rs | 201 +++++++++++++----- tests/crud/init_mssql.rs | 2 +- 2 files changed, 143 insertions(+), 60 deletions(-) diff --git a/canyon_connection/src/canyon_database_connector.rs b/canyon_connection/src/canyon_database_connector.rs index 66e7c22e..f71c99af 100644 --- a/canyon_connection/src/canyon_database_connector.rs +++ b/canyon_connection/src/canyon_database_connector.rs @@ -3,7 +3,7 @@ use async_std::net::TcpStream; #[cfg(feature = "mysql")] use mysql_async::Pool; #[cfg(feature = "mssql")] -use tiberius::{AuthMethod, Config}; +use tiberius::Config; #[cfg(feature = "postgres")] use tokio_postgres::{Client, NoTls}; @@ -70,7 +70,7 @@ impl DatabaseConnection { pub fn postgres_connection(&self) -> &PostgreSqlConnection { match self { DatabaseConnection::Postgres(conn) => conn, - #[cfg(all(feature = "postgres", feature = "mssql", feature = "mysql"))] + #[cfg(any(feature = "mssql", feature = "mysql"))] _ => panic!(), } } @@ -79,7 +79,7 @@ impl DatabaseConnection { pub fn sqlserver_connection(&mut self) -> &mut SqlServerConnection { match self { DatabaseConnection::SqlServer(conn) => conn, - #[cfg(all(feature = "postgres", feature = "mssql", feature = "mysql"))] + #[cfg(any(feature = "postgres", feature = "mysql"))] _ => panic!(), } } @@ -88,7 +88,7 @@ impl DatabaseConnection { pub fn mysql_connection(&self) -> &MysqlConnection { match self { DatabaseConnection::MySQL(conn) => conn, - #[cfg(all(feature = "postgres", feature = "mssql", feature = "mysql"))] + #[cfg(any(feature = "postgres", feature = "mssql"))] _ => panic!(), } } @@ -96,38 +96,34 @@ impl DatabaseConnection { pub mod connection_helpers { use super::*; + use auth::AuthConfig; #[cfg(feature = "postgres")] pub async fn create_postgres_connection( datasource: &DatasourceConfig, ) -> Result> { - use crate::datasources::{Auth, PostgresAuth}; - - let (username, password) = match &datasource.auth { - Auth::Postgres(postgres_auth) - if matches!( - postgres_auth, - PostgresAuth::Basic { .. } - ) => - { - let PostgresAuth::Basic { username, password } = postgres_auth; - (username.as_str(), password.as_str()) - } - _ => return Err("Invalid auth configuration for a PostgreSQL datasource".into()), - }; - - let (new_client, new_connection) = tokio_postgres::connect( - &format!( - "postgres://{user}:{pswd}@{host}:{port}/{db}", - user = username, - pswd = password, - host = datasource.properties.host, - port = datasource.properties.port.unwrap_or_default(), - db = datasource.properties.db_name - ), - NoTls, - ) - .await?; + let (new_client, new_connection) = + match auth::extract_auth(&datasource.auth, DatabaseType::PostgreSql)? { + AuthConfig::Postgres(username, password) => { + tokio_postgres::connect( + &format!( + "postgres://{user}:{pswd}@{host}:{port}/{db}", + user = username, + pswd = password, + host = datasource.properties.host, + port = datasource.properties.port.unwrap_or_default(), + db = datasource.properties.db_name + ), + NoTls, + ) + .await? + } + _ => { + return Err( + format!("Failed to set the auth for datasource: {:?}", datasource).into(), + ) + } + }; tokio::spawn(async move { if let Err(e) = new_connection.await { @@ -146,34 +142,31 @@ pub mod connection_helpers { pub async fn create_sqlserver_connection( datasource: &DatasourceConfig, ) -> Result> { - use crate::datasources::{Auth, SqlServerAuth}; - - let mut config = Config::new(); - - config.host(&datasource.properties.host); - config.port(datasource.properties.port.unwrap_or_default()); - config.database(&datasource.properties.db_name); - - config.authentication(match &datasource.auth { - Auth::SqlServer(sql_server_auth) => match sql_server_auth { - SqlServerAuth::Basic { username, password } => { - AuthMethod::sql_server(username, password) - } - SqlServerAuth::Integrated => AuthMethod::Integrated, - }, - _ => return Err("Invalid auth configuration for a SqlServer datasource".into()), - }); + let mut tiberius_config = Config::new(); + + tiberius_config.host(&datasource.properties.host); + tiberius_config.port(datasource.properties.port.unwrap_or_default()); + tiberius_config.database(&datasource.properties.db_name); + + match auth::extract_auth(&datasource.auth, DatabaseType::SqlServer)? { + AuthConfig::SqlServer(auth_method) => tiberius_config.authentication(auth_method), + _ => { + return Err( + format!("Failed to set the auth for datasource: {:?}", datasource).into(), + ) + } + }; - config.trust_cert(); // TODO: this should be specificaly set via user input + tiberius_config.trust_cert(); // TODO: this should be specificaly set via user input - let tcp = TcpStream::connect(config.get_addr()) + let tcp = TcpStream::connect(tiberius_config.get_addr()) .await .expect("Error instantiating the SqlServer TCP Stream"); tcp.set_nodelay(true) .expect("Error in the SqlServer `nodelay` config"); - let client = tiberius::Client::connect(config, tcp).await?; + let client = tiberius::Client::connect(tiberius_config, tcp).await?; Ok(DatabaseConnection::SqlServer(SqlServerConnection { client: Box::leak(Box::new(client)), @@ -184,14 +177,14 @@ pub mod connection_helpers { pub async fn create_mysql_connection( datasource: &DatasourceConfig, ) -> Result> { - use crate::datasources::{Auth, MySQLAuth}; - - let (user, password) = match &datasource.auth { - Auth::MySQL(MySQLAuth::Basic { - username, - password, - }) => (username, password), - _ => return Err("Invalid auth configuration for a MySQL datasource".into()), + + let (user, password) = match auth::extract_auth(&datasource.auth, DatabaseType::MySQL)? { + AuthConfig::MySQL(username, password) => (username, password), + _ => { + return Err( + format!("Failed to set the auth for datasource: {:?}", datasource).into(), + ) + } }; let url = format!( @@ -211,6 +204,96 @@ pub mod connection_helpers { } } +pub mod auth { + use std::marker::PhantomData; + + use crate::{database_type::DatabaseType, datasources::Auth}; + + #[cfg(feature = "mysql")] + use crate::datasources::MySQLAuth; + #[cfg(feature = "postgres")] + use crate::datasources::PostgresAuth; + #[cfg(feature = "mssql")] + use crate::datasources::SqlServerAuth; + + /// Custom type to act as a brigde between the parsed input auth data on the Canyon config file with serde + /// to the internal type(s) of the database connector vendors + pub enum AuthConfig<'a> { + #[cfg(feature = "postgres")] + Postgres(&'a str, &'a str), + + #[cfg(feature = "mssql")] + SqlServer(tiberius::AuthMethod), + + #[cfg(feature = "mysql")] + MySQL(&'a str, &'a str), + + Phanton(PhantomData<&'a ()>), + } + + pub fn extract_auth<'a>( + auth: &'a Auth, + db_type: DatabaseType, + ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { + match db_type { + #[cfg(feature = "postgres")] + DatabaseType::PostgreSql => extract_postgres_auth(auth), + #[cfg(feature = "mssql")] + DatabaseType::SqlServer => extract_mssql_auth(auth), + #[cfg(feature = "mysql")] + DatabaseType::MySQL => extract_mysql_auth(auth), + } + } + + #[cfg(feature = "postgres")] + fn extract_postgres_auth<'a>( + auth: &'a Auth, + ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { + match auth { + Auth::Postgres(pg_auth) => match pg_auth { + PostgresAuth::Basic { username, password } => { + Ok(AuthConfig::Postgres(username, password)) + } + }, + #[cfg(any(feature = "mssql", feature = "mysql"))] + _ => Err("Invalid auth configuration for a SqlServer datasource.".into()), + } + } + + #[cfg(feature = "mssql")] + fn extract_mssql_auth<'a>( + auth: &'a Auth, + ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { + match auth { + Auth::SqlServer(sql_server_auth) => match sql_server_auth { + SqlServerAuth::Basic { username, password } => Ok(AuthConfig::SqlServer( + tiberius::AuthMethod::sql_server(username, password), + )), + SqlServerAuth::Integrated => { + Ok(AuthConfig::SqlServer(tiberius::AuthMethod::Integrated)) + } + }, + #[cfg(any(feature = "postgres", feature = "mysql"))] + _ => Err("Invalid auth configuration for a SqlServer datasource.".into()), + } + } + + #[cfg(feature = "mysql")] + fn extract_mysql_auth<'a>( + auth: &'a Auth, + ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { + match auth { + Auth::MySQL(mysql_auth) => match mysql_auth { + MySQLAuth::Basic { username, password } => { + Ok(AuthConfig::MySQL(username, password)) + } + }, + #[cfg(any(feature = "mssql", feature = "mysql"))] + _ => Err("Invalid auth configuration for a SqlServer datasource.".into()), + } + } +} + // TODO: && NOTE: tests defined below should be integration tests, unfortunately, since they require a new connection to be made // Or just to split them further, and just unit test the url string generation from the actual connection instantion // #[cfg(test)] diff --git a/tests/crud/init_mssql.rs b/tests/crud/init_mssql.rs index 19b08549..b2d7e8fc 100644 --- a/tests/crud/init_mssql.rs +++ b/tests/crud/init_mssql.rs @@ -22,7 +22,7 @@ use canyon_sql::runtime::tokio_util::compat::TokioAsyncWriteCompatExt; #[canyon_sql::macros::canyon_tokio_test] #[ignore] fn initialize_sql_server_docker_instance() { - static CONN_STR: &str = + static CONN_STR: &str = // TODO: change this for the DS when will be in the public API "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true"; canyon_sql::runtime::futures::executor::block_on(async { From 1694006d2d4ac6062151a6689678ae406032958f Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 16 Jan 2025 11:30:15 +0100 Subject: [PATCH 008/155] feat: Removing unneeded bridge adapters for the db auth --- .../src/canyon_database_connector.rs | 137 +++++------------- 1 file changed, 37 insertions(+), 100 deletions(-) diff --git a/canyon_connection/src/canyon_database_connector.rs b/canyon_connection/src/canyon_database_connector.rs index f71c99af..6f7cb3c3 100644 --- a/canyon_connection/src/canyon_database_connector.rs +++ b/canyon_connection/src/canyon_database_connector.rs @@ -96,37 +96,25 @@ impl DatabaseConnection { pub mod connection_helpers { use super::*; - use auth::AuthConfig; #[cfg(feature = "postgres")] pub async fn create_postgres_connection( datasource: &DatasourceConfig, ) -> Result> { - let (new_client, new_connection) = - match auth::extract_auth(&datasource.auth, DatabaseType::PostgreSql)? { - AuthConfig::Postgres(username, password) => { - tokio_postgres::connect( - &format!( - "postgres://{user}:{pswd}@{host}:{port}/{db}", - user = username, - pswd = password, - host = datasource.properties.host, - port = datasource.properties.port.unwrap_or_default(), - db = datasource.properties.db_name - ), - NoTls, - ) - .await? - } - _ => { - return Err( - format!("Failed to set the auth for datasource: {:?}", datasource).into(), - ) - } - }; + let (user, password) = auth::extract_postgres_auth(&datasource.auth)?; + let (client, connection) = tokio_postgres::connect( + &format!( + "postgres://{user}:{password}@{host}:{port}/{db}", + host = datasource.properties.host, + port = datasource.properties.port.unwrap_or_default(), + db = datasource.properties.db_name + ), + NoTls, + ) + .await?; tokio::spawn(async move { - if let Err(e) = new_connection.await { + if let Err(e) = connection.await { eprintln!( "An error occurred while trying to connect to the PostgreSQL database: {e}" ); @@ -134,7 +122,7 @@ pub mod connection_helpers { }); Ok(DatabaseConnection::Postgres(PostgreSqlConnection { - client: new_client, + client, })) } @@ -148,23 +136,12 @@ pub mod connection_helpers { tiberius_config.port(datasource.properties.port.unwrap_or_default()); tiberius_config.database(&datasource.properties.db_name); - match auth::extract_auth(&datasource.auth, DatabaseType::SqlServer)? { - AuthConfig::SqlServer(auth_method) => tiberius_config.authentication(auth_method), - _ => { - return Err( - format!("Failed to set the auth for datasource: {:?}", datasource).into(), - ) - } - }; - + let auth_config = auth::extract_mssql_auth(&datasource.auth)?; + tiberius_config.authentication(auth_config); tiberius_config.trust_cert(); // TODO: this should be specificaly set via user input - let tcp = TcpStream::connect(tiberius_config.get_addr()) - .await - .expect("Error instantiating the SqlServer TCP Stream"); - - tcp.set_nodelay(true) - .expect("Error in the SqlServer `nodelay` config"); + let tcp = TcpStream::connect(tiberius_config.get_addr()).await?; + tcp.set_nodelay(true)?; let client = tiberius::Client::connect(tiberius_config, tcp).await?; @@ -177,22 +154,13 @@ pub mod connection_helpers { pub async fn create_mysql_connection( datasource: &DatasourceConfig, ) -> Result> { - - let (user, password) = match auth::extract_auth(&datasource.auth, DatabaseType::MySQL)? { - AuthConfig::MySQL(username, password) => (username, password), - _ => { - return Err( - format!("Failed to set the auth for datasource: {:?}", datasource).into(), - ) - } - }; - + let (user, password) = auth::extract_mysql_auth(&datasource.auth)?; let url = format!( - "mysql://{}:{}@{}:{}/{}", - user, - password, + "mysql://{user}:{password}@{}:{}/{}", datasource.properties.host, - datasource.properties.port.unwrap_or_default(), + datasource.properties.port.unwrap_or_default(), // TODO: impl default for the port + // config regarding the different kind + // of db conn datasource.properties.db_name ); @@ -205,9 +173,7 @@ pub mod connection_helpers { } pub mod auth { - use std::marker::PhantomData; - - use crate::{database_type::DatabaseType, datasources::Auth}; + use crate::datasources::Auth; #[cfg(feature = "mysql")] use crate::datasources::MySQLAuth; @@ -216,61 +182,32 @@ pub mod auth { #[cfg(feature = "mssql")] use crate::datasources::SqlServerAuth; - /// Custom type to act as a brigde between the parsed input auth data on the Canyon config file with serde - /// to the internal type(s) of the database connector vendors - pub enum AuthConfig<'a> { - #[cfg(feature = "postgres")] - Postgres(&'a str, &'a str), - - #[cfg(feature = "mssql")] - SqlServer(tiberius::AuthMethod), - - #[cfg(feature = "mysql")] - MySQL(&'a str, &'a str), - - Phanton(PhantomData<&'a ()>), - } - - pub fn extract_auth<'a>( - auth: &'a Auth, - db_type: DatabaseType, - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { - match db_type { - #[cfg(feature = "postgres")] - DatabaseType::PostgreSql => extract_postgres_auth(auth), - #[cfg(feature = "mssql")] - DatabaseType::SqlServer => extract_mssql_auth(auth), - #[cfg(feature = "mysql")] - DatabaseType::MySQL => extract_mysql_auth(auth), - } - } - #[cfg(feature = "postgres")] - fn extract_postgres_auth<'a>( + pub fn extract_postgres_auth<'a>( auth: &'a Auth, - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { + ) -> Result<(&'a str, &'a str), Box<(dyn std::error::Error + Send + Sync + 'static)>> { match auth { Auth::Postgres(pg_auth) => match pg_auth { PostgresAuth::Basic { username, password } => { - Ok(AuthConfig::Postgres(username, password)) + Ok((username, password)) } }, #[cfg(any(feature = "mssql", feature = "mysql"))] - _ => Err("Invalid auth configuration for a SqlServer datasource.".into()), + _ => Err("Invalid auth configuration for a Postgres datasource.".into()), } } #[cfg(feature = "mssql")] - fn extract_mssql_auth<'a>( + pub fn extract_mssql_auth<'a>( auth: &'a Auth, - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { + ) -> Result> { match auth { Auth::SqlServer(sql_server_auth) => match sql_server_auth { - SqlServerAuth::Basic { username, password } => Ok(AuthConfig::SqlServer( + SqlServerAuth::Basic { username, password } => Ok( tiberius::AuthMethod::sql_server(username, password), - )), + ), SqlServerAuth::Integrated => { - Ok(AuthConfig::SqlServer(tiberius::AuthMethod::Integrated)) + Ok(tiberius::AuthMethod::Integrated) } }, #[cfg(any(feature = "postgres", feature = "mysql"))] @@ -279,17 +216,17 @@ pub mod auth { } #[cfg(feature = "mysql")] - fn extract_mysql_auth<'a>( + pub fn extract_mysql_auth<'a>( auth: &'a Auth, - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { + ) -> Result<(&'a str, &'a str), Box<(dyn std::error::Error + Send + Sync + 'static)>> { match auth { Auth::MySQL(mysql_auth) => match mysql_auth { MySQLAuth::Basic { username, password } => { - Ok(AuthConfig::MySQL(username, password)) + Ok((username, password)) } }, - #[cfg(any(feature = "mssql", feature = "mysql"))] - _ => Err("Invalid auth configuration for a SqlServer datasource.".into()), + #[cfg(any(feature = "postgres", feature = "mssql"))] + _ => Err("Invalid auth configuration for a MySQL datasource.".into()), } } } From 341853c50540736aad43e798066b18f9ed50c32b Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 16 Jan 2025 14:15:09 +0100 Subject: [PATCH 009/155] feat: internal impl details modules of database connection made private --- canyon_connection/src/canyon_database_connector.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/canyon_connection/src/canyon_database_connector.rs b/canyon_connection/src/canyon_database_connector.rs index 6f7cb3c3..c34d152e 100644 --- a/canyon_connection/src/canyon_database_connector.rs +++ b/canyon_connection/src/canyon_database_connector.rs @@ -94,7 +94,7 @@ impl DatabaseConnection { } } -pub mod connection_helpers { +mod connection_helpers { use super::*; #[cfg(feature = "postgres")] @@ -172,7 +172,7 @@ pub mod connection_helpers { } } -pub mod auth { +mod auth { use crate::datasources::Auth; #[cfg(feature = "mysql")] From bc3a64c2073d8e76d8ea5cd93c32ae0612bc8f11 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 16 Jan 2025 14:27:26 +0100 Subject: [PATCH 010/155] feat: url connection string against the target server wrapped in a fn --- .../src/canyon_database_connector.rs | 57 +++++++++---------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/canyon_connection/src/canyon_database_connector.rs b/canyon_connection/src/canyon_database_connector.rs index c34d152e..530f6aef 100644 --- a/canyon_connection/src/canyon_database_connector.rs +++ b/canyon_connection/src/canyon_database_connector.rs @@ -102,16 +102,9 @@ mod connection_helpers { datasource: &DatasourceConfig, ) -> Result> { let (user, password) = auth::extract_postgres_auth(&datasource.auth)?; - let (client, connection) = tokio_postgres::connect( - &format!( - "postgres://{user}:{password}@{host}:{port}/{db}", - host = datasource.properties.host, - port = datasource.properties.port.unwrap_or_default(), - db = datasource.properties.db_name - ), - NoTls, - ) - .await?; + let url = connection_string(user, password, datasource); + + let (client, connection) = tokio_postgres::connect(&url, NoTls).await?; tokio::spawn(async move { if let Err(e) = connection.await { @@ -155,21 +148,31 @@ mod connection_helpers { datasource: &DatasourceConfig, ) -> Result> { let (user, password) = auth::extract_mysql_auth(&datasource.auth)?; - let url = format!( - "mysql://{user}:{password}@{}:{}/{}", - datasource.properties.host, - datasource.properties.port.unwrap_or_default(), // TODO: impl default for the port - // config regarding the different kind - // of db conn - datasource.properties.db_name - ); - + let url = connection_string(user, password, datasource); let mysql_connection = Pool::from_url(url)?; Ok(DatabaseConnection::MySQL(MysqlConnection { client: mysql_connection, })) } + + #[cfg(any(feature = "postgres", feature = "mysql"))] + fn connection_string(user: &str, pswd: &str, datasource: &DatasourceConfig) -> String { + let server = match datasource.get_db_type() { + #[cfg(feature = "postgres")] + DatabaseType::PostgreSql => "postgres", + + #[cfg(feature = "mysql")] + DatabaseType::MySQL => "mysql", + DatabaseType::SqlServer => todo!("Connection string for MSSQL should never be reached"), + }; + format!( + "{server}://{user}:{pswd}@{host}:{port}/{db}", + host = datasource.properties.host, + port = datasource.properties.port.unwrap_or_default(), + db = datasource.properties.db_name + ) + } } mod auth { @@ -188,9 +191,7 @@ mod auth { ) -> Result<(&'a str, &'a str), Box<(dyn std::error::Error + Send + Sync + 'static)>> { match auth { Auth::Postgres(pg_auth) => match pg_auth { - PostgresAuth::Basic { username, password } => { - Ok((username, password)) - } + PostgresAuth::Basic { username, password } => Ok((username, password)), }, #[cfg(any(feature = "mssql", feature = "mysql"))] _ => Err("Invalid auth configuration for a Postgres datasource.".into()), @@ -203,12 +204,10 @@ mod auth { ) -> Result> { match auth { Auth::SqlServer(sql_server_auth) => match sql_server_auth { - SqlServerAuth::Basic { username, password } => Ok( - tiberius::AuthMethod::sql_server(username, password), - ), - SqlServerAuth::Integrated => { - Ok(tiberius::AuthMethod::Integrated) + SqlServerAuth::Basic { username, password } => { + Ok(tiberius::AuthMethod::sql_server(username, password)) } + SqlServerAuth::Integrated => Ok(tiberius::AuthMethod::Integrated), }, #[cfg(any(feature = "postgres", feature = "mysql"))] _ => Err("Invalid auth configuration for a SqlServer datasource.".into()), @@ -221,9 +220,7 @@ mod auth { ) -> Result<(&'a str, &'a str), Box<(dyn std::error::Error + Send + Sync + 'static)>> { match auth { Auth::MySQL(mysql_auth) => match mysql_auth { - MySQLAuth::Basic { username, password } => { - Ok((username, password)) - } + MySQLAuth::Basic { username, password } => Ok((username, password)), }, #[cfg(any(feature = "postgres", feature = "mssql"))] _ => Err("Invalid auth configuration for a MySQL datasource.".into()), From 5d29b9f06f940c74763b9b003f579cb660ba60c8 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 16 Jan 2025 16:18:01 +0100 Subject: [PATCH 011/155] feat: renamed the databse conn file --- ..._database_connector.rs => db_connector.rs} | 2 +- canyon_connection/src/lib.rs | 6 +++--- canyon_connection/src/transaction.rs | 19 +++++++++++++++++++ canyon_crud/src/crud.rs | 8 ++++---- src/lib.rs | 6 +++--- 5 files changed, 30 insertions(+), 11 deletions(-) rename canyon_connection/src/{canyon_database_connector.rs => db_connector.rs} (98%) create mode 100644 canyon_connection/src/transaction.rs diff --git a/canyon_connection/src/canyon_database_connector.rs b/canyon_connection/src/db_connector.rs similarity index 98% rename from canyon_connection/src/canyon_database_connector.rs rename to canyon_connection/src/db_connector.rs index 530f6aef..534fbe12 100644 --- a/canyon_connection/src/canyon_database_connector.rs +++ b/canyon_connection/src/db_connector.rs @@ -234,7 +234,7 @@ mod auth { // mod connection_tests { // use tokio; // use super::connection_helpers::*; -// use crate::{canyon_database_connector::DatabaseConnection, datasources::{Auth, DatasourceConfig, DatasourceProperties, PostgresAuth}}; +// use crate::{db_connector::DatabaseConnection, datasources::{Auth, DatasourceConfig, DatasourceProperties, PostgresAuth}}; // #[tokio::test] // #[cfg(feature = "postgres")] diff --git a/canyon_connection/src/lib.rs b/canyon_connection/src/lib.rs index cc9997aa..da05c114 100644 --- a/canyon_connection/src/lib.rs +++ b/canyon_connection/src/lib.rs @@ -14,16 +14,16 @@ pub extern crate lazy_static; pub extern crate tokio; pub extern crate tokio_util; -pub mod canyon_database_connector; +pub mod db_connector; +pub mod transaction; pub mod database_type; - pub mod datasources; use std::fs; use std::path::PathBuf; use crate::datasources::{CanyonSqlConfig, DatasourceConfig}; -use canyon_database_connector::DatabaseConnection; +use db_connector::DatabaseConnection; use indexmap::IndexMap; use lazy_static::lazy_static; use tokio::sync::{Mutex, MutexGuard}; diff --git a/canyon_connection/src/transaction.rs b/canyon_connection/src/transaction.rs new file mode 100644 index 00000000..2ef7f409 --- /dev/null +++ b/canyon_connection/src/transaction.rs @@ -0,0 +1,19 @@ +/* pub trait DatabaseTransaction { + /// The type of rows returned by the database. + type Rows; + + /// The type of query parameters. + type QueryParam<'a>: QueryParameter<'a>; + + /// Perform a query against the database. + async fn query<'a, S, Z, T>( + stmt: S, + params: Z, + datasource_name: &'a str, + ) -> Result, Box> + where + S: AsRef + Display + Sync + Send + 'a, + Z: AsRef<[&'a Self::QueryParam<'a>]> + Sync + Send + 'a, + T: Sized; +} +*/ diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index ee53a43d..dbfc70ca 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; use std::fmt::Display; -use canyon_connection::canyon_database_connector::DatabaseConnection; +use canyon_connection::db_connector::DatabaseConnection; use canyon_connection::{get_database_connection, CACHED_DATABASE_CONN}; use crate::bounds::QueryParameter; @@ -156,7 +156,7 @@ where #[cfg(feature = "postgres")] mod postgres_query_launcher { - use canyon_connection::canyon_database_connector::DatabaseConnection; + use canyon_connection::db_connector::DatabaseConnection; use crate::bounds::QueryParameter; use crate::rows::CanyonRows; @@ -186,7 +186,7 @@ mod sqlserver_query_launcher { use crate::rows::CanyonRows; use crate::{ bounds::QueryParameter, - canyon_connection::{canyon_database_connector::DatabaseConnection, tiberius::Query}, + canyon_connection::{db_connector::DatabaseConnection, tiberius::Query}, }; pub async fn launch<'a, T, Z>( @@ -238,7 +238,7 @@ mod mysql_query_launcher { use mysql_async::QueryWithParams; use mysql_async::Value; - use canyon_connection::canyon_database_connector::DatabaseConnection; + use canyon_connection::db_connector::DatabaseConnection; use crate::bounds::QueryParameter; use crate::rows::CanyonRows; diff --git a/src/lib.rs b/src/lib.rs index c74efbc5..9eb9f926 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,13 +29,13 @@ pub mod macros { /// exposing them through the public API pub mod connection { #[cfg(feature = "postgres")] - pub use canyon_connection::canyon_database_connector::DatabaseConnection::Postgres; + pub use canyon_connection::db_connector::DatabaseConnection::Postgres; #[cfg(feature = "mssql")] - pub use canyon_connection::canyon_database_connector::DatabaseConnection::SqlServer; + pub use canyon_connection::db_connector::DatabaseConnection::SqlServer; #[cfg(feature = "mysql")] - pub use canyon_connection::canyon_database_connector::DatabaseConnection::MySQL; + pub use canyon_connection::db_connector::DatabaseConnection::MySQL; } /// Crud module serves to reexport the public elements of the `canyon_crud` crate, From a7ad61346879034fdf45b3833e5d9432f5ca0f58 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 16 Jan 2025 23:04:49 +0100 Subject: [PATCH 012/155] feat: introducing 'canyon_core' --- Cargo.toml | 5 ++++- canyon_connection/Cargo.toml | 5 +++-- canyon_core/Cargo.toml | 12 ++++++++++++ canyon_core/src/lib.rs | 0 canyon_core/src/query.rs | 0 canyon_crud/Cargo.toml | 6 +++--- 6 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 canyon_core/Cargo.toml create mode 100644 canyon_core/src/lib.rs create mode 100644 canyon_core/src/query.rs diff --git a/Cargo.toml b/Cargo.toml index d9bfd724..8ce99239 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,16 +11,18 @@ description.workspace = true [workspace] members = [ + "canyon_core", "canyon_connection", "canyon_crud", "canyon_entities", "canyon_migrations", "canyon_macros", - "tests" + "tests", ] [dependencies] # Project crates +canyon_core = { workspace = true } canyon_connection = { workspace = true } canyon_crud = { workspace = true } canyon_entities = { workspace = true } @@ -35,6 +37,7 @@ mysql_common = { workspace = true, optional = true } [workspace.dependencies] +canyon_core = { version = "0.5.1", path = "canyon_core" } canyon_crud = { version = "0.5.1", path = "canyon_crud" } canyon_connection = { version = "0.5.1", path = "canyon_connection" } canyon_entities = { version = "0.5.1", path = "canyon_entities" } diff --git a/canyon_connection/Cargo.toml b/canyon_connection/Cargo.toml index fac88ef5..daa88d1a 100644 --- a/canyon_connection/Cargo.toml +++ b/canyon_connection/Cargo.toml @@ -10,15 +10,17 @@ license.workspace = true description.workspace = true [dependencies] +canyon_core = { workspace = true } + tokio = { workspace = true } tokio-util = { workspace = true } tokio-postgres = { workspace = true, optional = true } + tiberius = { workspace = true, optional = true } mysql_async = { workspace = true, optional = true } mysql_common = { workspace = true, optional = true } - futures = { workspace = true } indexmap = { workspace = true } lazy_static = { workspace = true } @@ -27,7 +29,6 @@ serde = { workspace = true } async-std = { workspace = true, optional = true } walkdir = { workspace = true } - [features] postgres = ["tokio-postgres"] mssql = ["tiberius", "async-std"] diff --git a/canyon_core/Cargo.toml b/canyon_core/Cargo.toml new file mode 100644 index 00000000..405e9dbd --- /dev/null +++ b/canyon_core/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "canyon_core" +version.workspace = true +edition.workspace = true +authors.workspace = true +documentation.workspace = true +homepage.workspace = true +readme.workspace = true +license.workspace = true +description.workspace = true + +[dependencies] diff --git a/canyon_core/src/lib.rs b/canyon_core/src/lib.rs new file mode 100644 index 00000000..e69de29b diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs new file mode 100644 index 00000000..e69de29b diff --git a/canyon_crud/Cargo.toml b/canyon_crud/Cargo.toml index dfdd3ddb..96c1d070 100644 --- a/canyon_crud/Cargo.toml +++ b/canyon_crud/Cargo.toml @@ -10,6 +10,9 @@ license.workspace = true description.workspace = true [dependencies] +canyon_core = { workspace = true } +canyon_connection = { workspace = true } + tokio-postgres = { workspace = true, optional = true } tiberius = { workspace = true, optional = true } mysql_async = { workspace = true, optional = true } @@ -17,9 +20,6 @@ mysql_common = { workspace = true, optional = true } chrono = { workspace = true } async-trait = { workspace = true } - -canyon_connection = { workspace = true } - regex = { workspace = true } [features] From 36dc49dc0e3196cc255604748580d412cb98c02f Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 16 Jan 2025 23:14:47 +0100 Subject: [PATCH 013/155] feat(wip): initial design of the DatabaseConnection trait on canyon_core --- canyon_core/src/lib.rs | 1 + canyon_core/src/query.rs | 46 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/canyon_core/src/lib.rs b/canyon_core/src/lib.rs index e69de29b..67350db2 100644 --- a/canyon_core/src/lib.rs +++ b/canyon_core/src/lib.rs @@ -0,0 +1 @@ +pub mod query; diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index e69de29b..c7a44c88 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -0,0 +1,46 @@ +use std::fmt::Display; + +pub trait DatabaseQuery { // provisional name + /// Performs a query against the targeted database by the selected or + /// the defaulted datasource, wrapping the resultant collection of entities + /// in [`super::rows::CanyonRows`] + async fn query<'a, S, Z>( + stmt: S, + params: Z, + database_conn: impl DatabaseConnection, + ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'static)>> + where + S: AsRef + Display + Sync + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, + { + match *database_conn { // TODO: this query launch should be implemented in DatabaseClient on + // canyon_connection, after the actual DatabaseConnection struct in + // canyon_connection is renamed to DatabaseClient, so DatabaseClient + // implements DatabaseConnection from this crate, and we will be + // happy + #[cfg(feature = "postgres")] + DatabaseConnection::Postgres(_) => { + postgres_query_launcher::launch::( + database_conn, + stmt.to_string(), + params.as_ref(), + ) + .await + } + #[cfg(feature = "mssql")] + DatabaseConnection::SqlServer(_) => { + sqlserver_query_launcher::launch::( + database_conn, + &mut stmt.to_string(), + params, + ) + .await + } + #[cfg(feature = "mysql")] + DatabaseConnection::MySQL(_) => { + mysql_query_launcher::launch::(database_conn, stmt.to_string(), params.as_ref()) + .await + } + } + } +} From 16c393acbb61a2316708a2b67d3aa88e635574c3 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 17 Jan 2025 19:05:17 +0100 Subject: [PATCH 014/155] feat(wip): decoupling transaction from the public API --- canyon_connection/Cargo.toml | 2 + canyon_connection/src/db_connector.rs | 229 ++++- canyon_connection/src/lib.rs | 1 - canyon_connection/src/transaction.rs | 19 - canyon_core/Cargo.toml | 14 + canyon_core/src/column.rs | 62 ++ canyon_core/src/lib.rs | 5 + {canyon_crud => canyon_core}/src/mapper.rs | 8 - canyon_core/src/query.rs | 46 +- canyon_core/src/query_parameters.rs | 606 +++++++++++++ canyon_core/src/row.rs | 184 ++++ {canyon_crud => canyon_core}/src/rows.rs | 14 +- canyon_crud/src/bounds.rs | 808 +----------------- canyon_crud/src/crud.rs | 241 +----- canyon_crud/src/lib.rs | 2 - canyon_crud/src/query_elements/query.rs | 7 +- .../src/query_elements/query_builder.rs | 27 +- canyon_migrations/Cargo.toml | 1 + canyon_migrations/src/migrations/handler.rs | 8 +- src/lib.rs | 6 +- 20 files changed, 1146 insertions(+), 1144 deletions(-) delete mode 100644 canyon_connection/src/transaction.rs create mode 100644 canyon_core/src/column.rs rename {canyon_crud => canyon_core}/src/mapper.rs (70%) create mode 100644 canyon_core/src/query_parameters.rs create mode 100644 canyon_core/src/row.rs rename {canyon_crud => canyon_core}/src/rows.rs (91%) diff --git a/canyon_connection/Cargo.toml b/canyon_connection/Cargo.toml index daa88d1a..14cba996 100644 --- a/canyon_connection/Cargo.toml +++ b/canyon_connection/Cargo.toml @@ -27,7 +27,9 @@ lazy_static = { workspace = true } toml = { workspace = true } serde = { workspace = true } async-std = { workspace = true, optional = true } +async-trait = { workspace = true } walkdir = { workspace = true } +regex = { workspace = true } [features] postgres = ["tokio-postgres"] diff --git a/canyon_connection/src/db_connector.rs b/canyon_connection/src/db_connector.rs index 534fbe12..897a773f 100644 --- a/canyon_connection/src/db_connector.rs +++ b/canyon_connection/src/db_connector.rs @@ -1,5 +1,7 @@ #[cfg(feature = "mssql")] use async_std::net::TcpStream; +use canyon_core::query_parameters::QueryParameter; +use canyon_core::rows::CanyonRows; #[cfg(feature = "mysql")] use mysql_async::Pool; #[cfg(feature = "mssql")] @@ -9,6 +11,9 @@ use tokio_postgres::{Client, NoTls}; use crate::database_type::DatabaseType; use crate::datasources::DatasourceConfig; +use canyon_core::query::{DbConnection, Transaction}; + +use async_trait::async_trait; /// A connection with a `PostgreSQL` database #[cfg(feature = "postgres")] @@ -46,6 +51,45 @@ pub enum DatabaseConnection { unsafe impl Send for DatabaseConnection {} unsafe impl Sync for DatabaseConnection {} +#[async_trait] +impl Transaction for DatabaseConnection { + async fn query<'a, S, Z>( + stmt: S, + params: Z, + database_connection: impl DbConnection + ) -> Result> + where + S: AsRef + std::fmt::Display + Sync + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a + { + match database_connection { + #[cfg(feature = "postgres")] + DatabaseConnection::Postgres(_) => { + postgres_query_launcher::launch( + self, + stmt.to_string(), + params.as_ref(), + ) + .await + } + #[cfg(feature = "mssql")] + DatabaseConnection::SqlServer(_) => { + sqlserver_query_launcher::launch::( + self, + &mut stmt.to_string(), + params, + ) + .await + } + #[cfg(feature = "mysql")] + DatabaseConnection::MySQL(_) => { + mysql_query_launcher::launch(self, stmt.to_string(), params.as_ref()) + .await + } + } + } +} + impl DatabaseConnection { pub async fn new( datasource: &DatasourceConfig, @@ -156,14 +200,14 @@ mod connection_helpers { })) } - #[cfg(any(feature = "postgres", feature = "mysql"))] + // #[cfg(any(feature = "postgres", feature = "mysql"))] fn connection_string(user: &str, pswd: &str, datasource: &DatasourceConfig) -> String { let server = match datasource.get_db_type() { #[cfg(feature = "postgres")] DatabaseType::PostgreSql => "postgres", - #[cfg(feature = "mysql")] DatabaseType::MySQL => "mysql", + #[cfg(feature = "mssql")] DatabaseType::SqlServer => todo!("Connection string for MSSQL should never be reached"), }; format!( @@ -368,3 +412,184 @@ mod auth { // // } // } // } + +#[cfg(feature = "postgres")] +mod postgres_query_launcher { + use canyon_core::{query_parameters::QueryParameter, rows::CanyonRows}; + + use super::DatabaseConnection; + + + pub async fn launch<'a>( + db_conn: &DatabaseConnection, + stmt: String, + params: &'a [&'_ dyn QueryParameter<'_>], + ) -> Result> { + let mut m_params = Vec::new(); + for param in params { + m_params.push((*param).as_postgres_param()); + } + + let r = db_conn + .postgres_connection() + .client + .query(&stmt, m_params.as_slice()) + .await?; + + Ok(CanyonRows::Postgres(r)) + } +} + +#[cfg(feature = "mssql")] +mod sqlserver_query_launcher { + use canyon_core::{query_parameters::QueryParameter, rows::CanyonRows}; + use tiberius::Query; + + use super::DatabaseConnection; + + + pub async fn launch<'a, Z>( + db_conn: & DatabaseConnection, + stmt: &mut String, + params: Z, + ) -> Result> + where + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, + { + // Re-generate de insert statement to adequate it to the SQL SERVER syntax to retrieve the PK value(s) after insert + // TODO: redo this branch into the generated queries, before the MACROS + if stmt.contains("RETURNING") { + let c = stmt.clone(); + let temp = c.split_once("RETURNING").unwrap(); + let temp2 = temp.0.split_once("VALUES").unwrap(); + + *stmt = format!( + "{} OUTPUT inserted.{} VALUES {}", + temp2.0.trim(), + temp.1.trim(), + temp2.1.trim() + ); + } + + let mut mssql_query = Query::new(stmt.to_owned().replace('$', "@P")); + params + .as_ref() + .iter() + .for_each(|param| mssql_query.bind(*param)); + + #[allow(mutable_transmutes)] + let sqlservconn = unsafe { std::mem::transmute::<&DatabaseConnection, &mut DatabaseConnection>(db_conn) }; + let _results = mssql_query + .query(sqlservconn.sqlserver_connection().client) + .await? + .into_results() + .await?; + + Ok(CanyonRows::Tiberius( + _results.into_iter().flatten().collect(), + )) + } +} + +#[cfg(feature = "mysql")] +mod mysql_query_launcher { + #[cfg(feature = "mysql")] + pub const DETECT_PARAMS_IN_QUERY: &str = r"\$([\d])+"; + #[cfg(feature = "mysql")] + pub const DETECT_QUOTE_IN_QUERY: &str = r#"\"|\\"#; + + use std::sync::Arc; + + use mysql_async::prelude::Query; + use mysql_async::QueryWithParams; + use mysql_async::Value; + + use super::DatabaseConnection; + + use canyon_core::query_parameters::QueryParameter; + use canyon_core::rows::CanyonRows; + use mysql_async::Row; + use mysql_common::constants::ColumnType; + use mysql_common::row; + use regex::Regex; + + pub async fn launch<'a>( + db_conn: &DatabaseConnection, + stmt: String, + params: &'a [&'_ dyn QueryParameter<'_>], + ) -> Result> { + let mysql_connection = db_conn.mysql_connection().client.get_conn().await?; + + let stmt_with_escape_characters = regex::escape(&stmt); + let query_string = + Regex::new(DETECT_PARAMS_IN_QUERY)?.replace_all(&stmt_with_escape_characters, "?"); + + let mut query_string = Regex::new(DETECT_QUOTE_IN_QUERY)? + .replace_all(&query_string, "") + .to_string(); + + let mut is_insert = false; + if let Some(index_start_clausule_returning) = query_string.find(" RETURNING") { + query_string.truncate(index_start_clausule_returning); + is_insert = true; + } + + let params_query: Vec = + reorder_params(&stmt, params, |f| (*f).as_mysql_param().to_value()); + + let query_with_params = QueryWithParams { + query: query_string, + params: params_query, + }; + + let mut query_result = query_with_params + .run(mysql_connection) + .await + .expect("Error executing query in mysql"); + + let result_rows = if is_insert { + let last_insert = query_result + .last_insert_id() + .map(Value::UInt) + .expect("Error getting pk id in insert"); + + vec![row::new_row( + vec![last_insert], + Arc::new([mysql_async::Column::new(ColumnType::MYSQL_TYPE_UNKNOWN)]), + )] + } else { + query_result + .collect::() + .await + .expect("Error resolved trait FromRow in mysql") + }; +let a = CanyonRows::MySQL(result_rows); + Ok(a) + } + + #[cfg(feature = "mysql")] + fn reorder_params( + stmt: &str, + params: &[&'_ dyn QueryParameter<'_>], + fn_parser: impl Fn(&&dyn QueryParameter<'_>) -> T, + ) -> Vec { + let mut ordered_params = vec![]; + let rg = regex::Regex::new(DETECT_PARAMS_IN_QUERY) + .expect("Error create regex with detect params pattern expression"); + + for positional_param in rg.find_iter(stmt) { + let pp: &str = positional_param.as_str(); + let pp_index = pp[1..] // param $1 -> get 1 + .parse::() + .expect("Error parse mapped parameter to usized.") + - 1; + + let element = params + .get(pp_index) + .expect("Error obtaining the element of the mapping against parameters."); + ordered_params.push(fn_parser(element)); + } + + ordered_params + } +} diff --git a/canyon_connection/src/lib.rs b/canyon_connection/src/lib.rs index da05c114..5267dfce 100644 --- a/canyon_connection/src/lib.rs +++ b/canyon_connection/src/lib.rs @@ -15,7 +15,6 @@ pub extern crate tokio; pub extern crate tokio_util; pub mod db_connector; -pub mod transaction; pub mod database_type; pub mod datasources; diff --git a/canyon_connection/src/transaction.rs b/canyon_connection/src/transaction.rs deleted file mode 100644 index 2ef7f409..00000000 --- a/canyon_connection/src/transaction.rs +++ /dev/null @@ -1,19 +0,0 @@ -/* pub trait DatabaseTransaction { - /// The type of rows returned by the database. - type Rows; - - /// The type of query parameters. - type QueryParam<'a>: QueryParameter<'a>; - - /// Perform a query against the database. - async fn query<'a, S, Z, T>( - stmt: S, - params: Z, - datasource_name: &'a str, - ) -> Result, Box> - where - S: AsRef + Display + Sync + Send + 'a, - Z: AsRef<[&'a Self::QueryParam<'a>]> + Sync + Send + 'a, - T: Sized; -} -*/ diff --git a/canyon_core/Cargo.toml b/canyon_core/Cargo.toml index 405e9dbd..4fb6e6fd 100644 --- a/canyon_core/Cargo.toml +++ b/canyon_core/Cargo.toml @@ -10,3 +10,17 @@ license.workspace = true description.workspace = true [dependencies] +tokio-postgres = { workspace = true, optional = true } +tiberius = { workspace = true, optional = true } +mysql_async = { workspace = true, optional = true } +mysql_common = { workspace = true, optional = true } + +chrono = { workspace = true } +async-std = { workspace = true, optional = true } +async-trait = { workspace = true } +regex = { workspace = true } + +[features] +postgres = ["tokio-postgres"] +mssql = ["tiberius", "async-std"] +mysql = ["mysql_async","mysql_common"] \ No newline at end of file diff --git a/canyon_core/src/column.rs b/canyon_core/src/column.rs new file mode 100644 index 00000000..da01128a --- /dev/null +++ b/canyon_core/src/column.rs @@ -0,0 +1,62 @@ +use std::{any::Any, borrow::Cow}; + +#[cfg(feature = "mysql")] +use mysql_async::{self}; +#[cfg(feature = "mssql")] +use tiberius::{self}; +#[cfg(feature = "postgres")] +use tokio_postgres::{self}; + +/// Generic abstraction for hold a Column type that will be one of the Column +/// types present in the dependent crates +// #[derive(Copy, Clone)] +pub struct Column<'a> { + pub(crate) name: Cow<'a, str>, + pub(crate) type_: ColumnType, +} +impl<'a> Column<'a> { + pub fn name(&self) -> &str { + &self.name + } + pub fn column_type(&self) -> &ColumnType { + &self.type_ + } + // pub fn type_(&'a self) -> &'_ dyn Type { + // match (*self).type_ { + // #[cfg(feature = "postgres")] ColumnType::Postgres(v) => v as &'a dyn Type, + // #[cfg(feature = "mssql")] ColumnType::SqlServer(v) => v as &'a dyn Type, + // } + // } +} + +pub trait ColType { + fn as_any(&self) -> &dyn Any; +} +#[cfg(feature = "postgres")] +impl ColType for tokio_postgres::types::Type { + fn as_any(&self) -> &dyn Any { + self + } +} +#[cfg(feature = "mssql")] +impl ColType for tiberius::ColumnType { + fn as_any(&self) -> &dyn Any { + self + } +} +#[cfg(feature = "mysql")] +impl ColType for mysql_async::consts::ColumnType { + fn as_any(&self) -> &dyn Any { + self + } +} + +/// Wrapper over the dependencies Column's types +pub enum ColumnType { + #[cfg(feature = "postgres")] + Postgres(tokio_postgres::types::Type), + #[cfg(feature = "mssql")] + SqlServer(tiberius::ColumnType), + #[cfg(feature = "mysql")] + MySQL(mysql_async::consts::ColumnType), +} diff --git a/canyon_core/src/lib.rs b/canyon_core/src/lib.rs index 67350db2..5bf9e0a3 100644 --- a/canyon_core/src/lib.rs +++ b/canyon_core/src/lib.rs @@ -1 +1,6 @@ pub mod query; +pub mod query_parameters; +pub mod row; +pub mod rows; +pub mod column; +pub mod mapper; \ No newline at end of file diff --git a/canyon_crud/src/mapper.rs b/canyon_core/src/mapper.rs similarity index 70% rename from canyon_crud/src/mapper.rs rename to canyon_core/src/mapper.rs index 9c23ff2e..e7493934 100644 --- a/canyon_crud/src/mapper.rs +++ b/canyon_core/src/mapper.rs @@ -1,11 +1,3 @@ -#[cfg(feature = "mysql")] -use canyon_connection::mysql_async; -#[cfg(feature = "mssql")] -use canyon_connection::tiberius; -#[cfg(feature = "postgres")] -use canyon_connection::tokio_postgres; - - /// Declares functions that takes care to deserialize data incoming /// from some supported database in Canyon-SQL into a user's defined /// type `T` diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index c7a44c88..a285c74b 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -1,46 +1,24 @@ use std::fmt::Display; -pub trait DatabaseQuery { // provisional name +use async_trait::async_trait; + +use crate::{query_parameters::QueryParameter, rows::CanyonRows}; + +pub trait DbConnection{} + +#[async_trait] +pub trait Transaction { // provisional name /// Performs a query against the targeted database by the selected or /// the defaulted datasource, wrapping the resultant collection of entities /// in [`super::rows::CanyonRows`] async fn query<'a, S, Z>( stmt: S, params: Z, - database_conn: impl DatabaseConnection, - ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'static)>> + database_conn: impl DatabaseConnection + Send + ) -> Result> where S: AsRef + Display + Sync + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, - { - match *database_conn { // TODO: this query launch should be implemented in DatabaseClient on - // canyon_connection, after the actual DatabaseConnection struct in - // canyon_connection is renamed to DatabaseClient, so DatabaseClient - // implements DatabaseConnection from this crate, and we will be - // happy - #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(_) => { - postgres_query_launcher::launch::( - database_conn, - stmt.to_string(), - params.as_ref(), - ) - .await - } - #[cfg(feature = "mssql")] - DatabaseConnection::SqlServer(_) => { - sqlserver_query_launcher::launch::( - database_conn, - &mut stmt.to_string(), - params, - ) - .await - } - #[cfg(feature = "mysql")] - DatabaseConnection::MySQL(_) => { - mysql_query_launcher::launch::(database_conn, stmt.to_string(), params.as_ref()) - .await - } + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a { + Ok(CanyonRows::Postgres(vec![])) } - } } diff --git a/canyon_core/src/query_parameters.rs b/canyon_core/src/query_parameters.rs new file mode 100644 index 00000000..fedd24c5 --- /dev/null +++ b/canyon_core/src/query_parameters.rs @@ -0,0 +1,606 @@ +#[cfg(feature = "mysql")] +use mysql_async::{self, prelude::ToValue}; +#[cfg(feature = "mssql")] +use tiberius::{self, ColumnData, IntoSql}; +#[cfg(feature = "postgres")] +use tokio_postgres::{self, types::ToSql}; + +// TODO: cfg all +use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Utc}; + +/// Defines a trait for represent type bounds against the allowed +/// data types supported by Canyon to be used as query parameters. +pub trait QueryParameter<'a>: std::fmt::Debug + Sync + Send { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync); + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_>; + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue; +} + +/// The implementation of the [`canyon_connection::tiberius`] [`IntoSql`] for the +/// query parameters. +/// +/// This implementation is necessary because of the generic amplitude +/// of the arguments of the [`Transaction::query`], that should work with +/// a collection of [`QueryParameter<'a>`], in order to allow a workflow +/// that is not dependent of the specific type of the argument that holds +/// the query parameters of the database connectors +#[cfg(feature = "mssql")] +impl<'a> IntoSql<'a> for &'a dyn QueryParameter<'a> { + fn into_sql(self) -> ColumnData<'a> { + self.as_sqlserver_param() + } +} + +//TODO Pending to review and see if it is necessary to apply something similar to the previous implementation. + +impl<'a> QueryParameter<'a> for bool { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::Bit(Some(*self)) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for i16 { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::I16(Some(*self)) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for &i16 { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::I16(Some(**self)) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for Option { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::I16(*self) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for Option<&i16> { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::I16(Some(*self.unwrap())) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for i32 { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::I32(Some(*self)) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for &i32 { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::I32(Some(**self)) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for Option { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::I32(*self) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for Option<&i32> { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::I32(Some(*self.unwrap())) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for f32 { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::F32(Some(*self)) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for &f32 { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::F32(Some(**self)) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for Option { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::F32(*self) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for Option<&f32> { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::F32(Some( + *self.expect("Error on an f32 value on QueryParameter<'_>"), + )) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for f64 { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::F64(Some(*self)) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for &f64 { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::F64(Some(**self)) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for Option { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::F64(*self) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for Option<&f64> { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::F64(Some( + *self.expect("Error on an f64 value on QueryParameter<'_>"), + )) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for i64 { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::I64(Some(*self)) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for &i64 { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::I64(Some(**self)) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for Option { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::I64(*self) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for Option<&i64> { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::I64(Some(*self.unwrap())) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for String { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::String(Some(std::borrow::Cow::Owned(self.to_owned()))) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for &String { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::String(Some(std::borrow::Cow::Borrowed(self))) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for Option { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + match self { + Some(string) => ColumnData::String(Some(std::borrow::Cow::Owned(string.to_owned()))), + None => ColumnData::String(None), + } + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for Option<&String> { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + match self { + Some(string) => ColumnData::String(Some(std::borrow::Cow::Borrowed(string))), + None => ColumnData::String(None), + } + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for &'_ str { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + ColumnData::String(Some(std::borrow::Cow::Borrowed(*self))) + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for Option<&'_ str> { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + match *self { + Some(str) => ColumnData::String(Some(std::borrow::Cow::Borrowed(str))), + None => ColumnData::String(None), + } + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for NaiveDate { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + self.into_sql() + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for Option { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + self.into_sql() + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for NaiveTime { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + self.into_sql() + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for Option { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + self.into_sql() + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for NaiveDateTime { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + self.into_sql() + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +impl<'a> QueryParameter<'a> for Option { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + self.into_sql() + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + self + } +} + +//TODO pending +impl<'a> QueryParameter<'a> for DateTime { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + self.into_sql() + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + todo!() + } +} + +impl<'a> QueryParameter<'a> for Option> { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + self.into_sql() + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + todo!() + } +} + +impl<'a> QueryParameter<'a> for DateTime { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + self.into_sql() + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + todo!() + } +} + +impl<'a> QueryParameter<'a> for Option> { + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + self.into_sql() + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + todo!() + } +} diff --git a/canyon_core/src/row.rs b/canyon_core/src/row.rs new file mode 100644 index 00000000..43916aa1 --- /dev/null +++ b/canyon_core/src/row.rs @@ -0,0 +1,184 @@ +#[cfg(feature = "mysql")] +use mysql_async::{self}; +#[cfg(feature = "mssql")] +use tiberius::{self}; +#[cfg(feature = "postgres")] +use tokio_postgres::{self}; + +use crate::column::{Column, ColumnType}; +use std::{any::Any, borrow::Cow}; + +/// Generic abstraction to represent any of the Row types +/// from the client crates +pub trait Row { + fn as_any(&self) -> &dyn Any; +} + +#[cfg(feature = "postgres")] +impl Row for tokio_postgres::Row { + fn as_any(&self) -> &dyn Any { + self + } +} + +#[cfg(feature = "mssql")] +impl Row for tiberius::Row { + fn as_any(&self) -> &dyn Any { + self + } +} + +#[cfg(feature = "mysql")] +impl Row for mysql_async::Row { + fn as_any(&self) -> &dyn Any { + self + } +} + + +pub trait RowOperations { + #[cfg(feature = "postgres")] + fn get_postgres<'a, Output>(&'a self, col_name: &'a str) -> Output + where + Output: tokio_postgres::types::FromSql<'a>; + #[cfg(feature = "mssql")] + fn get_mssql<'a, Output>(&'a self, col_name: &'a str) -> Output + where + Output: tiberius::FromSql<'a>; + #[cfg(feature = "mysql")] + fn get_mysql<'a, Output>(&'a self, col_name: &'a str) -> Output + where + Output: mysql_async::prelude::FromValue; + + #[cfg(feature = "postgres")] + fn get_postgres_opt<'a, Output>(&'a self, col_name: &'a str) -> Option + where + Output: tokio_postgres::types::FromSql<'a>; + #[cfg(feature = "mssql")] + fn get_mssql_opt<'a, Output>(&'a self, col_name: &'a str) -> Option + where + Output: tiberius::FromSql<'a>; + + #[cfg(feature = "mysql")] + fn get_mysql_opt<'a, Output>(&'a self, col_name: &'a str) -> Option + where + Output: mysql_async::prelude::FromValue; + + fn columns(&self) -> Vec; +} + +impl RowOperations for &dyn Row { + #[cfg(feature = "postgres")] + fn get_postgres<'a, Output>(&'a self, col_name: &'a str) -> Output + where + Output: tokio_postgres::types::FromSql<'a>, + { + if let Some(row) = self.as_any().downcast_ref::() { + return row.get::<&str, Output>(col_name); + }; + panic!() // TODO into result and propagate + } + #[cfg(feature = "mssql")] + fn get_mssql<'a, Output>(&'a self, col_name: &'a str) -> Output + where + Output: tiberius::FromSql<'a>, + { + if let Some(row) = self.as_any().downcast_ref::() { + return row + .get::(col_name) + .expect("Failed to obtain a row in the MSSQL migrations"); + }; + panic!() // TODO into result and propagate + } + + #[cfg(feature = "mysql")] + fn get_mysql<'a, Output>(&'a self, col_name: &'a str) -> Output + where + Output: mysql_async::prelude::FromValue, + { + self.get_mysql_opt(col_name) + .expect("Failed to obtain a column in the MySql") + } + + #[cfg(feature = "postgres")] + fn get_postgres_opt<'a, Output>(&'a self, col_name: &'a str) -> Option + where + Output: tokio_postgres::types::FromSql<'a>, + { + if let Some(row) = self.as_any().downcast_ref::() { + return row.get::<&str, Option>(col_name); + }; + panic!() // TODO into result and propagate + } + + #[cfg(feature = "mssql")] + fn get_mssql_opt<'a, Output>(&'a self, col_name: &'a str) -> Option + where + Output: tiberius::FromSql<'a>, + { + if let Some(row) = self.as_any().downcast_ref::() { + return row.get::(col_name); + }; + panic!() // TODO into result and propagate + } + #[cfg(feature = "mysql")] + fn get_mysql_opt<'a, Output>(&'a self, col_name: &'a str) -> Option + where + Output: mysql_async::prelude::FromValue, + { + if let Some(row) = self.as_any().downcast_ref::() { + return row.get::(col_name); + }; + panic!() // TODO into result and propagate + } + + fn columns(&self) -> Vec { + let mut cols = vec![]; + + #[cfg(feature = "postgres")] + { + if self.as_any().is::() { + self.as_any() + .downcast_ref::() + .expect("Not a tokio postgres Row for column") + .columns() + .iter() + .for_each(|c| { + cols.push(Column { + name: std::borrow::Cow::from(c.name()), + type_: crate::column::ColumnType::Postgres(c.type_().to_owned()), + }) + }) + } + } + #[cfg(feature = "mssql")] + { + if self.as_any().is::() { + self.as_any() + .downcast_ref::() + .expect("Not a Tiberius Row for column") + .columns() + .iter() + .for_each(|c| { + cols.push(Column { + name: Cow::from(c.name()), + type_: ColumnType::SqlServer(c.column_type()), + }) + }) + }; + } + #[cfg(feature = "mysql")] + { + if let Some(mysql_row) = self.as_any().downcast_ref::() { + mysql_row.columns_ref().iter().for_each(|c| { + cols.push(Column { + name: c.name_str(), + type_: ColumnType::MySQL(c.column_type()), + }) + }) + } + } + + cols + } +} diff --git a/canyon_crud/src/rows.rs b/canyon_core/src/rows.rs similarity index 91% rename from canyon_crud/src/rows.rs rename to canyon_core/src/rows.rs index 70a29222..7ba9a00f 100644 --- a/canyon_crud/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -1,5 +1,4 @@ use crate::mapper::RowMapper; -use std::marker::PhantomData; /// Lightweight wrapper over the collection of results of the different crates /// supported by Canyon-SQL. @@ -7,18 +6,16 @@ use std::marker::PhantomData; /// Even tho the wrapping seems meaningless, this allows us to provide internal /// operations that are too difficult or to ugly to implement in the macros that /// will call the query method of Crud. -pub enum CanyonRows { +pub enum CanyonRows { #[cfg(feature = "postgres")] Postgres(Vec), #[cfg(feature = "mssql")] Tiberius(Vec), #[cfg(feature = "mysql")] - MySQL(Vec), - - UnusableTypeMarker(PhantomData), + MySQL(Vec) } -impl CanyonRows { +impl CanyonRows { #[cfg(feature = "postgres")] pub fn get_postgres_rows(&self) -> &Vec { match self { @@ -52,7 +49,6 @@ impl CanyonRows { Self::Tiberius(v) => v.iter().map(|row| Z::deserialize_sqlserver(row)).collect(), #[cfg(feature = "mysql")] Self::MySQL(v) => v.iter().map(|row| Z::deserialize_mysql(row)).collect(), - _ => panic!("This branch will never ever should be reachable"), } } @@ -65,7 +61,7 @@ impl CanyonRows { Self::Tiberius(v) => v.len(), #[cfg(feature = "mysql")] Self::MySQL(v) => v.len(), - _ => panic!("This branch will never ever should be reachable"), + _ => panic!("This branch will never ever should be reachable") } } @@ -78,7 +74,7 @@ impl CanyonRows { Self::Tiberius(v) => v.is_empty(), #[cfg(feature = "mysql")] Self::MySQL(v) => v.is_empty(), - _ => panic!("This branch will never ever should be reachable"), + _ => panic!("This branch will never ever should be reachable") } } } diff --git a/canyon_crud/src/bounds.rs b/canyon_crud/src/bounds.rs index 27ffb97f..758e0ce0 100644 --- a/canyon_crud/src/bounds.rs +++ b/canyon_crud/src/bounds.rs @@ -1,17 +1,6 @@ -use crate::{ - crud::{CrudOperations, Transaction}, - mapper::RowMapper, -}; -#[cfg(feature = "mysql")] -use canyon_connection::mysql_async::{self, prelude::ToValue}; -#[cfg(feature = "mssql")] -use canyon_connection::tiberius::{self, ColumnData, IntoSql}; -#[cfg(feature = "postgres")] -use canyon_connection::tokio_postgres::{self, types::ToSql}; +use canyon_core::{mapper::RowMapper, query::Transaction, query_parameters::QueryParameter}; -use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Utc}; - -use std::{any::Any, borrow::Cow}; +use crate::crud::CrudOperations; /// Created for retrieve the field's name of a field of a struct, giving /// the Canyon's autogenerated enum with the variants that maps this @@ -80,796 +69,3 @@ pub trait ForeignKeyable { /// Retrieves the field related to the column passed in fn get_fk_column(&self, column: &str) -> Option<&dyn QueryParameter<'_>>; } - -/// Generic abstraction to represent any of the Row types -/// from the client crates -pub trait Row { - fn as_any(&self) -> &dyn Any; -} - -#[cfg(feature = "postgres")] -impl Row for tokio_postgres::Row { - fn as_any(&self) -> &dyn Any { - self - } -} - -#[cfg(feature = "mssql")] -impl Row for tiberius::Row { - fn as_any(&self) -> &dyn Any { - self - } -} - -#[cfg(feature = "mysql")] -impl Row for mysql_async::Row { - fn as_any(&self) -> &dyn Any { - self - } -} - -/// Generic abstraction for hold a Column type that will be one of the Column -/// types present in the dependent crates -// #[derive(Copy, Clone)] -pub struct Column<'a> { - name: Cow<'a, str>, - type_: ColumnType, -} -impl<'a> Column<'a> { - pub fn name(&self) -> &str { - &self.name - } - pub fn column_type(&self) -> &ColumnType { - &self.type_ - } - // pub fn type_(&'a self) -> &'_ dyn Type { - // match (*self).type_ { - // #[cfg(feature = "postgres")] ColumnType::Postgres(v) => v as &'a dyn Type, - // #[cfg(feature = "mssql")] ColumnType::SqlServer(v) => v as &'a dyn Type, - // } - // } -} - -pub trait Type { - fn as_any(&self) -> &dyn Any; -} -#[cfg(feature = "postgres")] -impl Type for tokio_postgres::types::Type { - fn as_any(&self) -> &dyn Any { - self - } -} -#[cfg(feature = "mssql")] -impl Type for tiberius::ColumnType { - fn as_any(&self) -> &dyn Any { - self - } -} -#[cfg(feature = "mysql")] -impl Type for mysql_async::consts::ColumnType { - fn as_any(&self) -> &dyn Any { - self - } -} - -/// Wrapper over the dependencies Column's types -pub enum ColumnType { - #[cfg(feature = "postgres")] - Postgres(tokio_postgres::types::Type), - #[cfg(feature = "mssql")] - SqlServer(tiberius::ColumnType), - #[cfg(feature = "mysql")] - MySQL(mysql_async::consts::ColumnType), -} - -pub trait RowOperations { - #[cfg(feature = "postgres")] - fn get_postgres<'a, Output>(&'a self, col_name: &'a str) -> Output - where - Output: tokio_postgres::types::FromSql<'a>; - #[cfg(feature = "mssql")] - fn get_mssql<'a, Output>(&'a self, col_name: &'a str) -> Output - where - Output: tiberius::FromSql<'a>; - #[cfg(feature = "mysql")] - fn get_mysql<'a, Output>(&'a self, col_name: &'a str) -> Output - where - Output: mysql_async::prelude::FromValue; - - #[cfg(feature = "postgres")] - fn get_postgres_opt<'a, Output>(&'a self, col_name: &'a str) -> Option - where - Output: tokio_postgres::types::FromSql<'a>; - #[cfg(feature = "mssql")] - fn get_mssql_opt<'a, Output>(&'a self, col_name: &'a str) -> Option - where - Output: tiberius::FromSql<'a>; - - #[cfg(feature = "mysql")] - fn get_mysql_opt<'a, Output>(&'a self, col_name: &'a str) -> Option - where - Output: mysql_async::prelude::FromValue; - - fn columns(&self) -> Vec; -} - -impl RowOperations for &dyn Row { - #[cfg(feature = "postgres")] - fn get_postgres<'a, Output>(&'a self, col_name: &'a str) -> Output - where - Output: tokio_postgres::types::FromSql<'a>, - { - if let Some(row) = self.as_any().downcast_ref::() { - return row.get::<&str, Output>(col_name); - }; - panic!() // TODO into result and propagate - } - #[cfg(feature = "mssql")] - fn get_mssql<'a, Output>(&'a self, col_name: &'a str) -> Output - where - Output: tiberius::FromSql<'a>, - { - if let Some(row) = self.as_any().downcast_ref::() { - return row - .get::(col_name) - .expect("Failed to obtain a row in the MSSQL migrations"); - }; - panic!() // TODO into result and propagate - } - - #[cfg(feature = "mysql")] - fn get_mysql<'a, Output>(&'a self, col_name: &'a str) -> Output - where - Output: mysql_async::prelude::FromValue, - { - self.get_mysql_opt(col_name) - .expect("Failed to obtain a column in the MySql") - } - - #[cfg(feature = "postgres")] - fn get_postgres_opt<'a, Output>(&'a self, col_name: &'a str) -> Option - where - Output: tokio_postgres::types::FromSql<'a>, - { - if let Some(row) = self.as_any().downcast_ref::() { - return row.get::<&str, Option>(col_name); - }; - panic!() // TODO into result and propagate - } - - #[cfg(feature = "mssql")] - fn get_mssql_opt<'a, Output>(&'a self, col_name: &'a str) -> Option - where - Output: tiberius::FromSql<'a>, - { - if let Some(row) = self.as_any().downcast_ref::() { - return row.get::(col_name); - }; - panic!() // TODO into result and propagate - } - #[cfg(feature = "mysql")] - fn get_mysql_opt<'a, Output>(&'a self, col_name: &'a str) -> Option - where - Output: mysql_async::prelude::FromValue, - { - if let Some(row) = self.as_any().downcast_ref::() { - return row.get::(col_name); - }; - panic!() // TODO into result and propagate - } - - fn columns(&self) -> Vec { - let mut cols = vec![]; - - #[cfg(feature = "postgres")] - { - if self.as_any().is::() { - self.as_any() - .downcast_ref::() - .expect("Not a tokio postgres Row for column") - .columns() - .iter() - .for_each(|c| { - cols.push(Column { - name: Cow::from(c.name()), - type_: ColumnType::Postgres(c.type_().to_owned()), - }) - }) - } - } - #[cfg(feature = "mssql")] - { - if self.as_any().is::() { - self.as_any() - .downcast_ref::() - .expect("Not a Tiberius Row for column") - .columns() - .iter() - .for_each(|c| { - cols.push(Column { - name: Cow::from(c.name()), - type_: ColumnType::SqlServer(c.column_type()), - }) - }) - }; - } - #[cfg(feature = "mysql")] - { - if let Some(mysql_row) = self.as_any().downcast_ref::() { - mysql_row.columns_ref().iter().for_each(|c| { - cols.push(Column { - name: c.name_str(), - type_: ColumnType::MySQL(c.column_type()), - }) - }) - } - } - - cols - } -} - -/// Defines a trait for represent type bounds against the allowed -/// data types supported by Canyon to be used as query parameters. -pub trait QueryParameter<'a>: std::fmt::Debug + Sync + Send { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync); - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_>; - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue; -} - -/// The implementation of the [`canyon_connection::tiberius`] [`IntoSql`] for the -/// query parameters. -/// -/// This implementation is necessary because of the generic amplitude -/// of the arguments of the [`Transaction::query`], that should work with -/// a collection of [`QueryParameter<'a>`], in order to allow a workflow -/// that is not dependent of the specific type of the argument that holds -/// the query parameters of the database connectors -#[cfg(feature = "mssql")] -impl<'a> IntoSql<'a> for &'a dyn QueryParameter<'a> { - fn into_sql(self) -> ColumnData<'a> { - self.as_sqlserver_param() - } -} - -//TODO Pending to review and see if it is necessary to apply something similar to the previous implementation. - -impl<'a> QueryParameter<'a> for bool { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::Bit(Some(*self)) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn ToValue { - self - } -} -impl<'a> QueryParameter<'a> for i16 { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::I16(Some(*self)) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for &i16 { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::I16(Some(**self)) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for Option { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::I16(*self) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for Option<&i16> { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::I16(Some(*self.unwrap())) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for i32 { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::I32(Some(*self)) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for &i32 { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::I32(Some(**self)) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for Option { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::I32(*self) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for Option<&i32> { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::I32(Some(*self.unwrap())) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for f32 { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::F32(Some(*self)) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for &f32 { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::F32(Some(**self)) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for Option { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::F32(*self) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for Option<&f32> { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::F32(Some( - *self.expect("Error on an f32 value on QueryParameter<'_>"), - )) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for f64 { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::F64(Some(*self)) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for &f64 { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::F64(Some(**self)) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for Option { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::F64(*self) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for Option<&f64> { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::F64(Some( - *self.expect("Error on an f64 value on QueryParameter<'_>"), - )) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for i64 { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::I64(Some(*self)) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for &i64 { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::I64(Some(**self)) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for Option { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::I64(*self) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for Option<&i64> { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::I64(Some(*self.unwrap())) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for String { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::String(Some(std::borrow::Cow::Owned(self.to_owned()))) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for &String { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::String(Some(std::borrow::Cow::Borrowed(self))) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for Option { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - match self { - Some(string) => ColumnData::String(Some(std::borrow::Cow::Owned(string.to_owned()))), - None => ColumnData::String(None), - } - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for Option<&String> { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - match self { - Some(string) => ColumnData::String(Some(std::borrow::Cow::Borrowed(string))), - None => ColumnData::String(None), - } - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for &'_ str { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::String(Some(std::borrow::Cow::Borrowed(*self))) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for Option<&'_ str> { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - match *self { - Some(str) => ColumnData::String(Some(std::borrow::Cow::Borrowed(str))), - None => ColumnData::String(None), - } - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for NaiveDate { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - self.into_sql() - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for Option { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - self.into_sql() - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for NaiveTime { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - self.into_sql() - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for Option { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - self.into_sql() - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for NaiveDateTime { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - self.into_sql() - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl<'a> QueryParameter<'a> for Option { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - self.into_sql() - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} - -//TODO pending -impl<'a> QueryParameter<'a> for DateTime { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - self.into_sql() - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - todo!() - } -} - -impl<'a> QueryParameter<'a> for Option> { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - self.into_sql() - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - todo!() - } -} - -impl<'a> QueryParameter<'a> for DateTime { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - self.into_sql() - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - todo!() - } -} - -impl<'a> QueryParameter<'a> for Option> { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - self.into_sql() - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - todo!() - } -} diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index dbfc70ca..e14005f7 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -1,70 +1,10 @@ use async_trait::async_trait; -use std::fmt::Display; +use canyon_core::{mapper::RowMapper, query::Transaction}; +use canyon_core::query_parameters::QueryParameter; -use canyon_connection::db_connector::DatabaseConnection; -use canyon_connection::{get_database_connection, CACHED_DATABASE_CONN}; - -use crate::bounds::QueryParameter; -use crate::mapper::RowMapper; use crate::query_elements::query_builder::{ DeleteQueryBuilder, SelectQueryBuilder, UpdateQueryBuilder, }; -use crate::rows::CanyonRows; - -#[cfg(feature = "mysql")] -pub const DETECT_PARAMS_IN_QUERY: &str = r"\$([\d])+"; -#[cfg(feature = "mysql")] -pub const DETECT_QUOTE_IN_QUERY: &str = r#"\"|\\"#; - -/// This traits defines and implements a query against a database given -/// an statement `stmt` and the params to pass the to the client. -/// -/// Returns [`std::result::Result`] of [`CanyonRows`], which is the core Canyon type to wrap -/// the result of the query provide automatic mappings and deserialization -#[async_trait] -pub trait Transaction { - /// Performs a query against the targeted database by the selected or - /// the defaulted datasource, wrapping the resultant collection of entities - /// in [`super::rows::CanyonRows`] - async fn query<'a, S, Z>( - stmt: S, - params: Z, - datasource_name: &'a str, - ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'static)>> - where - S: AsRef + Display + Sync + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, - { - let mut guarded_cache = CACHED_DATABASE_CONN.lock().await; - let database_conn = get_database_connection(datasource_name, &mut guarded_cache); - - match *database_conn { - #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(_) => { - postgres_query_launcher::launch::( - database_conn, - stmt.to_string(), - params.as_ref(), - ) - .await - } - #[cfg(feature = "mssql")] - DatabaseConnection::SqlServer(_) => { - sqlserver_query_launcher::launch::( - database_conn, - &mut stmt.to_string(), - params, - ) - .await - } - #[cfg(feature = "mysql")] - DatabaseConnection::MySQL(_) => { - mysql_query_launcher::launch::(database_conn, stmt.to_string(), params.as_ref()) - .await - } - } - } -} /// *CrudOperations* it's the core part of Canyon-SQL. /// @@ -153,180 +93,3 @@ where fn delete_query_datasource(datasource_name: &str) -> DeleteQueryBuilder<'_, T>; } - -#[cfg(feature = "postgres")] -mod postgres_query_launcher { - use canyon_connection::db_connector::DatabaseConnection; - - use crate::bounds::QueryParameter; - use crate::rows::CanyonRows; - - pub async fn launch<'a, T>( - db_conn: &DatabaseConnection, - stmt: String, - params: &'a [&'_ dyn QueryParameter<'_>], - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { - let mut m_params = Vec::new(); - for param in params { - m_params.push(param.as_postgres_param()); - } - - let r = db_conn - .postgres_connection() - .client - .query(&stmt, m_params.as_slice()) - .await?; - - Ok(CanyonRows::Postgres(r)) - } -} - -#[cfg(feature = "mssql")] -mod sqlserver_query_launcher { - use crate::rows::CanyonRows; - use crate::{ - bounds::QueryParameter, - canyon_connection::{db_connector::DatabaseConnection, tiberius::Query}, - }; - - pub async fn launch<'a, T, Z>( - db_conn: &mut DatabaseConnection, - stmt: &mut String, - params: Z, - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> - where - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, - { - // Re-generate de insert statement to adequate it to the SQL SERVER syntax to retrieve the PK value(s) after insert - // TODO: redo this branch into the generated queries, before the MACROS - if stmt.contains("RETURNING") { - let c = stmt.clone(); - let temp = c.split_once("RETURNING").unwrap(); - let temp2 = temp.0.split_once("VALUES").unwrap(); - - *stmt = format!( - "{} OUTPUT inserted.{} VALUES {}", - temp2.0.trim(), - temp.1.trim(), - temp2.1.trim() - ); - } - - let mut mssql_query = Query::new(stmt.to_owned().replace('$', "@P")); - params - .as_ref() - .iter() - .for_each(|param| mssql_query.bind(*param)); - - let _results = mssql_query - .query(db_conn.sqlserver_connection().client) - .await? - .into_results() - .await?; - - Ok(CanyonRows::Tiberius( - _results.into_iter().flatten().collect(), - )) - } -} - -#[cfg(feature = "mysql")] -mod mysql_query_launcher { - use std::sync::Arc; - - use mysql_async::prelude::Query; - use mysql_async::QueryWithParams; - use mysql_async::Value; - - use canyon_connection::db_connector::DatabaseConnection; - - use crate::bounds::QueryParameter; - use crate::rows::CanyonRows; - use mysql_async::Row; - use mysql_common::constants::ColumnType; - use mysql_common::row; - - use super::reorder_params; - use crate::crud::{DETECT_PARAMS_IN_QUERY, DETECT_QUOTE_IN_QUERY}; - use regex::Regex; - - pub async fn launch<'a, T>( - db_conn: &DatabaseConnection, - stmt: String, - params: &'a [&'_ dyn QueryParameter<'_>], - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { - let mysql_connection = db_conn.mysql_connection().client.get_conn().await?; - - let stmt_with_escape_characters = regex::escape(&stmt); - let query_string = - Regex::new(DETECT_PARAMS_IN_QUERY)?.replace_all(&stmt_with_escape_characters, "?"); - - let mut query_string = Regex::new(DETECT_QUOTE_IN_QUERY)? - .replace_all(&query_string, "") - .to_string(); - - let mut is_insert = false; - if let Some(index_start_clausule_returning) = query_string.find(" RETURNING") { - query_string.truncate(index_start_clausule_returning); - is_insert = true; - } - - let params_query: Vec = - reorder_params(&stmt, params, |f| f.as_mysql_param().to_value()); - - let query_with_params = QueryWithParams { - query: query_string, - params: params_query, - }; - - let mut query_result = query_with_params - .run(mysql_connection) - .await - .expect("Error executing query in mysql"); - - let result_rows = if is_insert { - let last_insert = query_result - .last_insert_id() - .map(Value::UInt) - .expect("Error getting pk id in insert"); - - vec![row::new_row( - vec![last_insert], - Arc::new([mysql_async::Column::new(ColumnType::MYSQL_TYPE_UNKNOWN)]), - )] - } else { - query_result - .collect::() - .await - .expect("Error resolved trait FromRow in mysql") - }; - - Ok(CanyonRows::MySQL(result_rows)) - } -} - -#[cfg(feature = "mysql")] -fn reorder_params( - stmt: &str, - params: &[&'_ dyn QueryParameter<'_>], - fn_parser: impl Fn(&&dyn QueryParameter<'_>) -> T, -) -> Vec { - let mut ordered_params = vec![]; - let rg = regex::Regex::new(DETECT_PARAMS_IN_QUERY) - .expect("Error create regex with detect params pattern expression"); - - for positional_param in rg.find_iter(stmt) { - let pp: &str = positional_param.as_str(); - let pp_index = pp[1..] // param $1 -> get 1 - .parse::() - .expect("Error parse mapped parameter to usized.") - - 1; - - let element = params - .get(pp_index) - .expect("Error obtaining the element of the mapping against parameters."); - ordered_params.push(fn_parser(element)); - } - - ordered_params -} diff --git a/canyon_crud/src/lib.rs b/canyon_crud/src/lib.rs index 20061096..f5260756 100644 --- a/canyon_crud/src/lib.rs +++ b/canyon_crud/src/lib.rs @@ -3,9 +3,7 @@ extern crate canyon_connection; pub mod bounds; pub mod crud; -pub mod mapper; pub mod query_elements; -pub mod rows; pub use query_elements::operators::*; diff --git a/canyon_crud/src/query_elements/query.rs b/canyon_crud/src/query_elements/query.rs index 3923d3b6..f77ee3b8 100644 --- a/canyon_crud/src/query_elements/query.rs +++ b/canyon_crud/src/query_elements/query.rs @@ -1,10 +1,7 @@ use std::{fmt::Debug, marker::PhantomData}; -use crate::{ - bounds::QueryParameter, - crud::{CrudOperations, Transaction}, - mapper::RowMapper, -}; +use canyon_core::{mapper::RowMapper, query::Transaction, query_parameters::QueryParameter}; +use crate::crud::CrudOperations; /// Holds a sql sentence details #[derive(Debug, Clone)] diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index f0088dd5..33f9b01e 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -3,18 +3,19 @@ use std::fmt::Debug; use canyon_connection::{ database_type::DatabaseType, get_database_config, DATASOURCES, }; +use canyon_core::{mapper::RowMapper, query::Transaction, query_parameters::QueryParameter}; use crate::{ - bounds::{FieldIdentifier, FieldValueIdentifier, QueryParameter}, - crud::{CrudOperations, Transaction}, - mapper::RowMapper, - query_elements::query::Query, - Operator, + bounds::{FieldIdentifier, FieldValueIdentifier}, crud::CrudOperations, query_elements::query::Query, Operator }; /// Contains the elements that makes part of the formal declaration /// of the behaviour of the Canyon-SQL QueryBuilder pub mod ops { + use canyon_core::{mapper::RowMapper, query::Transaction, query_parameters::QueryParameter}; + + use crate::crud::CrudOperations; + pub use super::*; /// The [`QueryBuilder`] trait is the root of a kind of hierarchy @@ -174,13 +175,15 @@ where ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'static)>> { self.query.sql.push(';'); - Ok(T::query( - self.query.sql.clone(), - self.query.params.to_vec(), - self.datasource_name, - ) - .await? - .into_results::()) + // Ok(T::query( + // self, + // self.query.sql.clone(), + // self.query.params.to_vec(), + // // self.datasource_name, + // ) + // .await? + // .into_results::()) + todo!() } pub fn r#where>(&mut self, r#where: Z, op: impl Operator) { diff --git a/canyon_migrations/Cargo.toml b/canyon_migrations/Cargo.toml index ec9a31db..fd0dd8dd 100644 --- a/canyon_migrations/Cargo.toml +++ b/canyon_migrations/Cargo.toml @@ -10,6 +10,7 @@ license.workspace = true description.workspace = true [dependencies] +canyon_core = { workspace = true } canyon_crud = { workspace = true } canyon_connection = { workspace = true } canyon_entities = { workspace = true } diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index 3d00da8b..dc458c2d 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -1,14 +1,10 @@ use canyon_connection::{datasources::Migrations as MigrationsStatus, DATASOURCES}; -use canyon_crud::rows::CanyonRows; +use canyon_core::{query::Transaction, rows::CanyonRows}; use canyon_entities::CANYON_REGISTER_ENTITIES; use partialdebug::placeholder::PartialDebug; use crate::{ - canyon_crud::{ - bounds::{Column, Row, RowOperations}, - crud::Transaction, - DatabaseType, - }, + canyon_crud::DatabaseType, constants, migrations::{ information_schema::{ColumnMetadata, ColumnMetadataTypeValue, TableMetadata}, diff --git a/src/lib.rs b/src/lib.rs index 9eb9f926..b3b6d0ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ /// Here it's where all the available functionalities and features /// reaches the top most level, grouping them and making them visible /// through this crate, building the *public API* of the library +extern crate canyon_core; extern crate canyon_connection; extern crate canyon_crud; extern crate canyon_macros; @@ -38,13 +39,16 @@ pub mod connection { pub use canyon_connection::db_connector::DatabaseConnection::MySQL; } +pub mod core { + pub use canyon_core::rows::CanyonRows; +} + /// Crud module serves to reexport the public elements of the `canyon_crud` crate, /// exposing them through the public API pub mod crud { pub use canyon_crud::bounds; pub use canyon_crud::crud::*; pub use canyon_crud::mapper::*; - pub use canyon_crud::rows::CanyonRows; pub use canyon_crud::DatabaseType; } From e9a1dca14a3f21479affaf61d68b0a1a61805ca1 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sat, 18 Jan 2025 11:48:13 +0100 Subject: [PATCH 015/155] feat: refactoring the internal of the database clients. As usual, only the piece of s*** of the public API of tiberius is making us go crazy --- Cargo.toml | 6 +- canyon_connection/Cargo.toml | 6 +- canyon_connection/src/db_connector.rs | 332 +++++------------- canyon_connection/src/provisional_tests.rs | 143 ++++++++ canyon_core/src/lib.rs | 13 +- canyon_core/src/query.rs | 26 +- canyon_core/src/rows.rs | 7 + canyon_crud/Cargo.toml | 6 +- canyon_macros/Cargo.toml | 7 +- canyon_migrations/Cargo.toml | 8 +- canyon_migrations/src/migrations/handler.rs | 6 +- .../src/migrations/information_schema.rs | 2 +- canyon_migrations/src/migrations/memory.rs | 3 +- canyon_migrations/src/migrations/processor.rs | 3 +- 14 files changed, 294 insertions(+), 274 deletions(-) create mode 100644 canyon_connection/src/provisional_tests.rs diff --git a/Cargo.toml b/Cargo.toml index 8ce99239..d9179984 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,7 @@ license = "MIT" description = "A Rust ORM and QueryBuilder" [features] -postgres = ["tokio-postgres", "canyon_connection/postgres", "canyon_crud/postgres", "canyon_migrations/postgres", "canyon_macros/postgres"] -mssql = ["tiberius", "canyon_connection/mssql", "canyon_crud/mssql", "canyon_migrations/mssql", "canyon_macros/mssql"] -mysql = ["mysql_async", "mysql_common", "canyon_connection/mysql", "canyon_crud/mysql", "canyon_migrations/mysql", "canyon_macros/mysql"] +postgres = ["tokio-postgres", "canyon_core/postgres", "canyon_connection/postgres", "canyon_crud/postgres", "canyon_migrations/postgres", "canyon_macros/postgres"] +mssql = ["tiberius", "canyon_core/mssql", "canyon_connection/mssql", "canyon_crud/mssql", "canyon_migrations/mssql", "canyon_macros/mssql"] +mysql = ["mysql_async", "mysql_common", "canyon_core/mysql", "canyon_connection/mysql", "canyon_crud/mysql", "canyon_migrations/mysql", "canyon_macros/mysql"] migrations = ["canyon_migrations", "canyon_macros/migrations"] diff --git a/canyon_connection/Cargo.toml b/canyon_connection/Cargo.toml index 14cba996..adaf4261 100644 --- a/canyon_connection/Cargo.toml +++ b/canyon_connection/Cargo.toml @@ -32,8 +32,8 @@ walkdir = { workspace = true } regex = { workspace = true } [features] -postgres = ["tokio-postgres"] -mssql = ["tiberius", "async-std"] -mysql = ["mysql_async","mysql_common"] +postgres = ["tokio-postgres", "canyon_core/postgres"] +mssql = ["tiberius", "async-std", "canyon_core/mssql"] +mysql = ["mysql_async","mysql_common", "canyon_core/mysql"] diff --git a/canyon_connection/src/db_connector.rs b/canyon_connection/src/db_connector.rs index 897a773f..81b0b743 100644 --- a/canyon_connection/src/db_connector.rs +++ b/canyon_connection/src/db_connector.rs @@ -1,7 +1,7 @@ +use std::fmt::Display; + #[cfg(feature = "mssql")] use async_std::net::TcpStream; -use canyon_core::query_parameters::QueryParameter; -use canyon_core::rows::CanyonRows; #[cfg(feature = "mysql")] use mysql_async::Pool; #[cfg(feature = "mssql")] @@ -12,6 +12,8 @@ use tokio_postgres::{Client, NoTls}; use crate::database_type::DatabaseType; use crate::datasources::DatasourceConfig; use canyon_core::query::{DbConnection, Transaction}; +use canyon_core::query_parameters::QueryParameter; +use canyon_core::rows::CanyonRows; use async_trait::async_trait; @@ -53,40 +55,17 @@ unsafe impl Sync for DatabaseConnection {} #[async_trait] impl Transaction for DatabaseConnection { - async fn query<'a, S, Z>( + async fn query<'a, C, S, Z>( stmt: S, params: Z, - database_connection: impl DbConnection + db_conn: &C, ) -> Result> - where - S: AsRef + std::fmt::Display + Sync + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a + where + S: AsRef + std::fmt::Display + Sync + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, + C: DbConnection + Display + Sync + Send + 'a, { - match database_connection { - #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(_) => { - postgres_query_launcher::launch( - self, - stmt.to_string(), - params.as_ref(), - ) - .await - } - #[cfg(feature = "mssql")] - DatabaseConnection::SqlServer(_) => { - sqlserver_query_launcher::launch::( - self, - &mut stmt.to_string(), - params, - ) - .await - } - #[cfg(feature = "mysql")] - DatabaseConnection::MySQL(_) => { - mysql_query_launcher::launch(self, stmt.to_string(), params.as_ref()) - .await - } - } + db_conn.launch(stmt.as_ref(), params.as_ref()).await } } @@ -272,222 +251,83 @@ mod auth { } } -// TODO: && NOTE: tests defined below should be integration tests, unfortunately, since they require a new connection to be made -// Or just to split them further, and just unit test the url string generation from the actual connection instantion -// #[cfg(test)] -// mod connection_tests { -// use tokio; -// use super::connection_helpers::*; -// use crate::{db_connector::DatabaseConnection, datasources::{Auth, DatasourceConfig, DatasourceProperties, PostgresAuth}}; - -// #[tokio::test] -// #[cfg(feature = "postgres")] -// async fn test_create_postgres_connection() { -// use crate::datasources::PostgresAuth; - -// let config = DatasourceConfig { -// name: "PostgresDs".to_string(), -// auth: Auth::Postgres(PostgresAuth::Basic { -// username: "test_user".to_string(), -// password: "test_password".to_string(), -// }), -// properties: DatasourceProperties { -// host: "localhost".to_string(), -// port: Some(5432), -// db_name: "test_db".to_string(), -// migrations: None -// }, -// }; - -// let result = create_postgres_connection(&config).await; -// assert!(result.is_ok()); -// } - -// #[tokio::test] -// #[cfg(feature = "mssql")] -// async fn test_create_sqlserver_connection() { -// use crate::datasources::SqlServerAuth; - -// let config = DatasourceConfig { -// name: "SqlServerDs".to_string(), -// auth: Auth::SqlServer(SqlServerAuth::Basic { -// username: "test_user".to_string(), -// password: "test_password".to_string(), -// }), -// properties: DatasourceProperties { -// host: "localhost".to_string(), -// port: Some(1433), -// db_name: "test_db".to_string(), -// migrations: None -// }, -// }; - -// let result = create_sqlserver_connection(&config).await; -// assert!(result.is_ok()); -// } - -// #[tokio::test] -// #[cfg(feature = "mysql")] -// async fn test_create_mysql_connection() { -// use crate::datasources::MySQLAuth; - -// let config = DatasourceConfig { -// name: "MySQLDs".to_string(), -// auth: Auth::MySQL(MySQLAuth::Basic { -// username: "test_user".to_string(), -// password: "test_password".to_string(), -// }), -// properties: DatasourceProperties { -// host: "localhost".to_string(), -// port: Some(3306), -// db_name: "test_db".to_string(), -// migrations: None, -// }, -// }; - -// let result = create_mysql_connection(&config).await; -// assert!(result.is_ok()); -// } - -// #[tokio::test] -// async fn test_database_connection_new() { -// #[cfg(feature = "postgres")] -// { -// use crate::datasources::PostgresAuth; - -// let config = DatasourceConfig { -// name: "PostgresDs".to_string(), -// auth: Auth::Postgres(PostgresAuth::Basic { -// username: "test_user".to_string(), -// password: "test_password".to_string(), -// }), -// properties: DatasourceProperties { -// host: "localhost".to_string(), -// port: Some(5432), -// db_name: "test_db".to_string(), -// migrations: None -// }, -// }; - -// let result = DatabaseConnection::new(&config).await; -// assert!(result.is_ok()); -// } - -// // #[cfg(feature = "mssql")] -// // { -// // let config = DatasourceConfig { -// // db_type: DatabaseType::SqlServer, -// // auth: Auth::SqlServer(SqlServerAuth::Basic { -// // username: "test_user".to_string(), -// // password: "test_password".to_string(), -// // }), -// // properties: crate::datasources::Properties { -// // host: "localhost".to_string(), -// // port: Some(1433), -// // db_name: "test_db".to_string(), -// // }, -// // }; - -// // let result = DatabaseConnection::new(&config).await; -// // assert!(result.is_ok()); -// // } - -// // #[cfg(feature = "mysql")] -// // { -// // let config = DatasourceConfig { -// // db_type: DatabaseType::MySQL, -// // auth: Auth::MySQL(MySQLAuth::Basic { -// // username: "test_user".to_string(), -// // password: "test_password".to_string(), -// // }), -// // properties: crate::datasources::Properties { -// // host: "localhost".to_string(), -// // port: Some(3306), -// // db_name: "test_db".to_string(), -// // }, -// // }; - -// // let result = DatabaseConnection::new(&config).await; -// // assert!(result.is_ok()); -// // } -// } -// } - #[cfg(feature = "postgres")] mod postgres_query_launcher { - use canyon_core::{query_parameters::QueryParameter, rows::CanyonRows}; - - use super::DatabaseConnection; + use super::*; + #[async_trait] + impl DbConnection for PostgreSqlConnection { + async fn launch( + &self, + stmt: &str, + params: &[&dyn QueryParameter<'_>], + ) -> Result> { + let mut m_params = Vec::new(); + for param in params { + m_params.push((*param).as_postgres_param()); + } + let r = self.client.query(stmt, m_params.as_slice()).await?; - pub async fn launch<'a>( - db_conn: &DatabaseConnection, - stmt: String, - params: &'a [&'_ dyn QueryParameter<'_>], - ) -> Result> { - let mut m_params = Vec::new(); - for param in params { - m_params.push((*param).as_postgres_param()); + Ok(CanyonRows::Postgres(r)) } - - let r = db_conn - .postgres_connection() - .client - .query(&stmt, m_params.as_slice()) - .await?; - - Ok(CanyonRows::Postgres(r)) } } #[cfg(feature = "mssql")] mod sqlserver_query_launcher { - use canyon_core::{query_parameters::QueryParameter, rows::CanyonRows}; + use super::SqlServerConnection; + use async_trait::async_trait; + use canyon_core::{query::DbConnection, query_parameters::QueryParameter, rows::CanyonRows}; use tiberius::Query; - use super::DatabaseConnection; - - - pub async fn launch<'a, Z>( - db_conn: & DatabaseConnection, - stmt: &mut String, - params: Z, - ) -> Result> - where - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, - { - // Re-generate de insert statement to adequate it to the SQL SERVER syntax to retrieve the PK value(s) after insert - // TODO: redo this branch into the generated queries, before the MACROS - if stmt.contains("RETURNING") { - let c = stmt.clone(); - let temp = c.split_once("RETURNING").unwrap(); - let temp2 = temp.0.split_once("VALUES").unwrap(); - - *stmt = format!( - "{} OUTPUT inserted.{} VALUES {}", - temp2.0.trim(), - temp.1.trim(), - temp2.1.trim() - ); + #[async_trait] + impl DbConnection for SqlServerConnection { + async fn launch( + &self, + stmt: &str, + params: &[&dyn QueryParameter<'_>], + ) -> Result> { + // Re-generate de insert statement to adequate it to the SQL SERVER syntax to retrieve the PK value(s) after insert + // TODO: redo this branch into the generated queries, before the MACROS + // if stmt.contains("RETURNING") { + // let c = stmt.clone(); + // let temp = c.split_once("RETURNING").unwrap(); + // let temp2 = temp.0.split_once("VALUES").unwrap(); + // + // *stmt = format!( + // "{} OUTPUT inserted.{} VALUES {}", + // temp2.0.trim(), + // temp.1.trim(), + // temp2.1.trim() + // ); + // } + + // TODO: We must address the query generation. Look at the returning example, or the + // replace below. We may use our own type Query to address this concerns when the query + // is generated + let mut mssql_query = Query::new(stmt.to_owned().replace('$', "@P")); + // params + // .into_iter() + // .for_each(|param| mssql_query.bind(*param)); + + for param in params.clone() { + let p = param.clone(); + mssql_query.bind(p) + } + #[allow(mutable_transmutes)] + let sqlservconn = unsafe { + std::mem::transmute::<&SqlServerConnection, &mut SqlServerConnection>(self) + }; + let _results = mssql_query + .query(sqlservconn.client) + .await? + .into_results() + .await?; + + Ok(CanyonRows::Tiberius( + _results.into_iter().flatten().collect(), + )) } - - let mut mssql_query = Query::new(stmt.to_owned().replace('$', "@P")); - params - .as_ref() - .iter() - .for_each(|param| mssql_query.bind(*param)); - - #[allow(mutable_transmutes)] - let sqlservconn = unsafe { std::mem::transmute::<&DatabaseConnection, &mut DatabaseConnection>(db_conn) }; - let _results = mssql_query - .query(sqlservconn.sqlserver_connection().client) - .await? - .into_results() - .await?; - - Ok(CanyonRows::Tiberius( - _results.into_iter().flatten().collect(), - )) } } @@ -500,11 +340,13 @@ mod mysql_query_launcher { use std::sync::Arc; + use async_trait::async_trait; + use canyon_core::query::DbConnection; use mysql_async::prelude::Query; use mysql_async::QueryWithParams; use mysql_async::Value; - use super::DatabaseConnection; + use super::MysqlConnection; use canyon_core::query_parameters::QueryParameter; use canyon_core::rows::CanyonRows; @@ -513,12 +355,14 @@ mod mysql_query_launcher { use mysql_common::row; use regex::Regex; - pub async fn launch<'a>( - db_conn: &DatabaseConnection, - stmt: String, - params: &'a [&'_ dyn QueryParameter<'_>], - ) -> Result> { - let mysql_connection = db_conn.mysql_connection().client.get_conn().await?; + #[async_trait] + impl DbConnection for MysqlConnection { + async fn launch( + &self, + stmt: &str, + params: &[&dyn QueryParameter<'_>], + ) -> Result> { + let mysql_connection = self.client.get_conn().await?; let stmt_with_escape_characters = regex::escape(&stmt); let query_string = @@ -563,9 +407,9 @@ mod mysql_query_launcher { .await .expect("Error resolved trait FromRow in mysql") }; -let a = CanyonRows::MySQL(result_rows); + let a = CanyonRows::MySQL(result_rows); Ok(a) - } + } } #[cfg(feature = "mysql")] fn reorder_params( diff --git a/canyon_connection/src/provisional_tests.rs b/canyon_connection/src/provisional_tests.rs new file mode 100644 index 00000000..c48a0b38 --- /dev/null +++ b/canyon_connection/src/provisional_tests.rs @@ -0,0 +1,143 @@ + + +// TODO: && NOTE: tests defined below should be integration tests, unfortunately, since they require a new connection to be made +// Or just to split them further, and just unit test the url string generation from the actual connection instantion +// #[cfg(test)] +// mod connection_tests { +// use tokio; +// use super::connection_helpers::*; +// use crate::{db_connector::DatabaseConnection, datasources::{Auth, DatasourceConfig, DatasourceProperties, PostgresAuth}}; + +// #[tokio::test] +// #[cfg(feature = "postgres")] +// async fn test_create_postgres_connection() { +// use crate::datasources::PostgresAuth; + +// let config = DatasourceConfig { +// name: "PostgresDs".to_string(), +// auth: Auth::Postgres(PostgresAuth::Basic { +// username: "test_user".to_string(), +// password: "test_password".to_string(), +// }), +// properties: DatasourceProperties { +// host: "localhost".to_string(), +// port: Some(5432), +// db_name: "test_db".to_string(), +// migrations: None +// }, +// }; + +// let result = create_postgres_connection(&config).await; +// assert!(result.is_ok()); +// } + +// #[tokio::test] +// #[cfg(feature = "mssql")] +// async fn test_create_sqlserver_connection() { +// use crate::datasources::SqlServerAuth; + +// let config = DatasourceConfig { +// name: "SqlServerDs".to_string(), +// auth: Auth::SqlServer(SqlServerAuth::Basic { +// username: "test_user".to_string(), +// password: "test_password".to_string(), +// }), +// properties: DatasourceProperties { +// host: "localhost".to_string(), +// port: Some(1433), +// db_name: "test_db".to_string(), +// migrations: None +// }, +// }; + +// let result = create_sqlserver_connection(&config).await; +// assert!(result.is_ok()); +// } + +// #[tokio::test] +// #[cfg(feature = "mysql")] +// async fn test_create_mysql_connection() { +// use crate::datasources::MySQLAuth; + +// let config = DatasourceConfig { +// name: "MySQLDs".to_string(), +// auth: Auth::MySQL(MySQLAuth::Basic { +// username: "test_user".to_string(), +// password: "test_password".to_string(), +// }), +// properties: DatasourceProperties { +// host: "localhost".to_string(), +// port: Some(3306), +// db_name: "test_db".to_string(), +// migrations: None, +// }, +// }; + +// let result = create_mysql_connection(&config).await; +// assert!(result.is_ok()); +// } + +// #[tokio::test] +// async fn test_database_connection_new() { +// #[cfg(feature = "postgres")] +// { +// use crate::datasources::PostgresAuth; + +// let config = DatasourceConfig { +// name: "PostgresDs".to_string(), +// auth: Auth::Postgres(PostgresAuth::Basic { +// username: "test_user".to_string(), +// password: "test_password".to_string(), +// }), +// properties: DatasourceProperties { +// host: "localhost".to_string(), +// port: Some(5432), +// db_name: "test_db".to_string(), +// migrations: None +// }, +// }; + +// let result = DatabaseConnection::new(&config).await; +// assert!(result.is_ok()); +// } + +// // #[cfg(feature = "mssql")] +// // { +// // let config = DatasourceConfig { +// // db_type: DatabaseType::SqlServer, +// // auth: Auth::SqlServer(SqlServerAuth::Basic { +// // username: "test_user".to_string(), +// // password: "test_password".to_string(), +// // }), +// // properties: crate::datasources::Properties { +// // host: "localhost".to_string(), +// // port: Some(1433), +// // db_name: "test_db".to_string(), +// // }, +// // }; + +// // let result = DatabaseConnection::new(&config).await; +// // assert!(result.is_ok()); +// // } + +// // #[cfg(feature = "mysql")] +// // { +// // let config = DatasourceConfig { +// // db_type: DatabaseType::MySQL, +// // auth: Auth::MySQL(MySQLAuth::Basic { +// // username: "test_user".to_string(), +// // password: "test_password".to_string(), +// // }), +// // properties: crate::datasources::Properties { +// // host: "localhost".to_string(), +// // port: Some(3306), +// // db_name: "test_db".to_string(), +// // }, +// // }; + +// // let result = DatabaseConnection::new(&config).await; +// // assert!(result.is_ok()); +// // } +// } +// } + diff --git a/canyon_core/src/lib.rs b/canyon_core/src/lib.rs index 5bf9e0a3..a7fd7465 100644 --- a/canyon_core/src/lib.rs +++ b/canyon_core/src/lib.rs @@ -1,6 +1,17 @@ +#[cfg(feature = "postgres")] +pub extern crate tokio_postgres; + +#[cfg(feature = "mssql")] +pub extern crate async_std; +#[cfg(feature = "mssql")] +pub extern crate tiberius; + +#[cfg(feature = "mysql")] +pub extern crate mysql_async; + pub mod query; pub mod query_parameters; pub mod row; pub mod rows; pub mod column; -pub mod mapper; \ No newline at end of file +pub mod mapper; diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index a285c74b..b350b4e4 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -4,21 +4,33 @@ use async_trait::async_trait; use crate::{query_parameters::QueryParameter, rows::CanyonRows}; -pub trait DbConnection{} +#[async_trait] +pub trait DbConnection { + async fn launch( + &self, + stmt: &str, + params: &[&dyn QueryParameter<'_>], + ) -> Result>; +} #[async_trait] -pub trait Transaction { // provisional name +pub trait Transaction { + // provisional name /// Performs a query against the targeted database by the selected or /// the defaulted datasource, wrapping the resultant collection of entities /// in [`super::rows::CanyonRows`] - async fn query<'a, S, Z>( + async fn query<'a, C, S, Z>( + // &self, stmt: S, params: Z, - database_conn: impl DatabaseConnection + Send + db_conn: &C, ) -> Result> where S: AsRef + Display + Sync + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a { - Ok(CanyonRows::Postgres(vec![])) - } + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, + C: DbConnection + Display + Sync + Send + 'a, + { + // Ok(CanyonRows::Postgres(vec![])) + todo!() + } } diff --git a/canyon_core/src/rows.rs b/canyon_core/src/rows.rs index 7ba9a00f..e52c8f19 100644 --- a/canyon_core/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -1,3 +1,10 @@ +#[cfg(feature = "postgres")] +use tokio_postgres::{self}; +#[cfg(feature = "mysql")] +use mysql_async::{self}; +#[cfg(feature = "mssql")] +use tiberius::{self}; + use crate::mapper::RowMapper; /// Lightweight wrapper over the collection of results of the different crates diff --git a/canyon_crud/Cargo.toml b/canyon_crud/Cargo.toml index 96c1d070..2b15487d 100644 --- a/canyon_crud/Cargo.toml +++ b/canyon_crud/Cargo.toml @@ -23,6 +23,6 @@ async-trait = { workspace = true } regex = { workspace = true } [features] -postgres = ["tokio-postgres", "canyon_connection/postgres"] -mssql = ["tiberius", "canyon_connection/mssql"] -mysql = ["mysql_async","mysql_common", "canyon_connection/mysql"] +postgres = ["tokio-postgres", "canyon_core/postgres", "canyon_connection/postgres"] +mssql = ["tiberius", "canyon_core/mssql", "canyon_connection/mssql"] +mysql = ["mysql_async","mysql_common", "canyon_core/mysql", "canyon_connection/mysql"] diff --git a/canyon_macros/Cargo.toml b/canyon_macros/Cargo.toml index 8b8a2852..1ba7d67b 100755 --- a/canyon_macros/Cargo.toml +++ b/canyon_macros/Cargo.toml @@ -19,14 +19,15 @@ proc-macro2 = { workspace = true } futures = { workspace = true } tokio = { workspace = true } +canyon_core = { workspace = true } canyon_connection = { workspace = true } canyon_crud = { workspace = true } canyon_entities = { workspace = true } canyon_migrations = { workspace = true, optional = true } [features] -postgres = ["canyon_connection/postgres", "canyon_crud/postgres", "canyon_migrations/postgres"] -mssql = ["canyon_connection/mssql", "canyon_crud/mssql", "canyon_migrations/mssql"] -mysql = ["canyon_connection/mysql", "canyon_crud/mysql", "canyon_migrations/mysql"] +postgres = ["canyon_core/postgres", "canyon_connection/postgres", "canyon_crud/postgres", "canyon_migrations/postgres"] +mssql = ["canyon_core/mssql", "canyon_connection/mssql", "canyon_crud/mssql", "canyon_migrations/mssql"] +mysql = ["canyon_core/mysql", "canyon_connection/mysql", "canyon_crud/mysql", "canyon_migrations/mysql"] migrations = ["canyon_migrations"] diff --git a/canyon_migrations/Cargo.toml b/canyon_migrations/Cargo.toml index fd0dd8dd..70927d21 100644 --- a/canyon_migrations/Cargo.toml +++ b/canyon_migrations/Cargo.toml @@ -11,8 +11,8 @@ description.workspace = true [dependencies] canyon_core = { workspace = true } -canyon_crud = { workspace = true } canyon_connection = { workspace = true } +canyon_crud = { workspace = true } canyon_entities = { workspace = true } tokio = { workspace = true } @@ -32,7 +32,7 @@ quote = { workspace = true } syn = { version = "1.0.86", features = ["full", "parsing"] } # TODO Pending to refactor and upgrade [features] -postgres = ["tokio-postgres", "canyon_connection/postgres", "canyon_crud/postgres"] -mssql = ["tiberius", "canyon_connection/mssql", "canyon_crud/mssql"] -mysql = ["mysql_async","mysql_common", "canyon_connection/mysql", "canyon_crud/mysql"] +postgres = ["tokio-postgres", "canyon_core/postgres", "canyon_connection/postgres", "canyon_crud/postgres"] +mssql = ["tiberius", "canyon_core/mssql", "canyon_connection/mssql", "canyon_crud/mssql"] +mysql = ["mysql_async","mysql_common", "canyon_core/mysql", "canyon_connection/mysql", "canyon_crud/mysql"] diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index dc458c2d..b045ba1d 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -1,5 +1,5 @@ use canyon_connection::{datasources::Migrations as MigrationsStatus, DATASOURCES}; -use canyon_core::{query::Transaction, rows::CanyonRows}; +use canyon_core::{column::Column, query::Transaction, row::Row, rows::CanyonRows}; use canyon_entities::CANYON_REGISTER_ENTITIES; use partialdebug::placeholder::PartialDebug; @@ -84,7 +84,7 @@ impl Migrations { async fn fetch_database( datasource_name: &str, db_type: DatabaseType, - ) -> CanyonRows { + ) -> CanyonRows { let query = match db_type { #[cfg(feature = "postgres")] DatabaseType::PostgreSql => constants::postgresql_queries::FETCH_PUBLIC_SCHEMA, @@ -106,7 +106,7 @@ impl Migrations { /// Handler for parse the result of query the information of some database schema, /// and extract the content of the returned rows into custom structures with /// the data well organized for every entity present on that schema - fn map_rows(db_results: CanyonRows, db_type: DatabaseType) -> Vec { + fn map_rows(db_results: CanyonRows, db_type: DatabaseType) -> Vec { match db_results { #[cfg(feature = "postgres")] CanyonRows::Postgres(v) => Self::process_tp_rows(v, db_type), diff --git a/canyon_migrations/src/migrations/information_schema.rs b/canyon_migrations/src/migrations/information_schema.rs index 9e165eee..bb852073 100644 --- a/canyon_migrations/src/migrations/information_schema.rs +++ b/canyon_migrations/src/migrations/information_schema.rs @@ -2,7 +2,7 @@ use canyon_connection::tiberius::ColumnType as TIB_TY; #[cfg(feature = "postgres")] use canyon_connection::tokio_postgres::types::Type as TP_TYP; -use canyon_crud::bounds::{Column, ColumnType, Row, RowOperations}; +use canyon_core::{column::{Column, ColumnType}, row::Row}; /// Model that represents the database entities that belongs to the current schema. /// diff --git a/canyon_migrations/src/migrations/memory.rs b/canyon_migrations/src/migrations/memory.rs index 1ad6263a..113810b6 100644 --- a/canyon_migrations/src/migrations/memory.rs +++ b/canyon_migrations/src/migrations/memory.rs @@ -1,5 +1,6 @@ use crate::constants; -use canyon_crud::{crud::Transaction, DatabaseType, DatasourceConfig}; +use canyon_core::query::Transaction; +use canyon_crud::{DatabaseType, DatasourceConfig}; use regex::Regex; use std::collections::HashMap; use std::fs; diff --git a/canyon_migrations/src/migrations/processor.rs b/canyon_migrations/src/migrations/processor.rs index 9296689f..bd932045 100644 --- a/canyon_migrations/src/migrations/processor.rs +++ b/canyon_migrations/src/migrations/processor.rs @@ -1,13 +1,14 @@ //! File that contains all the datatypes and logic to perform the migrations //! over a target database use async_trait::async_trait; +use canyon_core::query::Transaction; use canyon_crud::DatabaseType; use regex::Regex; use std::collections::HashMap; use std::fmt::Debug; use std::ops::Not; -use crate::canyon_crud::{crud::Transaction, DatasourceConfig}; +use crate::canyon_crud::DatasourceConfig; use crate::constants::regex_patterns; use crate::save_migrations_query_to_execute; From f0275f4d5836638e808f4ed794abc25c06fa6fd4 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sat, 18 Jan 2025 12:12:48 +0100 Subject: [PATCH 016/155] fix: new DbConnection trait lifetime issues on the tiberius impl --- canyon_connection/src/db_connector.rs | 28 +++++++++++++-------------- canyon_core/src/query.rs | 4 ++-- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/canyon_connection/src/db_connector.rs b/canyon_connection/src/db_connector.rs index 81b0b743..b1a4623e 100644 --- a/canyon_connection/src/db_connector.rs +++ b/canyon_connection/src/db_connector.rs @@ -256,10 +256,10 @@ mod postgres_query_launcher { use super::*; #[async_trait] impl DbConnection for PostgreSqlConnection { - async fn launch( + async fn launch<'a>( &self, stmt: &str, - params: &[&dyn QueryParameter<'_>], + params: &[&'a dyn QueryParameter<'a>], ) -> Result> { let mut m_params = Vec::new(); for param in params { @@ -282,10 +282,10 @@ mod sqlserver_query_launcher { #[async_trait] impl DbConnection for SqlServerConnection { - async fn launch( + async fn launch<'a>( &self, stmt: &str, - params: &[&dyn QueryParameter<'_>], + params: &[&'a dyn QueryParameter<'a>], ) -> Result> { // Re-generate de insert statement to adequate it to the SQL SERVER syntax to retrieve the PK value(s) after insert // TODO: redo this branch into the generated queries, before the MACROS @@ -306,14 +306,10 @@ mod sqlserver_query_launcher { // replace below. We may use our own type Query to address this concerns when the query // is generated let mut mssql_query = Query::new(stmt.to_owned().replace('$', "@P")); - // params - // .into_iter() - // .for_each(|param| mssql_query.bind(*param)); + params + .iter() + .for_each(|param| mssql_query.bind(*param)); - for param in params.clone() { - let p = param.clone(); - mssql_query.bind(p) - } #[allow(mutable_transmutes)] let sqlservconn = unsafe { std::mem::transmute::<&SqlServerConnection, &mut SqlServerConnection>(self) @@ -357,14 +353,14 @@ mod mysql_query_launcher { #[async_trait] impl DbConnection for MysqlConnection { - async fn launch( + async fn launch<'a>( &self, stmt: &str, - params: &[&dyn QueryParameter<'_>], + params: &[&'a dyn QueryParameter<'a>], ) -> Result> { let mysql_connection = self.client.get_conn().await?; - let stmt_with_escape_characters = regex::escape(&stmt); + let stmt_with_escape_characters = regex::escape(stmt); let query_string = Regex::new(DETECT_PARAMS_IN_QUERY)?.replace_all(&stmt_with_escape_characters, "?"); @@ -373,13 +369,15 @@ mod mysql_query_launcher { .to_string(); let mut is_insert = false; + // TODO: take care of this ugly replace for the concrete client syntax by using canyon + // Query if let Some(index_start_clausule_returning) = query_string.find(" RETURNING") { query_string.truncate(index_start_clausule_returning); is_insert = true; } let params_query: Vec = - reorder_params(&stmt, params, |f| (*f).as_mysql_param().to_value()); + reorder_params(stmt, params, |f| (*f).as_mysql_param().to_value()); let query_with_params = QueryWithParams { query: query_string, diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index b350b4e4..1fca0199 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -6,10 +6,10 @@ use crate::{query_parameters::QueryParameter, rows::CanyonRows}; #[async_trait] pub trait DbConnection { - async fn launch( + async fn launch<'a>( &self, stmt: &str, - params: &[&dyn QueryParameter<'_>], + params: &[&'a dyn QueryParameter<'a>], ) -> Result>; } From ed3b057c90731754be7e2a133ccdf0084654eb49 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 19 Jan 2025 01:39:01 +0100 Subject: [PATCH 017/155] chore: cleaning unnedeed lifetimes. Refactored the Transaction impls --- canyon_connection/src/db_connector.rs | 141 +++++++++--------- canyon_core/src/query.rs | 8 +- canyon_core/src/query_parameters.rs | 74 ++++----- canyon_core/src/rows.rs | 2 - canyon_crud/src/bounds.rs | 1 + canyon_entities/src/entity.rs | 2 +- canyon_macros/src/lib.rs | 16 +- canyon_macros/src/query_operations/delete.rs | 6 +- canyon_macros/src/query_operations/insert.rs | 28 ++-- canyon_macros/src/query_operations/select.rs | 42 +++--- canyon_macros/src/query_operations/update.rs | 8 +- canyon_migrations/src/migrations/handler.rs | 15 +- .../src/migrations/information_schema.rs | 2 +- canyon_migrations/src/migrations/memory.rs | 16 +- canyon_migrations/src/migrations/processor.rs | 8 +- src/lib.rs | 5 +- tests/migrations/mod.rs | 2 +- 17 files changed, 199 insertions(+), 177 deletions(-) diff --git a/canyon_connection/src/db_connector.rs b/canyon_connection/src/db_connector.rs index b1a4623e..01bccce9 100644 --- a/canyon_connection/src/db_connector.rs +++ b/canyon_connection/src/db_connector.rs @@ -1,5 +1,3 @@ -use std::fmt::Display; - #[cfg(feature = "mssql")] use async_std::net::TcpStream; #[cfg(feature = "mysql")] @@ -11,7 +9,7 @@ use tokio_postgres::{Client, NoTls}; use crate::database_type::DatabaseType; use crate::datasources::DatasourceConfig; -use canyon_core::query::{DbConnection, Transaction}; +use canyon_core::query::DbConnection; use canyon_core::query_parameters::QueryParameter; use canyon_core::rows::CanyonRows; @@ -50,25 +48,35 @@ pub enum DatabaseConnection { MySQL(MysqlConnection), } -unsafe impl Send for DatabaseConnection {} -unsafe impl Sync for DatabaseConnection {} - #[async_trait] -impl Transaction for DatabaseConnection { - async fn query<'a, C, S, Z>( - stmt: S, - params: Z, - db_conn: &C, - ) -> Result> - where - S: AsRef + std::fmt::Display + Sync + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, - C: DbConnection + Display + Sync + Send + 'a, - { - db_conn.launch(stmt.as_ref(), params.as_ref()).await +impl DbConnection for DatabaseConnection { + async fn launch<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + match self { + #[cfg(feature = "postgres")] + DatabaseConnection::Postgres(client) => { + client.launch(stmt, params).await + } + + #[cfg(feature = "mssql")] + DatabaseConnection::SqlServer(client) => { + client.launch(stmt, params).await + } + + #[cfg(feature = "mysql")] + DatabaseConnection::MySQL(client) => { + client.launch(stmt, params).await + } + } } } +unsafe impl Send for DatabaseConnection {} +unsafe impl Sync for DatabaseConnection {} + impl DatabaseConnection { pub async fn new( datasource: &DatasourceConfig, @@ -306,9 +314,7 @@ mod sqlserver_query_launcher { // replace below. We may use our own type Query to address this concerns when the query // is generated let mut mssql_query = Query::new(stmt.to_owned().replace('$', "@P")); - params - .iter() - .for_each(|param| mssql_query.bind(*param)); + params.iter().for_each(|param| mssql_query.bind(*param)); #[allow(mutable_transmutes)] let sqlservconn = unsafe { @@ -358,56 +364,57 @@ mod mysql_query_launcher { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result> { - let mysql_connection = self.client.get_conn().await?; - - let stmt_with_escape_characters = regex::escape(stmt); - let query_string = - Regex::new(DETECT_PARAMS_IN_QUERY)?.replace_all(&stmt_with_escape_characters, "?"); - - let mut query_string = Regex::new(DETECT_QUOTE_IN_QUERY)? - .replace_all(&query_string, "") - .to_string(); - - let mut is_insert = false; - // TODO: take care of this ugly replace for the concrete client syntax by using canyon - // Query - if let Some(index_start_clausule_returning) = query_string.find(" RETURNING") { - query_string.truncate(index_start_clausule_returning); - is_insert = true; - } + let mysql_connection = self.client.get_conn().await?; + + let stmt_with_escape_characters = regex::escape(stmt); + let query_string = + Regex::new(DETECT_PARAMS_IN_QUERY)?.replace_all(&stmt_with_escape_characters, "?"); + + let mut query_string = Regex::new(DETECT_QUOTE_IN_QUERY)? + .replace_all(&query_string, "") + .to_string(); + + let mut is_insert = false; + // TODO: take care of this ugly replace for the concrete client syntax by using canyon + // Query + if let Some(index_start_clausule_returning) = query_string.find(" RETURNING") { + query_string.truncate(index_start_clausule_returning); + is_insert = true; + } - let params_query: Vec = - reorder_params(stmt, params, |f| (*f).as_mysql_param().to_value()); + let params_query: Vec = + reorder_params(stmt, params, |f| (*f).as_mysql_param().to_value()); - let query_with_params = QueryWithParams { - query: query_string, - params: params_query, - }; + let query_with_params = QueryWithParams { + query: query_string, + params: params_query, + }; - let mut query_result = query_with_params - .run(mysql_connection) - .await - .expect("Error executing query in mysql"); - - let result_rows = if is_insert { - let last_insert = query_result - .last_insert_id() - .map(Value::UInt) - .expect("Error getting pk id in insert"); - - vec![row::new_row( - vec![last_insert], - Arc::new([mysql_async::Column::new(ColumnType::MYSQL_TYPE_UNKNOWN)]), - )] - } else { - query_result - .collect::() + let mut query_result = query_with_params + .run(mysql_connection) .await - .expect("Error resolved trait FromRow in mysql") - }; - let a = CanyonRows::MySQL(result_rows); - Ok(a) - } } + .expect("Error executing query in mysql"); + + let result_rows = if is_insert { + let last_insert = query_result + .last_insert_id() + .map(Value::UInt) + .expect("Error getting pk id in insert"); + + vec![row::new_row( + vec![last_insert], + Arc::new([mysql_async::Column::new(ColumnType::MYSQL_TYPE_UNKNOWN)]), + )] + } else { + query_result + .collect::() + .await + .expect("Error resolved trait FromRow in mysql") + }; + let a = CanyonRows::MySQL(result_rows); + Ok(a) + } + } #[cfg(feature = "mysql")] fn reorder_params( diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index 1fca0199..fe068dfc 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -20,17 +20,15 @@ pub trait Transaction { /// the defaulted datasource, wrapping the resultant collection of entities /// in [`super::rows::CanyonRows`] async fn query<'a, C, S, Z>( - // &self, stmt: S, params: Z, - db_conn: &C, + db_conn: &mut C, ) -> Result> where S: AsRef + Display + Sync + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, - C: DbConnection + Display + Sync + Send + 'a, + C: DbConnection + Sync + Send + 'a, { - // Ok(CanyonRows::Postgres(vec![])) - todo!() + db_conn.launch(stmt.as_ref(), params.as_ref()).await } } diff --git a/canyon_core/src/query_parameters.rs b/canyon_core/src/query_parameters.rs index fedd24c5..c38ce3f4 100644 --- a/canyon_core/src/query_parameters.rs +++ b/canyon_core/src/query_parameters.rs @@ -36,7 +36,7 @@ impl<'a> IntoSql<'a> for &'a dyn QueryParameter<'a> { //TODO Pending to review and see if it is necessary to apply something similar to the previous implementation. -impl<'a> QueryParameter<'a> for bool { +impl QueryParameter<'_> for bool { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -51,7 +51,7 @@ impl<'a> QueryParameter<'a> for bool { } } -impl<'a> QueryParameter<'a> for i16 { +impl QueryParameter<'_> for i16 { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -66,7 +66,7 @@ impl<'a> QueryParameter<'a> for i16 { } } -impl<'a> QueryParameter<'a> for &i16 { +impl QueryParameter<'_> for &i16 { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -81,7 +81,7 @@ impl<'a> QueryParameter<'a> for &i16 { } } -impl<'a> QueryParameter<'a> for Option { +impl QueryParameter<'_> for Option { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -96,7 +96,7 @@ impl<'a> QueryParameter<'a> for Option { } } -impl<'a> QueryParameter<'a> for Option<&i16> { +impl QueryParameter<'_> for Option<&i16> { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -111,7 +111,7 @@ impl<'a> QueryParameter<'a> for Option<&i16> { } } -impl<'a> QueryParameter<'a> for i32 { +impl QueryParameter<'_> for i32 { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -126,7 +126,7 @@ impl<'a> QueryParameter<'a> for i32 { } } -impl<'a> QueryParameter<'a> for &i32 { +impl QueryParameter<'_> for &i32 { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -141,7 +141,7 @@ impl<'a> QueryParameter<'a> for &i32 { } } -impl<'a> QueryParameter<'a> for Option { +impl QueryParameter<'_> for Option { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -156,7 +156,7 @@ impl<'a> QueryParameter<'a> for Option { } } -impl<'a> QueryParameter<'a> for Option<&i32> { +impl QueryParameter<'_> for Option<&i32> { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -171,7 +171,7 @@ impl<'a> QueryParameter<'a> for Option<&i32> { } } -impl<'a> QueryParameter<'a> for f32 { +impl QueryParameter<'_> for f32 { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -186,7 +186,7 @@ impl<'a> QueryParameter<'a> for f32 { } } -impl<'a> QueryParameter<'a> for &f32 { +impl QueryParameter<'_> for &f32 { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -201,7 +201,7 @@ impl<'a> QueryParameter<'a> for &f32 { } } -impl<'a> QueryParameter<'a> for Option { +impl QueryParameter<'_> for Option { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -216,7 +216,7 @@ impl<'a> QueryParameter<'a> for Option { } } -impl<'a> QueryParameter<'a> for Option<&f32> { +impl QueryParameter<'_> for Option<&f32> { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -233,7 +233,7 @@ impl<'a> QueryParameter<'a> for Option<&f32> { } } -impl<'a> QueryParameter<'a> for f64 { +impl QueryParameter<'_> for f64 { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -248,7 +248,7 @@ impl<'a> QueryParameter<'a> for f64 { } } -impl<'a> QueryParameter<'a> for &f64 { +impl QueryParameter<'_> for &f64 { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -263,7 +263,7 @@ impl<'a> QueryParameter<'a> for &f64 { } } -impl<'a> QueryParameter<'a> for Option { +impl QueryParameter<'_> for Option { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -278,7 +278,7 @@ impl<'a> QueryParameter<'a> for Option { } } -impl<'a> QueryParameter<'a> for Option<&f64> { +impl QueryParameter<'_> for Option<&f64> { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -295,7 +295,7 @@ impl<'a> QueryParameter<'a> for Option<&f64> { } } -impl<'a> QueryParameter<'a> for i64 { +impl QueryParameter<'_> for i64 { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -310,7 +310,7 @@ impl<'a> QueryParameter<'a> for i64 { } } -impl<'a> QueryParameter<'a> for &i64 { +impl QueryParameter<'_> for &i64 { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -325,7 +325,7 @@ impl<'a> QueryParameter<'a> for &i64 { } } -impl<'a> QueryParameter<'a> for Option { +impl QueryParameter<'_> for Option { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -340,7 +340,7 @@ impl<'a> QueryParameter<'a> for Option { } } -impl<'a> QueryParameter<'a> for Option<&i64> { +impl QueryParameter<'_> for Option<&i64> { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -355,7 +355,7 @@ impl<'a> QueryParameter<'a> for Option<&i64> { } } -impl<'a> QueryParameter<'a> for String { +impl QueryParameter<'_> for String { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -370,7 +370,7 @@ impl<'a> QueryParameter<'a> for String { } } -impl<'a> QueryParameter<'a> for &String { +impl QueryParameter<'_> for &String { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -385,7 +385,7 @@ impl<'a> QueryParameter<'a> for &String { } } -impl<'a> QueryParameter<'a> for Option { +impl QueryParameter<'_> for Option { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -403,7 +403,7 @@ impl<'a> QueryParameter<'a> for Option { } } -impl<'a> QueryParameter<'a> for Option<&String> { +impl QueryParameter<'_> for Option<&String> { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -421,7 +421,7 @@ impl<'a> QueryParameter<'a> for Option<&String> { } } -impl<'a> QueryParameter<'a> for &'_ str { +impl QueryParameter<'_> for &'_ str { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -436,7 +436,7 @@ impl<'a> QueryParameter<'a> for &'_ str { } } -impl<'a> QueryParameter<'a> for Option<&'_ str> { +impl QueryParameter<'_> for Option<&'_ str> { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -454,7 +454,7 @@ impl<'a> QueryParameter<'a> for Option<&'_ str> { } } -impl<'a> QueryParameter<'a> for NaiveDate { +impl QueryParameter<'_> for NaiveDate { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -469,7 +469,7 @@ impl<'a> QueryParameter<'a> for NaiveDate { } } -impl<'a> QueryParameter<'a> for Option { +impl QueryParameter<'_> for Option { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -484,7 +484,7 @@ impl<'a> QueryParameter<'a> for Option { } } -impl<'a> QueryParameter<'a> for NaiveTime { +impl QueryParameter<'_> for NaiveTime { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -499,7 +499,7 @@ impl<'a> QueryParameter<'a> for NaiveTime { } } -impl<'a> QueryParameter<'a> for Option { +impl QueryParameter<'_> for Option { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -514,7 +514,7 @@ impl<'a> QueryParameter<'a> for Option { } } -impl<'a> QueryParameter<'a> for NaiveDateTime { +impl QueryParameter<'_> for NaiveDateTime { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -529,7 +529,7 @@ impl<'a> QueryParameter<'a> for NaiveDateTime { } } -impl<'a> QueryParameter<'a> for Option { +impl QueryParameter<'_> for Option { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -545,7 +545,7 @@ impl<'a> QueryParameter<'a> for Option { } //TODO pending -impl<'a> QueryParameter<'a> for DateTime { +impl QueryParameter<'_> for DateTime { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -560,7 +560,7 @@ impl<'a> QueryParameter<'a> for DateTime { } } -impl<'a> QueryParameter<'a> for Option> { +impl QueryParameter<'_> for Option> { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -575,7 +575,7 @@ impl<'a> QueryParameter<'a> for Option> { } } -impl<'a> QueryParameter<'a> for DateTime { +impl QueryParameter<'_> for DateTime { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -590,7 +590,7 @@ impl<'a> QueryParameter<'a> for DateTime { } } -impl<'a> QueryParameter<'a> for Option> { +impl QueryParameter<'_> for Option> { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self diff --git a/canyon_core/src/rows.rs b/canyon_core/src/rows.rs index e52c8f19..ca36476a 100644 --- a/canyon_core/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -68,7 +68,6 @@ impl CanyonRows { Self::Tiberius(v) => v.len(), #[cfg(feature = "mysql")] Self::MySQL(v) => v.len(), - _ => panic!("This branch will never ever should be reachable") } } @@ -81,7 +80,6 @@ impl CanyonRows { Self::Tiberius(v) => v.is_empty(), #[cfg(feature = "mysql")] Self::MySQL(v) => v.is_empty(), - _ => panic!("This branch will never ever should be reachable") } } } diff --git a/canyon_crud/src/bounds.rs b/canyon_crud/src/bounds.rs index 758e0ce0..08be5e25 100644 --- a/canyon_crud/src/bounds.rs +++ b/canyon_crud/src/bounds.rs @@ -26,6 +26,7 @@ use crate::crud::CrudOperations; /// `let struct_field_name_from_variant = StructField::some_field.field_name_as_str();` pub trait FieldIdentifier where + // TODO: maybe just QueryParameter? T: Transaction + CrudOperations + RowMapper, { fn as_str(&self) -> &'static str; diff --git a/canyon_entities/src/entity.rs b/canyon_entities/src/entity.rs index 8604d0e8..fa8834a5 100644 --- a/canyon_entities/src/entity.rs +++ b/canyon_entities/src/entity.rs @@ -50,7 +50,7 @@ impl CanyonEntity { .iter() .map(|f| { let field_name = &f.name; - quote! { #field_name(&'a dyn canyon_sql::crud::bounds::QueryParameter<'a>) } + quote! { #field_name(&'a dyn canyon_sql::core::QueryParameter<'a>) } }) .collect::>() } diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index bd9cff0f..03da45b5 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -128,7 +128,7 @@ pub fn querybuilder_fields(input: CompilerTokenStream) -> CompilerTokenStream { let _generated_enum_type_for_fields = generate_enum_with_fields(&entity); let _generated_enum_type_for_fields_values = generate_enum_with_fields_values(&entity); quote! { - use canyon_sql::crud::bounds::QueryParameter; + use canyon_sql::core::QueryParameter; #_generated_enum_type_for_fields #_generated_enum_type_for_fields_values } @@ -335,7 +335,7 @@ fn impl_crud_operations_trait_for_struct( #crud_operations_tokens } - impl canyon_sql::crud::Transaction<#ty> for #ty {} + impl canyon_sql::core::Transaction<#ty> for #ty {} /// Hidden trait for generate the foreign key operations available /// in Canyon without have to define them before hand in CrudOperations @@ -352,7 +352,7 @@ fn impl_crud_operations_trait_for_struct( where #ty: std::fmt::Debug + canyon_sql::crud::CrudOperations<#ty> + - canyon_sql::crud::RowMapper<#ty> + canyon_sql::core::RowMapper<#ty> { #(#fk_method_implementations)* #(#rev_fk_method_implementations)* @@ -365,7 +365,7 @@ fn impl_crud_operations_trait_for_struct( #crud_operations_tokens } - impl canyon_sql::crud::Transaction<#ty> for #ty {} + impl canyon_sql::core::Transaction<#ty> for #ty {} } }; @@ -398,7 +398,7 @@ pub fn implement_foreignkeyable_for_type( let field_idents = fields.iter().map(|(_vis, ident)| { let i = ident.to_string(); quote! { - #i => Some(&self.#ident as &dyn canyon_sql::crud::bounds::QueryParameter<'_>) + #i => Some(&self.#ident as &dyn canyon_sql::core::QueryParameter<'_>) } }); let field_idents_cloned = field_idents.clone(); @@ -407,7 +407,7 @@ pub fn implement_foreignkeyable_for_type( /// Implementation of the trait `ForeignKeyable` for the type /// calling this derive proc macro impl canyon_sql::crud::bounds::ForeignKeyable for #ty { - fn get_fk_column(&self, column: &str) -> Option<&dyn canyon_sql::crud::bounds::QueryParameter<'_>> { + fn get_fk_column(&self, column: &str) -> Option<&dyn canyon_sql::core::QueryParameter<'_>> { match column { #(#field_idents),*, _ => None @@ -417,7 +417,7 @@ pub fn implement_foreignkeyable_for_type( /// Implementation of the trait `ForeignKeyable` for a reference of this type /// calling this derive proc macro impl canyon_sql::crud::bounds::ForeignKeyable<&Self> for &#ty { - fn get_fk_column<'a>(&self, column: &'a str) -> Option<&dyn canyon_sql::crud::bounds::QueryParameter<'_>> { + fn get_fk_column<'a>(&self, column: &'a str) -> Option<&dyn canyon_sql::core::QueryParameter<'_>> { match column { #(#field_idents_cloned),*, _ => None @@ -578,7 +578,7 @@ pub fn implement_row_mapper_for_type(input: proc_macro::TokenStream) -> proc_mac // Wrap everything in the shared `impl` block let tokens = quote! { - impl canyon_sql::crud::RowMapper for #ty { + impl canyon_sql::core::RowMapper for #ty { #impl_methods } }; diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index cabfa37f..a31dcfb6 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -19,14 +19,14 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri "Something really bad happened finding the Ident for the pk field on the delete", ); let pk_field_value = - quote! { &self.#pk_field as &dyn canyon_sql::crud::bounds::QueryParameter<'_> }; + quote! { &self.#pk_field as &dyn canyon_sql::core::QueryParameter<'_> }; quote! { /// Deletes from a database entity the row that matches /// the current instance of a T type, returning a result /// indicating a possible failure querying the database. async fn delete(&self) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'static)>> { - <#ty as canyon_sql::crud::Transaction<#ty>>::query( + <#ty as canyon_sql::core::Transaction<#ty>>::query( format!("DELETE FROM {} WHERE {:?} = $1", #table_schema_data, #primary_key), &[#pk_field_value], "" @@ -41,7 +41,7 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri async fn delete_datasource<'a>(&self, datasource_name: &'a str) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'static)>> { - <#ty as canyon_sql::crud::Transaction<#ty>>::query( + <#ty as canyon_sql::core::Transaction<#ty>>::query( format!("DELETE FROM {} WHERE {:?} = $1", #table_schema_data, #primary_key), &[#pk_field_value], datasource_name diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index c6e5e205..a3d03311 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -49,7 +49,7 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri #primary_key ); - let rows = <#ty as canyon_sql::crud::Transaction<#ty>>::query( + let rows = <#ty as canyon_sql::core::Transaction<#ty>>::query( stmt, values, datasource_name @@ -57,7 +57,7 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri match rows { #[cfg(feature = "postgres")] - canyon_sql::crud::CanyonRows::Postgres(mut v) => { + canyon_sql::core::CanyonRows::Postgres(mut v) => { self.#pk_ident = v .get(0) .ok_or("Failed getting the returned IDs for an insert")? @@ -65,7 +65,7 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri Ok(()) }, #[cfg(feature = "mssql")] - canyon_sql::crud::CanyonRows::Tiberius(mut v) => { + canyon_sql::core::CanyonRows::Tiberius(mut v) => { self.#pk_ident = v .get(0) .ok_or("Failed getting the returned IDs for a multi insert")? @@ -74,7 +74,7 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri Ok(()) }, #[cfg(feature = "mysql")] - canyon_sql::crud::CanyonRows::MySQL(mut v) => { + canyon_sql::core::CanyonRows::MySQL(mut v) => { self.#pk_ident = v .get(0) .ok_or("Failed getting the returned IDs for a multi insert")? @@ -95,7 +95,7 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri #primary_key ); - <#ty as canyon_sql::crud::Transaction<#ty>>::query( + <#ty as canyon_sql::core::Transaction<#ty>>::query( stmt, values, datasource_name @@ -148,7 +148,7 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri -> Result<(), Box> { let datasource_name = ""; - let mut values: Vec<&dyn canyon_sql::crud::bounds::QueryParameter<'_>> = vec![#(#insert_values),*]; + let mut values: Vec<&dyn canyon_sql::core::QueryParameter<'_>> = vec![#(#insert_values),*]; #insert_transaction } @@ -193,7 +193,7 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri async fn insert_datasource<'a>(&mut self, datasource_name: &'a str) -> Result<(), Box> { - let mut values: Vec<&dyn canyon_sql::crud::bounds::QueryParameter<'_>> = vec![#(#insert_values_cloned),*]; + let mut values: Vec<&dyn canyon_sql::core::QueryParameter<'_>> = vec![#(#insert_values_cloned),*]; #insert_transaction } @@ -294,7 +294,7 @@ pub fn generate_multiple_insert_tokens( } } - let multi_insert_result = <#ty as canyon_sql::crud::Transaction<#ty>>::query( + let multi_insert_result = <#ty as canyon_sql::core::Transaction<#ty>>::query( stmt, v_arr, datasource_name @@ -302,7 +302,7 @@ pub fn generate_multiple_insert_tokens( match multi_insert_result { #[cfg(feature="postgres")] - canyon_sql::crud::CanyonRows::Postgres(mut v) => { + canyon_sql::core::CanyonRows::Postgres(mut v) => { for (idx, instance) in instances.iter_mut().enumerate() { instance.#pk_ident = v .get(idx) @@ -313,7 +313,7 @@ pub fn generate_multiple_insert_tokens( Ok(()) }, #[cfg(feature="mssql")] - canyon_sql::crud::CanyonRows::Tiberius(mut v) => { + canyon_sql::core::CanyonRows::Tiberius(mut v) => { for (idx, instance) in instances.iter_mut().enumerate() { instance.#pk_ident = v .get(idx) @@ -325,7 +325,7 @@ pub fn generate_multiple_insert_tokens( Ok(()) }, #[cfg(feature="mysql")] - canyon_sql::crud::CanyonRows::MySQL(mut v) => { + canyon_sql::core::CanyonRows::MySQL(mut v) => { for (idx, instance) in instances.iter_mut().enumerate() { instance.#pk_ident = v .get(idx) @@ -393,7 +393,7 @@ pub fn generate_multiple_insert_tokens( } } - <#ty as canyon_sql::crud::Transaction<#ty>>::query( + <#ty as canyon_sql::core::Transaction<#ty>>::query( stmt, v_arr, datasource_name @@ -440,7 +440,7 @@ pub fn generate_multiple_insert_tokens( async fn multi_insert<'a>(instances: &'a mut [&'a mut #ty]) -> ( Result<(), Box> ) { - use canyon_sql::crud::bounds::QueryParameter; + use canyon_sql::core::QueryParameter; let datasource_name = ""; let mut final_values: Vec>> = Vec::new(); @@ -497,7 +497,7 @@ pub fn generate_multiple_insert_tokens( async fn multi_insert_datasource<'a>(instances: &'a mut [&'a mut #ty], datasource_name: &'a str) -> ( Result<(), Box> ) { - use canyon_sql::crud::bounds::QueryParameter; + use canyon_sql::core::QueryParameter; let mut final_values: Vec>> = Vec::new(); for instance in instances.iter() { diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index 82a1a5b5..796091bd 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -21,7 +21,7 @@ pub fn generate_find_all_unchecked_tokens( /// database convention. P.ej. PostgreSQL prefers table names declared /// with snake_case identifiers. async fn find_all_unchecked<'a>() -> Vec<#ty> { - <#ty as canyon_sql::crud::Transaction<#ty>>::query( + <#ty as canyon_sql::core::Transaction<#ty>>::query( #stmt, &[], "" @@ -39,7 +39,7 @@ pub fn generate_find_all_unchecked_tokens( /// described in the configuration file, and selected with the [`&str`] /// passed as parameter. async fn find_all_unchecked_datasource<'a>(datasource_name: &'a str) -> Vec<#ty> { - <#ty as canyon_sql::crud::Transaction<#ty>>::query( + <#ty as canyon_sql::core::Transaction<#ty>>::query( #stmt, &[], datasource_name @@ -68,7 +68,7 @@ pub fn generate_find_all_tokens( Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { Ok( - <#ty as canyon_sql::crud::Transaction<#ty>>::query( + <#ty as canyon_sql::core::Transaction<#ty>>::query( #stmt, &[], "" @@ -93,7 +93,7 @@ pub fn generate_find_all_tokens( Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { Ok( - <#ty as canyon_sql::crud::Transaction<#ty>>::query( + <#ty as canyon_sql::core::Transaction<#ty>>::query( #stmt, &[], datasource_name @@ -152,18 +152,18 @@ pub fn generate_count_tokens( let result_handling = quote! { #[cfg(feature="postgres")] - canyon_sql::crud::CanyonRows::Postgres(mut v) => Ok( + canyon_sql::core::CanyonRows::Postgres(mut v) => Ok( v.remove(0).get::<&str, i64>("count") ), #[cfg(feature="mssql")] - canyon_sql::crud::CanyonRows::Tiberius(mut v) => + canyon_sql::core::CanyonRows::Tiberius(mut v) => v.remove(0) .get::(0) .map(|c| c as i64) .ok_or(format!("Failure in the COUNT query for MSSQL for: {}", #ty_str).into()) .into(), #[cfg(feature="mysql")] - canyon_sql::crud::CanyonRows::MySQL(mut v) => v.remove(0) + canyon_sql::core::CanyonRows::MySQL(mut v) => v.remove(0) .get::(0) .ok_or(format!("Failure in the COUNT query for MYSQL for: {}", #ty_str).into()), _ => panic!() // TODO remove when the generics will be refactored @@ -173,7 +173,7 @@ pub fn generate_count_tokens( /// Performs a COUNT(*) query over some table, returning a [`Result`] rather than panicking, /// wrapping a possible success or error coming from the database async fn count() -> Result> { - let count = <#ty as canyon_sql::crud::Transaction<#ty>>::query( + let count = <#ty as canyon_sql::core::Transaction<#ty>>::query( #stmt, &[], "" @@ -187,7 +187,7 @@ pub fn generate_count_tokens( /// Performs a COUNT(*) query over some table, returning a [`Result`] rather than panicking, /// wrapping a possible success or error coming from the database with the specified datasource async fn count_datasource<'a>(datasource_name: &'a str) -> Result> { - let count = <#ty as canyon_sql::crud::Transaction<#ty>>::query( + let count = <#ty as canyon_sql::core::Transaction<#ty>>::query( #stmt, &[], datasource_name @@ -212,7 +212,7 @@ pub fn generate_find_by_pk_tokens( // Disabled if there's no `primary_key` annotation if pk.is_empty() { return quote! { - async fn find_by_pk<'a>(value: &'a dyn canyon_sql::crud::bounds::QueryParameter<'a>) + async fn find_by_pk<'a>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { Err( @@ -226,7 +226,7 @@ pub fn generate_find_by_pk_tokens( } async fn find_by_pk_datasource<'a>( - value: &'a dyn canyon_sql::crud::bounds::QueryParameter<'a>, + value: &'a dyn canyon_sql::core::QueryParameter<'a>, datasource_name: &'a str ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { Err( @@ -263,10 +263,10 @@ pub fn generate_find_by_pk_tokens( /// querying the database, or, if no errors happens, a success containing /// and Option with the data found wrapped in the Some(T) variant, /// or None if the value isn't found on the table. - async fn find_by_pk<'a>(value: &'a dyn canyon_sql::crud::bounds::QueryParameter<'a>) -> + async fn find_by_pk<'a>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { - let result = <#ty as canyon_sql::crud::Transaction<#ty>>::query( + let result = <#ty as canyon_sql::core::Transaction<#ty>>::query( #stmt, vec![value], "" @@ -292,11 +292,11 @@ pub fn generate_find_by_pk_tokens( /// and Option with the data found wrapped in the Some(T) variant, /// or None if the value isn't found on the table. async fn find_by_pk_datasource<'a>( - value: &'a dyn canyon_sql::crud::bounds::QueryParameter<'a>, + value: &'a dyn canyon_sql::core::QueryParameter<'a>, datasource_name: &'a str ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { - let result = <#ty as canyon_sql::crud::Transaction<#ty>>::query( + let result = <#ty as canyon_sql::core::Transaction<#ty>>::query( #stmt, vec![value], datasource_name @@ -360,9 +360,9 @@ pub fn generate_find_by_foreign_key_tokens( quote! { /// Searches the parent entity (if exists) for this type #quoted_method_signature { - let result = <#fk_ty as canyon_sql::crud::Transaction<#fk_ty>>::query( + let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( #stmt, - &[&self.#field_ident as &dyn canyon_sql::crud::bounds::QueryParameter<'_>], + &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], "" ).await?; @@ -376,9 +376,9 @@ pub fn generate_find_by_foreign_key_tokens( quote! { /// Searches the parent entity (if exists) for this type with the specified datasource #quoted_datasource_method_signature { - let result = <#fk_ty as canyon_sql::crud::Transaction<#fk_ty>>::query( + let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( #stmt, - &[&self.#field_ident as &dyn canyon_sql::crud::bounds::QueryParameter<'_>], + &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], datasource_name ).await?; @@ -445,7 +445,7 @@ pub fn generate_find_by_reverse_foreign_key_tokens( format!("\"{}\"", #f_ident).as_str() ); - Ok(<#ty as canyon_sql::crud::Transaction<#ty>>::query( + Ok(<#ty as canyon_sql::core::Transaction<#ty>>::query( stmt, &[lookage_value], "" @@ -473,7 +473,7 @@ pub fn generate_find_by_reverse_foreign_key_tokens( format!("\"{}\"", #f_ident).as_str() ); - Ok(<#ty as canyon_sql::crud::Transaction<#ty>>::query( + Ok(<#ty as canyon_sql::core::Transaction<#ty>>::query( stmt, &[lookage_value], datasource_name diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index 5837325a..fc775a4c 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -39,9 +39,9 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri "UPDATE {} SET {} WHERE {} = ${:?}", #table_schema_data, #str_columns_values, #primary_key, #pk_index + 1 ); - let update_values: &[&dyn canyon_sql::crud::bounds::QueryParameter<'_>] = &[#(#update_values),*]; + let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = &[#(#update_values),*]; - <#ty as canyon_sql::crud::Transaction<#ty>>::query( + <#ty as canyon_sql::core::Transaction<#ty>>::query( stmt, update_values, "" ).await?; @@ -60,9 +60,9 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri "UPDATE {} SET {} WHERE {} = ${:?}", #table_schema_data, #str_columns_values, #primary_key, #pk_index + 1 ); - let update_values: &[&dyn canyon_sql::crud::bounds::QueryParameter<'_>] = &[#(#update_values_cloned),*]; + let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = &[#(#update_values_cloned),*]; - <#ty as canyon_sql::crud::Transaction<#ty>>::query( + <#ty as canyon_sql::core::Transaction<#ty>>::query( stmt, update_values, datasource_name ).await?; diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index b045ba1d..26998223 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -1,5 +1,5 @@ -use canyon_connection::{datasources::Migrations as MigrationsStatus, DATASOURCES}; -use canyon_core::{column::Column, query::Transaction, row::Row, rows::CanyonRows}; +use canyon_connection::{datasources::Migrations as MigrationsStatus, db_connector::DatabaseConnection, DATASOURCES}; +use canyon_core::{column::Column, query::Transaction, row::{Row, RowOperations}, rows::CanyonRows}; use canyon_entities::CANYON_REGISTER_ENTITIES; use partialdebug::placeholder::PartialDebug; @@ -42,13 +42,15 @@ impl Migrations { ); let mut migrations_processor = MigrationsProcessor::default(); + let mut conn_cache = canyon_connection::CACHED_DATABASE_CONN.lock().await; + let db_conn = canyon_connection::get_database_connection(&datasource.name, &mut conn_cache); let canyon_entities = CANYON_REGISTER_ENTITIES.lock().unwrap().to_vec(); let canyon_memory = CanyonMemory::remember(datasource, &canyon_entities).await; // Tracked entities that must be migrated whenever Canyon starts let schema_status = - Self::fetch_database(&datasource.name, datasource.get_db_type()).await; + Self::fetch_database(&datasource.name, db_conn, datasource.get_db_type()).await; let database_tables_schema_info = Self::map_rows(schema_status, datasource.get_db_type()); @@ -82,7 +84,8 @@ impl Migrations { /// Fetches a concrete schema metadata by target the database /// chosen by it's datasource name property async fn fetch_database( - datasource_name: &str, + ds_name: &str, + db_conn: &mut DatabaseConnection, db_type: DatabaseType, ) -> CanyonRows { let query = match db_type { @@ -94,11 +97,11 @@ impl Migrations { DatabaseType::MySQL => todo!("Not implemented fetch database in mysql"), }; - Self::query(query, [], datasource_name) + Self::query(query, [], db_conn) .await .unwrap_or_else(|_| { panic!( - "Error querying the schema information for the datasource: {datasource_name}" + "Error querying the schema information for the datasource: {ds_name}" ) }) } diff --git a/canyon_migrations/src/migrations/information_schema.rs b/canyon_migrations/src/migrations/information_schema.rs index bb852073..a9d15b9b 100644 --- a/canyon_migrations/src/migrations/information_schema.rs +++ b/canyon_migrations/src/migrations/information_schema.rs @@ -2,7 +2,7 @@ use canyon_connection::tiberius::ColumnType as TIB_TY; #[cfg(feature = "postgres")] use canyon_connection::tokio_postgres::types::Type as TP_TYP; -use canyon_core::{column::{Column, ColumnType}, row::Row}; +use canyon_core::{column::{Column, ColumnType}, row::{Row, RowOperations}}; /// Model that represents the database entities that belongs to the current schema. /// diff --git a/canyon_migrations/src/migrations/memory.rs b/canyon_migrations/src/migrations/memory.rs index 113810b6..a6ea71b1 100644 --- a/canyon_migrations/src/migrations/memory.rs +++ b/canyon_migrations/src/migrations/memory.rs @@ -1,4 +1,5 @@ use crate::constants; +use canyon_connection::db_connector::DatabaseConnection; use canyon_core::query::Transaction; use canyon_crud::{DatabaseType, DatasourceConfig}; use regex::Regex; @@ -63,11 +64,16 @@ impl CanyonMemory { datasource: &DatasourceConfig, canyon_entities: &[CanyonRegisterEntity<'_>], ) -> Self { + // TODO: can't we get the target DS while in the migrations at call site and avoid to + // duplicate calls to the pool? + let mut conn_cache = canyon_connection::CACHED_DATABASE_CONN.lock().await; + let db_conn = canyon_connection::get_database_connection(&datasource.name, &mut conn_cache); + // Creates the memory table if not exists - Self::create_memory(&datasource.name, &datasource.get_db_type()).await; + Self::create_memory(&datasource.name, db_conn, &datasource.get_db_type()).await; // Retrieve the last status data from the `canyon_memory` table - let res = Self::query("SELECT * FROM canyon_memory", [], &datasource.name) + let res = Self::query("SELECT * FROM canyon_memory", [], db_conn) .await .expect("Error querying Canyon Memory"); @@ -241,7 +247,7 @@ impl CanyonMemory { } /// Generates, if not exists the `canyon_memory` table - async fn create_memory(datasource_name: &str, database_type: &DatabaseType) { + async fn create_memory(datasource_name: &str, db_conn: &mut DatabaseConnection, database_type: &DatabaseType) { let query = match database_type { #[cfg(feature = "postgres")] DatabaseType::PostgreSql => constants::postgresql_queries::CANYON_MEMORY_TABLE, @@ -251,9 +257,9 @@ impl CanyonMemory { DatabaseType::MySQL => todo!("Memory table in mysql not implemented"), }; - Self::query(query, [], datasource_name) + Self::query(query, [], db_conn) .await - .expect("Error creating the 'canyon_memory' table"); + .unwrap_or_else(|_| panic!("Error creating the 'canyon_memory' table while processing the datasource: {datasource_name}")); } } diff --git a/canyon_migrations/src/migrations/processor.rs b/canyon_migrations/src/migrations/processor.rs index bd932045..a4f15d87 100644 --- a/canyon_migrations/src/migrations/processor.rs +++ b/canyon_migrations/src/migrations/processor.rs @@ -576,7 +576,13 @@ impl MigrationsProcessor { pub async fn from_query_register(queries_to_execute: &HashMap<&str, Vec<&str>>) { for datasource in queries_to_execute.iter() { for query_to_execute in datasource.1 { - let res = Self::query(query_to_execute, [], datasource.0).await; + let datasource_name = datasource.0; + + let mut conn_cache = canyon_connection::CACHED_DATABASE_CONN.lock().await; + let db_conn = + canyon_connection::get_database_connection(datasource_name, &mut conn_cache); + + let res = Self::query(query_to_execute, [], db_conn).await; match res { Ok(_) => println!( diff --git a/src/lib.rs b/src/lib.rs index b3b6d0ef..51e61ce2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,11 @@ pub mod connection { } pub mod core { + pub use canyon_core::query::Transaction; + pub use canyon_core::query::DbConnection; + pub use canyon_core::query_parameters::QueryParameter; pub use canyon_core::rows::CanyonRows; + pub use canyon_core::mapper::*; } /// Crud module serves to reexport the public elements of the `canyon_crud` crate, @@ -48,7 +52,6 @@ pub mod core { pub mod crud { pub use canyon_crud::bounds; pub use canyon_crud::crud::*; - pub use canyon_crud::mapper::*; pub use canyon_crud::DatabaseType; } diff --git a/tests/migrations/mod.rs b/tests/migrations/mod.rs index b0fbed96..4e81959a 100644 --- a/tests/migrations/mod.rs +++ b/tests/migrations/mod.rs @@ -1,7 +1,7 @@ #![allow(unused_imports)] use crate::constants; /// Integration tests for the migrations feature of `Canyon-SQL` -use canyon_sql::crud::Transaction; +use canyon_sql::core::Transaction; #[cfg(feature = "migrations")] use canyon_sql::migrations::handler::Migrations; From 36b47e257639bdc5f0a9591453774a30f5b6fe2e Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 19 Jan 2025 01:44:18 +0100 Subject: [PATCH 018/155] fix: added the missed 'migrations' feature to the integration tests crate --- tests/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Cargo.toml b/tests/Cargo.toml index ef9ee7f0..16f4462e 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -14,4 +14,5 @@ path = "canyon_integration_tests.rs" [features] postgres = ["canyon_sql/postgres"] mssql = ["canyon_sql/mssql"] -mysql = ["canyon_sql/mysql"] \ No newline at end of file +mysql = ["canyon_sql/mysql"] +migrations = ["canyon_sql/migrations"] From c3c98d070fa2ec92fda45425d2e243f2ea5b701f Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 19 Jan 2025 12:05:12 +0100 Subject: [PATCH 019/155] feat: new standalone fn to retrieve a database connection without leaking the internal pool to the user code (yet leaked, will be fixed) --- canyon_connection/src/lib.rs | 26 +++++++++++++++++++++++++- src/lib.rs | 2 ++ tests/migrations/mod.rs | 6 +++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/canyon_connection/src/lib.rs b/canyon_connection/src/lib.rs index 5267dfce..8bd5e77b 100644 --- a/canyon_connection/src/lib.rs +++ b/canyon_connection/src/lib.rs @@ -18,7 +18,7 @@ pub mod db_connector; pub mod database_type; pub mod datasources; -use std::fs; +use std::{error::Error, fs}; use std::path::PathBuf; use crate::datasources::{CanyonSqlConfig, DatasourceConfig}; @@ -89,7 +89,31 @@ pub async fn init_connections_cache() { ); } } +pub async fn get_database_connection_by_ds<'a, T: AsRef> ( + datasource_name: Option, +) -> Result> { + + let datasource = if let Some(ds_name) = datasource_name { + let ds_identifier = ds_name.as_ref(); + DATASOURCES + .iter() + .find(|ds| ds.name.eq(ds_identifier)) + } else { + DATASOURCES + .first() + }; + + let conn = match datasource.ok_or_else(|| panic!("fix me later")) { + Ok(ds_cfg) => DatabaseConnection::new(ds_cfg), + Err(e) => todo!("{:?}", e), + }; + + conn.await +} +// TODO: get_cached_database_connection +// the idea behind this is that we can have a #cfg feature that offers the end user to let Canyon to automagically manage the connections +// to the db servers, instead of being the default behaviour pub fn get_database_connection<'a>( datasource_name: &str, guarded_cache: &'a mut MutexGuard>, diff --git a/src/lib.rs b/src/lib.rs index 51e61ce2..582be0ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,6 +37,8 @@ pub mod connection { #[cfg(feature = "mysql")] pub use canyon_connection::db_connector::DatabaseConnection::MySQL; + + pub use canyon_connection::*; } pub mod core { diff --git a/tests/migrations/mod.rs b/tests/migrations/mod.rs index 4e81959a..977f4686 100644 --- a/tests/migrations/mod.rs +++ b/tests/migrations/mod.rs @@ -9,7 +9,11 @@ use canyon_sql::migrations::handler::Migrations; #[cfg(all(feature = "postgres", feature = "migrations"))] #[canyon_sql::macros::canyon_tokio_test] fn test_migrations_postgresql_status_query() { - let results = Migrations::query(constants::FETCH_PUBLIC_SCHEMA, [], constants::PSQL_DS).await; + let conn_res = canyon_sql::connection::get_database_connection_by_ds(Some(constants::PSQL_DS)).await; + assert!(conn_res.is_ok()); + + let db_conn = &mut conn_res.unwrap(); + let results = Migrations::query(constants::FETCH_PUBLIC_SCHEMA, [], db_conn).await; assert!(results.is_ok()); let res = results.unwrap(); From a25ae0aa054f9db907691fd33d134ba89ee21e36 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 19 Jan 2025 13:15:17 +0100 Subject: [PATCH 020/155] feat: polishing the new impl for getting db conns --- canyon_connection/src/conn_errors.rs | 18 +++++++++++++ canyon_connection/src/lib.rs | 40 ++++++++++++++++------------ 2 files changed, 41 insertions(+), 17 deletions(-) create mode 100644 canyon_connection/src/conn_errors.rs diff --git a/canyon_connection/src/conn_errors.rs b/canyon_connection/src/conn_errors.rs new file mode 100644 index 00000000..5e7a5362 --- /dev/null +++ b/canyon_connection/src/conn_errors.rs @@ -0,0 +1,18 @@ +//! Defines the Canyon-SQL custom connection error types + +/// Raised when a [`crate::datasources::DatasourceConfig`] isn't found given a user input +#[derive(Debug, Clone)] +pub struct DatasourceNotFound + ?Sized + std::fmt::Debug + Default> { + pub datasource_name: T +} +impl + std::fmt::Debug + Default> From> for DatasourceNotFound { + fn from(value: Option) -> Self { + DatasourceNotFound { datasource_name: value.unwrap_or_default() } + } +} +impl + std::fmt::Debug + Default> std::fmt::Display for DatasourceNotFound { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Unable to found a datasource that matches: {:?}", self.datasource_name) + } +} +impl + std::fmt::Debug + Default> std::error::Error for DatasourceNotFound {} \ No newline at end of file diff --git a/canyon_connection/src/lib.rs b/canyon_connection/src/lib.rs index 8bd5e77b..9e1bb01d 100644 --- a/canyon_connection/src/lib.rs +++ b/canyon_connection/src/lib.rs @@ -17,11 +17,14 @@ pub extern crate tokio_util; pub mod db_connector; pub mod database_type; pub mod datasources; +pub mod conn_errors; +use std::fmt::Debug; use std::{error::Error, fs}; use std::path::PathBuf; use crate::datasources::{CanyonSqlConfig, DatasourceConfig}; +use conn_errors::DatasourceNotFound; use db_connector::DatabaseConnection; use indexmap::IndexMap; use lazy_static::lazy_static; @@ -89,28 +92,31 @@ pub async fn init_connections_cache() { ); } } -pub async fn get_database_connection_by_ds<'a, T: AsRef> ( + +// TODO: idea. Should we leak the datasources config pull to the user, so we can be more flexible and let the +// user code determine whenever you can find a valid datasource via a concrete type instead of an string? + +// TODO: doc (main way for the user to obtain a db connection given a datasource identifier) +pub async fn get_database_connection_by_ds<'a, T: AsRef + Copy + Debug + Default + Send + Sync + 'static> ( datasource_name: Option, -) -> Result> { - - let datasource = if let Some(ds_name) = datasource_name { - let ds_identifier = ds_name.as_ref(); - DATASOURCES - .iter() - .find(|ds| ds.name.eq(ds_identifier)) - } else { - DATASOURCES - .first() - }; +) -> Result> { - let conn = match datasource.ok_or_else(|| panic!("fix me later")) { - Ok(ds_cfg) => DatabaseConnection::new(ds_cfg), - Err(e) => todo!("{:?}", e), - }; + let ds = find_datasource_by_name_or_try_default(datasource_name)?; + DatabaseConnection::new(ds).await +} - conn.await +fn find_datasource_by_name_or_try_default<'a, T: AsRef + Copy + Debug + Default>(datasource_name: Option) -> Result<&'a DatasourceConfig, DatasourceNotFound> { + datasource_name + .map_or_else( + || DATASOURCES.first(), + |ds_name| DATASOURCES + .iter() + .find(|ds| ds.name.eq(ds_name.as_ref())) + ).ok_or_else(|| DatasourceNotFound::from(datasource_name)) } +// TODO: create a new one that just receives a str and tries to find the ds config on the vec, so we can make a facade over the one above + // TODO: get_cached_database_connection // the idea behind this is that we can have a #cfg feature that offers the end user to let Canyon to automagically manage the connections // to the db servers, instead of being the default behaviour From 5ffbe2460073ffab76e9eb912b44abd64fa3ab27 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 19 Jan 2025 22:11:51 +0100 Subject: [PATCH 021/155] feat: new modules for each available db client --- canyon_connection/src/conn_errors.rs | 14 +- canyon_connection/src/database_type.rs | 2 +- canyon_connection/src/db_clients/mod.rs | 6 + canyon_connection/src/db_clients/mssql.rs | 70 ++++++ canyon_connection/src/db_clients/mysql.rs | 129 ++++++++++ .../src/db_clients/postgresql.rs | 43 ++++ canyon_connection/src/db_connector.rs | 234 +----------------- canyon_connection/src/lib.rs | 26 +- canyon_core/src/lib.rs | 4 +- canyon_core/src/query.rs | 1 + canyon_core/src/row.rs | 1 - canyon_core/src/rows.rs | 6 +- canyon_crud/src/crud.rs | 4 +- canyon_crud/src/query_elements/query.rs | 2 +- .../src/query_elements/query_builder.rs | 12 +- canyon_macros/src/query_operations/select.rs | 4 +- canyon_migrations/src/migrations/handler.rs | 24 +- .../src/migrations/information_schema.rs | 5 +- canyon_migrations/src/migrations/memory.rs | 6 +- src/lib.rs | 6 +- tests/migrations/mod.rs | 3 +- 21 files changed, 332 insertions(+), 270 deletions(-) create mode 100644 canyon_connection/src/db_clients/mod.rs create mode 100644 canyon_connection/src/db_clients/mssql.rs create mode 100644 canyon_connection/src/db_clients/mysql.rs create mode 100644 canyon_connection/src/db_clients/postgresql.rs diff --git a/canyon_connection/src/conn_errors.rs b/canyon_connection/src/conn_errors.rs index 5e7a5362..a4f02c19 100644 --- a/canyon_connection/src/conn_errors.rs +++ b/canyon_connection/src/conn_errors.rs @@ -3,16 +3,22 @@ /// Raised when a [`crate::datasources::DatasourceConfig`] isn't found given a user input #[derive(Debug, Clone)] pub struct DatasourceNotFound + ?Sized + std::fmt::Debug + Default> { - pub datasource_name: T + pub datasource_name: T, } impl + std::fmt::Debug + Default> From> for DatasourceNotFound { fn from(value: Option) -> Self { - DatasourceNotFound { datasource_name: value.unwrap_or_default() } + DatasourceNotFound { + datasource_name: value.unwrap_or_default(), + } } } impl + std::fmt::Debug + Default> std::fmt::Display for DatasourceNotFound { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "Unable to found a datasource that matches: {:?}", self.datasource_name) + write!( + f, + "Unable to found a datasource that matches: {:?}", + self.datasource_name + ) } } -impl + std::fmt::Debug + Default> std::error::Error for DatasourceNotFound {} \ No newline at end of file +impl + std::fmt::Debug + Default> std::error::Error for DatasourceNotFound {} diff --git a/canyon_connection/src/database_type.rs b/canyon_connection/src/database_type.rs index 00153a28..a319d512 100644 --- a/canyon_connection/src/database_type.rs +++ b/canyon_connection/src/database_type.rs @@ -27,4 +27,4 @@ impl From<&Auth> for DatabaseType { crate::datasources::Auth::MySQL(_) => DatabaseType::MySQL, } } -} \ No newline at end of file +} diff --git a/canyon_connection/src/db_clients/mod.rs b/canyon_connection/src/db_clients/mod.rs new file mode 100644 index 00000000..366249d5 --- /dev/null +++ b/canyon_connection/src/db_clients/mod.rs @@ -0,0 +1,6 @@ +#[cfg(feature = "mssql")] +pub mod mssql; +#[cfg(feature = "mysql")] +pub mod mysql; +#[cfg(feature = "postgres")] +pub mod postgresql; diff --git a/canyon_connection/src/db_clients/mssql.rs b/canyon_connection/src/db_clients/mssql.rs new file mode 100644 index 00000000..e27d22f1 --- /dev/null +++ b/canyon_connection/src/db_clients/mssql.rs @@ -0,0 +1,70 @@ +#[cfg(feature = "mssql")] +use async_std::net::TcpStream; + +use async_trait::async_trait; +use canyon_core::{query::DbConnection, query_parameters::QueryParameter, rows::CanyonRows}; +use tiberius::Query; + +/// A connection with a `SqlServer` database +#[cfg(feature = "mssql")] +pub struct SqlServerConnection { + pub client: &'static mut tiberius::Client, +} + +#[async_trait] +impl DbConnection for SqlServerConnection { + async fn launch<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + sqlserver_query_launcher::launch(stmt, params, self).await + } +} + +#[cfg(feature = "mssql")] +pub(crate) mod sqlserver_query_launcher { + use super::*; + + #[inline(always)] + + pub(crate) async fn launch<'a>( + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + conn: &SqlServerConnection, + ) -> Result> { + // Re-generate de insert statement to adequate it to the SQL SERVER syntax to retrieve the PK value(s) after insert + // TODO: redo this branch into the generated queries, before the MACROS + // if stmt.contains("RETURNING") { + // let c = stmt.clone(); + // let temp = c.split_once("RETURNING").unwrap(); + // let temp2 = temp.0.split_once("VALUES").unwrap(); + // + // *stmt = format!( + // "{} OUTPUT inserted.{} VALUES {}", + // temp2.0.trim(), + // temp.1.trim(), + // temp2.1.trim() + // ); + // } + + // TODO: We must address the query generation. Look at the returning example, or the + // replace below. We may use our own type Query to address this concerns when the query + // is generated + let mut mssql_query = Query::new(stmt.to_owned().replace('$', "@P")); + params.iter().for_each(|param| mssql_query.bind(*param)); + + #[allow(mutable_transmutes)] // TODO: pls solve this elegantly someday :( + let sqlservconn = + unsafe { std::mem::transmute::<&SqlServerConnection, &mut SqlServerConnection>(conn) }; + let _results = mssql_query + .query(sqlservconn.client) + .await? + .into_results() + .await?; + + Ok(CanyonRows::Tiberius( + _results.into_iter().flatten().collect(), + )) + } +} diff --git a/canyon_connection/src/db_clients/mysql.rs b/canyon_connection/src/db_clients/mysql.rs new file mode 100644 index 00000000..b00dd435 --- /dev/null +++ b/canyon_connection/src/db_clients/mysql.rs @@ -0,0 +1,129 @@ +use async_trait::async_trait; +use canyon_core::query::DbConnection; +#[cfg(feature = "mysql")] +use mysql_async::Pool; + +use canyon_core::query_parameters::QueryParameter; +use canyon_core::rows::CanyonRows; +use mysql_async::Row; +use mysql_common::constants::ColumnType; +use mysql_common::row; + +/// A connection with a `Mysql` database +#[cfg(feature = "mysql")] +pub struct MysqlConnection { + pub client: Pool, +} + +#[async_trait] +impl DbConnection for MysqlConnection { + async fn launch<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + mysql_query_launcher::launch(stmt, params, self).await + } +} + +#[cfg(feature = "mysql")] +pub(crate) mod mysql_query_launcher { + #[cfg(feature = "mysql")] + pub const DETECT_PARAMS_IN_QUERY: &str = r"\$([\d])+"; + #[cfg(feature = "mysql")] + pub const DETECT_QUOTE_IN_QUERY: &str = r#"\"|\\"#; + + use super::*; + + use mysql_async::prelude::Query; + use mysql_async::QueryWithParams; + use mysql_async::Value; + use regex::Regex; + use std::sync::Arc; + + #[inline(always)] + + pub(crate) async fn launch<'a>( + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + conn: &MysqlConnection, + ) -> Result> { + let mysql_connection = conn.client.get_conn().await?; + + let stmt_with_escape_characters = regex::escape(stmt); + let query_string = + Regex::new(DETECT_PARAMS_IN_QUERY)?.replace_all(&stmt_with_escape_characters, "?"); + + let mut query_string = Regex::new(DETECT_QUOTE_IN_QUERY)? + .replace_all(&query_string, "") + .to_string(); + + let mut is_insert = false; + // TODO: take care of this ugly replace for the concrete client syntax by using canyon + // Query + if let Some(index_start_clausule_returning) = query_string.find(" RETURNING") { + query_string.truncate(index_start_clausule_returning); + is_insert = true; + } + + let params_query: Vec = + reorder_params(stmt, params, |f| (*f).as_mysql_param().to_value()); + + let query_with_params = QueryWithParams { + query: query_string, + params: params_query, + }; + + let mut query_result = query_with_params + .run(mysql_connection) + .await + .expect("Error executing query in mysql"); + + let result_rows = if is_insert { + let last_insert = query_result + .last_insert_id() + .map(Value::UInt) + .expect("Error getting pk id in insert"); + + vec![row::new_row( + vec![last_insert], + Arc::new([mysql_async::Column::new(ColumnType::MYSQL_TYPE_UNKNOWN)]), + )] + } else { + query_result + .collect::() + .await + .expect("Error resolved trait FromRow in mysql") + }; + let a = CanyonRows::MySQL(result_rows); + Ok(a) + } +} + +#[cfg(feature = "mysql")] +fn reorder_params( + stmt: &str, + params: &[&'_ dyn QueryParameter<'_>], + fn_parser: impl Fn(&&dyn QueryParameter<'_>) -> T, +) -> Vec { + use mysql_query_launcher::DETECT_PARAMS_IN_QUERY; + + let mut ordered_params = vec![]; + let rg = regex::Regex::new(DETECT_PARAMS_IN_QUERY) + .expect("Error create regex with detect params pattern expression"); + + for positional_param in rg.find_iter(stmt) { + let pp: &str = positional_param.as_str(); + let pp_index = pp[1..] // param $1 -> get 1 + .parse::() + .expect("Error parse mapped parameter to usized.") + - 1; + + let element = params + .get(pp_index) + .expect("Error obtaining the element of the mapping against parameters."); + ordered_params.push(fn_parser(element)); + } + + ordered_params +} diff --git a/canyon_connection/src/db_clients/postgresql.rs b/canyon_connection/src/db_clients/postgresql.rs new file mode 100644 index 00000000..9dbfb811 --- /dev/null +++ b/canyon_connection/src/db_clients/postgresql.rs @@ -0,0 +1,43 @@ +use async_trait::async_trait; +use canyon_core::{query::DbConnection, query_parameters::QueryParameter, rows::CanyonRows}; +#[cfg(feature = "postgres")] +use tokio_postgres::Client; + +/// A connection with a `PostgreSQL` database +#[cfg(feature = "postgres")] +pub struct PostgreSqlConnection { + pub client: Client, + // pub connection: Connection, // TODO Hold it, or not to hold it... that's the question! +} + +#[async_trait] +impl DbConnection for PostgreSqlConnection { + async fn launch<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + postgres_query_launcher::launch(stmt, params, self).await + } +} + +#[cfg(feature = "postgres")] +pub(crate) mod postgres_query_launcher { + use super::*; + + #[inline(always)] + pub(crate) async fn launch<'a>( + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + conn: &PostgreSqlConnection, + ) -> Result> { + let mut m_params = Vec::new(); + for param in params { + m_params.push((*param).as_postgres_param()); + } + + let r = conn.client.query(stmt, m_params.as_slice()).await?; + + Ok(CanyonRows::Postgres(r)) + } +} diff --git a/canyon_connection/src/db_connector.rs b/canyon_connection/src/db_connector.rs index 01bccce9..f56eaf33 100644 --- a/canyon_connection/src/db_connector.rs +++ b/canyon_connection/src/db_connector.rs @@ -1,39 +1,14 @@ -#[cfg(feature = "mssql")] -use async_std::net::TcpStream; -#[cfg(feature = "mysql")] -use mysql_async::Pool; -#[cfg(feature = "mssql")] -use tiberius::Config; -#[cfg(feature = "postgres")] -use tokio_postgres::{Client, NoTls}; - use crate::database_type::DatabaseType; use crate::datasources::DatasourceConfig; +use crate::db_clients::mssql::SqlServerConnection; +use crate::db_clients::mysql::MysqlConnection; +use crate::db_clients::postgresql::PostgreSqlConnection; use canyon_core::query::DbConnection; use canyon_core::query_parameters::QueryParameter; use canyon_core::rows::CanyonRows; use async_trait::async_trait; -/// A connection with a `PostgreSQL` database -#[cfg(feature = "postgres")] -pub struct PostgreSqlConnection { - pub client: Client, - // pub connection: Connection, // TODO Hold it, or not to hold it... that's the question! -} - -/// A connection with a `SqlServer` database -#[cfg(feature = "mssql")] -pub struct SqlServerConnection { - pub client: &'static mut tiberius::Client, -} - -/// A connection with a `Mysql` database -#[cfg(feature = "mysql")] -pub struct MysqlConnection { - pub client: Pool, -} - /// The Canyon database connection handler. When the client's program /// starts, Canyon gets the information about the desired datasources, /// process them and generates a pool of 1 to 1 database connection for @@ -57,19 +32,13 @@ impl DbConnection for DatabaseConnection { ) -> Result> { match self { #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(client) => { - client.launch(stmt, params).await - } + DatabaseConnection::Postgres(client) => client.launch(stmt, params).await, #[cfg(feature = "mssql")] - DatabaseConnection::SqlServer(client) => { - client.launch(stmt, params).await - } + DatabaseConnection::SqlServer(client) => client.launch(stmt, params).await, #[cfg(feature = "mysql")] - DatabaseConnection::MySQL(client) => { - client.launch(stmt, params).await - } + DatabaseConnection::MySQL(client) => client.launch(stmt, params).await, } } } @@ -127,6 +96,7 @@ impl DatabaseConnection { mod connection_helpers { use super::*; + use tokio_postgres::NoTls; #[cfg(feature = "postgres")] pub async fn create_postgres_connection( @@ -154,7 +124,9 @@ mod connection_helpers { pub async fn create_sqlserver_connection( datasource: &DatasourceConfig, ) -> Result> { - let mut tiberius_config = Config::new(); + use async_std::net::TcpStream; + + let mut tiberius_config = tiberius::Config::new(); tiberius_config.host(&datasource.properties.host); tiberius_config.port(datasource.properties.port.unwrap_or_default()); @@ -178,6 +150,8 @@ mod connection_helpers { pub async fn create_mysql_connection( datasource: &DatasourceConfig, ) -> Result> { + use mysql_async::Pool; + let (user, password) = auth::extract_mysql_auth(&datasource.auth)?; let url = connection_string(user, password, datasource); let mysql_connection = Pool::from_url(url)?; @@ -258,187 +232,3 @@ mod auth { } } } - -#[cfg(feature = "postgres")] -mod postgres_query_launcher { - use super::*; - #[async_trait] - impl DbConnection for PostgreSqlConnection { - async fn launch<'a>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - let mut m_params = Vec::new(); - for param in params { - m_params.push((*param).as_postgres_param()); - } - - let r = self.client.query(stmt, m_params.as_slice()).await?; - - Ok(CanyonRows::Postgres(r)) - } - } -} - -#[cfg(feature = "mssql")] -mod sqlserver_query_launcher { - use super::SqlServerConnection; - use async_trait::async_trait; - use canyon_core::{query::DbConnection, query_parameters::QueryParameter, rows::CanyonRows}; - use tiberius::Query; - - #[async_trait] - impl DbConnection for SqlServerConnection { - async fn launch<'a>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - // Re-generate de insert statement to adequate it to the SQL SERVER syntax to retrieve the PK value(s) after insert - // TODO: redo this branch into the generated queries, before the MACROS - // if stmt.contains("RETURNING") { - // let c = stmt.clone(); - // let temp = c.split_once("RETURNING").unwrap(); - // let temp2 = temp.0.split_once("VALUES").unwrap(); - // - // *stmt = format!( - // "{} OUTPUT inserted.{} VALUES {}", - // temp2.0.trim(), - // temp.1.trim(), - // temp2.1.trim() - // ); - // } - - // TODO: We must address the query generation. Look at the returning example, or the - // replace below. We may use our own type Query to address this concerns when the query - // is generated - let mut mssql_query = Query::new(stmt.to_owned().replace('$', "@P")); - params.iter().for_each(|param| mssql_query.bind(*param)); - - #[allow(mutable_transmutes)] - let sqlservconn = unsafe { - std::mem::transmute::<&SqlServerConnection, &mut SqlServerConnection>(self) - }; - let _results = mssql_query - .query(sqlservconn.client) - .await? - .into_results() - .await?; - - Ok(CanyonRows::Tiberius( - _results.into_iter().flatten().collect(), - )) - } - } -} - -#[cfg(feature = "mysql")] -mod mysql_query_launcher { - #[cfg(feature = "mysql")] - pub const DETECT_PARAMS_IN_QUERY: &str = r"\$([\d])+"; - #[cfg(feature = "mysql")] - pub const DETECT_QUOTE_IN_QUERY: &str = r#"\"|\\"#; - - use std::sync::Arc; - - use async_trait::async_trait; - use canyon_core::query::DbConnection; - use mysql_async::prelude::Query; - use mysql_async::QueryWithParams; - use mysql_async::Value; - - use super::MysqlConnection; - - use canyon_core::query_parameters::QueryParameter; - use canyon_core::rows::CanyonRows; - use mysql_async::Row; - use mysql_common::constants::ColumnType; - use mysql_common::row; - use regex::Regex; - - #[async_trait] - impl DbConnection for MysqlConnection { - async fn launch<'a>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - let mysql_connection = self.client.get_conn().await?; - - let stmt_with_escape_characters = regex::escape(stmt); - let query_string = - Regex::new(DETECT_PARAMS_IN_QUERY)?.replace_all(&stmt_with_escape_characters, "?"); - - let mut query_string = Regex::new(DETECT_QUOTE_IN_QUERY)? - .replace_all(&query_string, "") - .to_string(); - - let mut is_insert = false; - // TODO: take care of this ugly replace for the concrete client syntax by using canyon - // Query - if let Some(index_start_clausule_returning) = query_string.find(" RETURNING") { - query_string.truncate(index_start_clausule_returning); - is_insert = true; - } - - let params_query: Vec = - reorder_params(stmt, params, |f| (*f).as_mysql_param().to_value()); - - let query_with_params = QueryWithParams { - query: query_string, - params: params_query, - }; - - let mut query_result = query_with_params - .run(mysql_connection) - .await - .expect("Error executing query in mysql"); - - let result_rows = if is_insert { - let last_insert = query_result - .last_insert_id() - .map(Value::UInt) - .expect("Error getting pk id in insert"); - - vec![row::new_row( - vec![last_insert], - Arc::new([mysql_async::Column::new(ColumnType::MYSQL_TYPE_UNKNOWN)]), - )] - } else { - query_result - .collect::() - .await - .expect("Error resolved trait FromRow in mysql") - }; - let a = CanyonRows::MySQL(result_rows); - Ok(a) - } - } - - #[cfg(feature = "mysql")] - fn reorder_params( - stmt: &str, - params: &[&'_ dyn QueryParameter<'_>], - fn_parser: impl Fn(&&dyn QueryParameter<'_>) -> T, - ) -> Vec { - let mut ordered_params = vec![]; - let rg = regex::Regex::new(DETECT_PARAMS_IN_QUERY) - .expect("Error create regex with detect params pattern expression"); - - for positional_param in rg.find_iter(stmt) { - let pp: &str = positional_param.as_str(); - let pp_index = pp[1..] // param $1 -> get 1 - .parse::() - .expect("Error parse mapped parameter to usized.") - - 1; - - let element = params - .get(pp_index) - .expect("Error obtaining the element of the mapping against parameters."); - ordered_params.push(fn_parser(element)); - } - - ordered_params - } -} diff --git a/canyon_connection/src/lib.rs b/canyon_connection/src/lib.rs index 9e1bb01d..122558f3 100644 --- a/canyon_connection/src/lib.rs +++ b/canyon_connection/src/lib.rs @@ -14,14 +14,15 @@ pub extern crate lazy_static; pub extern crate tokio; pub extern crate tokio_util; -pub mod db_connector; +pub mod conn_errors; pub mod database_type; pub mod datasources; -pub mod conn_errors; +pub mod db_clients; +pub mod db_connector; use std::fmt::Debug; -use std::{error::Error, fs}; use std::path::PathBuf; +use std::{error::Error, fs}; use crate::datasources::{CanyonSqlConfig, DatasourceConfig}; use conn_errors::DatasourceNotFound; @@ -97,22 +98,25 @@ pub async fn init_connections_cache() { // user code determine whenever you can find a valid datasource via a concrete type instead of an string? // TODO: doc (main way for the user to obtain a db connection given a datasource identifier) -pub async fn get_database_connection_by_ds<'a, T: AsRef + Copy + Debug + Default + Send + Sync + 'static> ( +pub async fn get_database_connection_by_ds< + 'a, + T: AsRef + Copy + Debug + Default + Send + Sync + 'static, +>( datasource_name: Option, -) -> Result> { - +) -> Result> { let ds = find_datasource_by_name_or_try_default(datasource_name)?; DatabaseConnection::new(ds).await } -fn find_datasource_by_name_or_try_default<'a, T: AsRef + Copy + Debug + Default>(datasource_name: Option) -> Result<&'a DatasourceConfig, DatasourceNotFound> { +fn find_datasource_by_name_or_try_default<'a, T: AsRef + Copy + Debug + Default>( + datasource_name: Option, +) -> Result<&'a DatasourceConfig, DatasourceNotFound> { datasource_name .map_or_else( || DATASOURCES.first(), - |ds_name| DATASOURCES - .iter() - .find(|ds| ds.name.eq(ds_name.as_ref())) - ).ok_or_else(|| DatasourceNotFound::from(datasource_name)) + |ds_name| DATASOURCES.iter().find(|ds| ds.name.eq(ds_name.as_ref())), + ) + .ok_or_else(|| DatasourceNotFound::from(datasource_name)) } // TODO: create a new one that just receives a str and tries to find the ds config on the vec, so we can make a facade over the one above diff --git a/canyon_core/src/lib.rs b/canyon_core/src/lib.rs index a7fd7465..e0dd521b 100644 --- a/canyon_core/src/lib.rs +++ b/canyon_core/src/lib.rs @@ -9,9 +9,9 @@ pub extern crate tiberius; #[cfg(feature = "mysql")] pub extern crate mysql_async; +pub mod column; +pub mod mapper; pub mod query; pub mod query_parameters; pub mod row; pub mod rows; -pub mod column; -pub mod mapper; diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index fe068dfc..e2e18708 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -6,6 +6,7 @@ use crate::{query_parameters::QueryParameter, rows::CanyonRows}; #[async_trait] pub trait DbConnection { + // TODO: guess that this is the trait that must remain sealed async fn launch<'a>( &self, stmt: &str, diff --git a/canyon_core/src/row.rs b/canyon_core/src/row.rs index 43916aa1..76cf1fce 100644 --- a/canyon_core/src/row.rs +++ b/canyon_core/src/row.rs @@ -35,7 +35,6 @@ impl Row for mysql_async::Row { } } - pub trait RowOperations { #[cfg(feature = "postgres")] fn get_postgres<'a, Output>(&'a self, col_name: &'a str) -> Output diff --git a/canyon_core/src/rows.rs b/canyon_core/src/rows.rs index ca36476a..dd176b23 100644 --- a/canyon_core/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -1,9 +1,9 @@ -#[cfg(feature = "postgres")] -use tokio_postgres::{self}; #[cfg(feature = "mysql")] use mysql_async::{self}; #[cfg(feature = "mssql")] use tiberius::{self}; +#[cfg(feature = "postgres")] +use tokio_postgres::{self}; use crate::mapper::RowMapper; @@ -19,7 +19,7 @@ pub enum CanyonRows { #[cfg(feature = "mssql")] Tiberius(Vec), #[cfg(feature = "mysql")] - MySQL(Vec) + MySQL(Vec), } impl CanyonRows { diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index e14005f7..ff9233f2 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -1,6 +1,6 @@ use async_trait::async_trait; -use canyon_core::{mapper::RowMapper, query::Transaction}; use canyon_core::query_parameters::QueryParameter; +use canyon_core::{mapper::RowMapper, query::Transaction}; use crate::query_elements::query_builder::{ DeleteQueryBuilder, SelectQueryBuilder, UpdateQueryBuilder, @@ -24,7 +24,7 @@ use crate::query_elements::query_builder::{ #[async_trait] pub trait CrudOperations: Transaction where - T: CrudOperations + RowMapper, // TODO: do we need here the RowMapper bound? + T: CrudOperations + RowMapper, { async fn find_all<'a>() -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>>; diff --git a/canyon_crud/src/query_elements/query.rs b/canyon_crud/src/query_elements/query.rs index f77ee3b8..dc66e178 100644 --- a/canyon_crud/src/query_elements/query.rs +++ b/canyon_crud/src/query_elements/query.rs @@ -1,7 +1,7 @@ use std::{fmt::Debug, marker::PhantomData}; -use canyon_core::{mapper::RowMapper, query::Transaction, query_parameters::QueryParameter}; use crate::crud::CrudOperations; +use canyon_core::{mapper::RowMapper, query::Transaction, query_parameters::QueryParameter}; /// Holds a sql sentence details #[derive(Debug, Clone)] diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index 33f9b01e..a94aba12 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -1,12 +1,13 @@ use std::fmt::Debug; -use canyon_connection::{ - database_type::DatabaseType, get_database_config, DATASOURCES, -}; +use canyon_connection::{database_type::DatabaseType, get_database_config, DATASOURCES}; use canyon_core::{mapper::RowMapper, query::Transaction, query_parameters::QueryParameter}; use crate::{ - bounds::{FieldIdentifier, FieldValueIdentifier}, crud::CrudOperations, query_elements::query::Query, Operator + bounds::{FieldIdentifier, FieldValueIdentifier}, + crud::CrudOperations, + query_elements::query::Query, + Operator, }; /// Contains the elements that makes part of the formal declaration @@ -497,7 +498,8 @@ where return self; } if self._inner.query.sql.contains("SET") { - panic!( // TODO: this should return an Err and not panic! + panic!( + // TODO: this should return an Err and not panic! "\n{}", String::from("\t[PANIC!] - Don't use chained calls of the .set(...) method. ") + "\n\tPass all the values in a unique call within the 'columns' " diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index 796091bd..82f718e9 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -368,7 +368,7 @@ pub fn generate_find_by_foreign_key_tokens( #result_handler } - } + }, )); fk_quotes.push(( @@ -384,7 +384,7 @@ pub fn generate_find_by_foreign_key_tokens( #result_handler } - } + }, )); } } diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index 26998223..5d2d5121 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -1,5 +1,12 @@ -use canyon_connection::{datasources::Migrations as MigrationsStatus, db_connector::DatabaseConnection, DATASOURCES}; -use canyon_core::{column::Column, query::Transaction, row::{Row, RowOperations}, rows::CanyonRows}; +use canyon_connection::{ + datasources::Migrations as MigrationsStatus, db_connector::DatabaseConnection, DATASOURCES, +}; +use canyon_core::{ + column::Column, + query::Transaction, + row::{Row, RowOperations}, + rows::CanyonRows, +}; use canyon_entities::CANYON_REGISTER_ENTITIES; use partialdebug::placeholder::PartialDebug; @@ -43,7 +50,8 @@ impl Migrations { let mut migrations_processor = MigrationsProcessor::default(); let mut conn_cache = canyon_connection::CACHED_DATABASE_CONN.lock().await; - let db_conn = canyon_connection::get_database_connection(&datasource.name, &mut conn_cache); + let db_conn = + canyon_connection::get_database_connection(&datasource.name, &mut conn_cache); let canyon_entities = CANYON_REGISTER_ENTITIES.lock().unwrap().to_vec(); let canyon_memory = CanyonMemory::remember(datasource, &canyon_entities).await; @@ -97,13 +105,9 @@ impl Migrations { DatabaseType::MySQL => todo!("Not implemented fetch database in mysql"), }; - Self::query(query, [], db_conn) - .await - .unwrap_or_else(|_| { - panic!( - "Error querying the schema information for the datasource: {ds_name}" - ) - }) + Self::query(query, [], db_conn).await.unwrap_or_else(|_| { + panic!("Error querying the schema information for the datasource: {ds_name}") + }) } /// Handler for parse the result of query the information of some database schema, diff --git a/canyon_migrations/src/migrations/information_schema.rs b/canyon_migrations/src/migrations/information_schema.rs index a9d15b9b..828fb8c7 100644 --- a/canyon_migrations/src/migrations/information_schema.rs +++ b/canyon_migrations/src/migrations/information_schema.rs @@ -2,7 +2,10 @@ use canyon_connection::tiberius::ColumnType as TIB_TY; #[cfg(feature = "postgres")] use canyon_connection::tokio_postgres::types::Type as TP_TYP; -use canyon_core::{column::{Column, ColumnType}, row::{Row, RowOperations}}; +use canyon_core::{ + column::{Column, ColumnType}, + row::{Row, RowOperations}, +}; /// Model that represents the database entities that belongs to the current schema. /// diff --git a/canyon_migrations/src/migrations/memory.rs b/canyon_migrations/src/migrations/memory.rs index a6ea71b1..2131d2bd 100644 --- a/canyon_migrations/src/migrations/memory.rs +++ b/canyon_migrations/src/migrations/memory.rs @@ -247,7 +247,11 @@ impl CanyonMemory { } /// Generates, if not exists the `canyon_memory` table - async fn create_memory(datasource_name: &str, db_conn: &mut DatabaseConnection, database_type: &DatabaseType) { + async fn create_memory( + datasource_name: &str, + db_conn: &mut DatabaseConnection, + database_type: &DatabaseType, + ) { let query = match database_type { #[cfg(feature = "postgres")] DatabaseType::PostgreSql => constants::postgresql_queries::CANYON_MEMORY_TABLE, diff --git a/src/lib.rs b/src/lib.rs index 582be0ab..9ae9e30b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,10 @@ //! The root crate of the `Canyon-SQL` project. +extern crate canyon_connection; /// /// Here it's where all the available functionalities and features /// reaches the top most level, grouping them and making them visible /// through this crate, building the *public API* of the library extern crate canyon_core; -extern crate canyon_connection; extern crate canyon_crud; extern crate canyon_macros; #[cfg(feature = "migrations")] @@ -42,11 +42,11 @@ pub mod connection { } pub mod core { - pub use canyon_core::query::Transaction; + pub use canyon_core::mapper::*; pub use canyon_core::query::DbConnection; + pub use canyon_core::query::Transaction; pub use canyon_core::query_parameters::QueryParameter; pub use canyon_core::rows::CanyonRows; - pub use canyon_core::mapper::*; } /// Crud module serves to reexport the public elements of the `canyon_crud` crate, diff --git a/tests/migrations/mod.rs b/tests/migrations/mod.rs index 977f4686..ebbdec5a 100644 --- a/tests/migrations/mod.rs +++ b/tests/migrations/mod.rs @@ -9,7 +9,8 @@ use canyon_sql::migrations::handler::Migrations; #[cfg(all(feature = "postgres", feature = "migrations"))] #[canyon_sql::macros::canyon_tokio_test] fn test_migrations_postgresql_status_query() { - let conn_res = canyon_sql::connection::get_database_connection_by_ds(Some(constants::PSQL_DS)).await; + let conn_res = + canyon_sql::connection::get_database_connection_by_ds(Some(constants::PSQL_DS)).await; assert!(conn_res.is_ok()); let db_conn = &mut conn_res.unwrap(); From 96119062ee5a8f55d241d29ecbfb85fdbd5443c6 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 20 Jan 2025 13:02:08 +0100 Subject: [PATCH 022/155] feat(wip): initial proposal for making the way of passing something that can end be a db_conn more flexible --- canyon_core/src/query.rs | 55 +++++++++++++++++++++++++++++-- canyon_macros/src/canyon_macro.rs | 4 +-- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index e2e18708..82845611 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -4,6 +4,8 @@ use async_trait::async_trait; use crate::{query_parameters::QueryParameter, rows::CanyonRows}; +// TODO: in order to avoid the tiberius transmute, we should define other method that takes the db_conn as a mut ref + #[async_trait] pub trait DbConnection { // TODO: guess that this is the trait that must remain sealed @@ -14,22 +16,69 @@ pub trait DbConnection { ) -> Result>; } +pub trait Datasource: DbConnection{} + #[async_trait] pub trait Transaction { // provisional name /// Performs a query against the targeted database by the selected or /// the defaulted datasource, wrapping the resultant collection of entities /// in [`super::rows::CanyonRows`] - async fn query<'a, C, S, Z>( + async fn query<'a, C, S, Z, I>( stmt: S, params: Z, - db_conn: &mut C, + input: I, ) -> Result> where S: AsRef + Display + Sync + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, C: DbConnection + Sync + Send + 'a, + I: Into> + Sync + Send + 'a { - db_conn.launch(stmt.as_ref(), params.as_ref()).await + let transaction_input= input.into(); + match transaction_input { + TransactionInput::DbConnection(mut conn) => { + conn.launch(stmt.as_ref(), params.as_ref()).await + } + // TransactionInput::DatasourceConfig(ds) => { + // let mut conn = DatabaseConnection::new(&ds).await?; + // conn.launch(stmt.as_ref(), params).await + // } + TransactionInput::DatasourceName(ds_name) => { + let ds = get_database_connection_by_ds(Some(ds_name))?; + let mut conn = DatabaseConnection::new(ds).await?; + conn.launch(stmt.as_ref(), params).await + } + } + } +} + +pub enum TransactionInput { + DbConnection(T), + // DatasourceConfig(DatasourceConfig), + DatasourceName(String), +} + +impl From for TransactionInput { + fn from(conn: T) -> Self { + TransactionInput::DbConnection(conn) + } +} + +// impl From for TransactionInput { +// fn from(ds: DatasourceConfig) -> Self { +// TransactionInput::DatasourceConfig(ds) +// } +// } + +impl From for TransactionInput { + fn from(ds_name: String) -> Self { + TransactionInput::DatasourceName(ds_name) + } +} + +impl From<&str> for TransactionInput { + fn from(ds_name: &str) -> Self { + TransactionInput::DatasourceName(ds_name.to_string()) } } diff --git a/canyon_macros/src/canyon_macro.rs b/canyon_macros/src/canyon_macro.rs index 95379581..626b8ebe 100644 --- a/canyon_macros/src/canyon_macro.rs +++ b/canyon_macros/src/canyon_macro.rs @@ -7,9 +7,9 @@ use canyon_migrations::{CM_QUERIES_TO_EXECUTE, QUERIES_TO_EXECUTE}; use proc_macro2::TokenStream; use quote::quote; -pub fn main_with_queries() -> TokenStream { +pub fn main_with_queries() -> TokenStream { // TODO: migrations on main instead of main_with_queries CANYON_TOKIO_RUNTIME.block_on(async { - canyon_connection::init_connections_cache().await; + canyon_connection::init_connections_cache().await; // TODO: isn't this cache always initialized anyway? try to remove it Migrations::migrate().await; }); From 77ff230a8a6fc11dd3d487a217c7b77476157ecc Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 20 Jan 2025 15:54:53 +0100 Subject: [PATCH 023/155] feat: Allowing Transaction::query(...) to receive different kind of inputs to have a db_conn --- Cargo.toml | 9 +- canyon_connection/Cargo.toml | 39 ----- canyon_connection/src/conn_errors.rs | 24 --- canyon_core/Cargo.toml | 10 ++ canyon_core/src/connection/conn_errors.rs | 24 +++ .../src/connection}/database_type.rs | 9 +- .../src/connection}/datasources.rs | 4 +- .../src/connection}/db_clients/mod.rs | 0 .../src/connection}/db_clients/mssql.rs | 2 +- .../src/connection}/db_clients/mysql.rs | 4 +- .../src/connection}/db_clients/postgresql.rs | 3 +- .../src/connection}/db_connector.rs | 25 +-- .../src => canyon_core/src/connection}/lib.rs | 0 canyon_core/src/connection/mod.rs | 159 ++++++++++++++++++ .../src/connection}/provisional_tests.rs | 0 canyon_core/src/lib.rs | 4 + canyon_core/src/query.rs | 56 ++++-- canyon_core/src/query_parameters.rs | 2 +- canyon_crud/Cargo.toml | 7 +- canyon_crud/src/lib.rs | 3 +- canyon_crud/src/query_elements/operators.rs | 2 +- .../src/query_elements/query_builder.rs | 2 +- canyon_macros/Cargo.toml | 7 +- canyon_macros/src/canyon_macro.rs | 4 +- canyon_migrations/Cargo.toml | 7 +- canyon_migrations/src/constants.rs | 2 +- canyon_migrations/src/lib.rs | 3 +- canyon_migrations/src/migrations/handler.rs | 12 +- .../src/migrations/information_schema.rs | 4 +- canyon_migrations/src/migrations/memory.rs | 6 +- canyon_migrations/src/migrations/processor.rs | 4 +- src/lib.rs | 25 ++- 32 files changed, 304 insertions(+), 158 deletions(-) delete mode 100644 canyon_connection/Cargo.toml delete mode 100644 canyon_connection/src/conn_errors.rs create mode 100644 canyon_core/src/connection/conn_errors.rs rename {canyon_connection/src => canyon_core/src/connection}/database_type.rs (72%) rename {canyon_connection/src => canyon_core/src/connection}/datasources.rs (99%) rename {canyon_connection/src => canyon_core/src/connection}/db_clients/mod.rs (100%) rename {canyon_connection/src => canyon_core/src/connection}/db_clients/mssql.rs (96%) rename {canyon_connection/src => canyon_core/src/connection}/db_clients/mysql.rs (96%) rename {canyon_connection/src => canyon_core/src/connection}/db_clients/postgresql.rs (93%) rename {canyon_connection/src => canyon_core/src/connection}/db_connector.rs (92%) rename {canyon_connection/src => canyon_core/src/connection}/lib.rs (100%) create mode 100644 canyon_core/src/connection/mod.rs rename {canyon_connection/src => canyon_core/src/connection}/provisional_tests.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index d9179984..a9488d46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ description.workspace = true [workspace] members = [ "canyon_core", - "canyon_connection", "canyon_crud", "canyon_entities", "canyon_migrations", @@ -23,7 +22,6 @@ members = [ [dependencies] # Project crates canyon_core = { workspace = true } -canyon_connection = { workspace = true } canyon_crud = { workspace = true } canyon_entities = { workspace = true } canyon_migrations = { workspace = true, optional = true } @@ -39,7 +37,6 @@ mysql_common = { workspace = true, optional = true } [workspace.dependencies] canyon_core = { version = "0.5.1", path = "canyon_core" } canyon_crud = { version = "0.5.1", path = "canyon_crud" } -canyon_connection = { version = "0.5.1", path = "canyon_connection" } canyon_entities = { version = "0.5.1", path = "canyon_entities" } canyon_migrations = { version = "0.5.1", path = "canyon_migrations"} canyon_macros = { version = "0.5.1", path = "canyon_macros" } @@ -78,7 +75,7 @@ license = "MIT" description = "A Rust ORM and QueryBuilder" [features] -postgres = ["tokio-postgres", "canyon_core/postgres", "canyon_connection/postgres", "canyon_crud/postgres", "canyon_migrations/postgres", "canyon_macros/postgres"] -mssql = ["tiberius", "canyon_core/mssql", "canyon_connection/mssql", "canyon_crud/mssql", "canyon_migrations/mssql", "canyon_macros/mssql"] -mysql = ["mysql_async", "mysql_common", "canyon_core/mysql", "canyon_connection/mysql", "canyon_crud/mysql", "canyon_migrations/mysql", "canyon_macros/mysql"] +postgres = ["tokio-postgres", "canyon_core/postgres", "canyon_crud/postgres", "canyon_migrations/postgres", "canyon_macros/postgres"] +mssql = ["tiberius", "canyon_core/mssql", "canyon_crud/mssql", "canyon_migrations/mssql", "canyon_macros/mssql"] +mysql = ["mysql_async", "mysql_common", "canyon_core/mysql", "canyon_crud/mysql", "canyon_migrations/mysql", "canyon_macros/mysql"] migrations = ["canyon_migrations", "canyon_macros/migrations"] diff --git a/canyon_connection/Cargo.toml b/canyon_connection/Cargo.toml deleted file mode 100644 index adaf4261..00000000 --- a/canyon_connection/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[package] -name = "canyon_connection" -version.workspace = true -edition.workspace = true -authors.workspace = true -documentation.workspace = true -homepage.workspace = true -readme.workspace = true -license.workspace = true -description.workspace = true - -[dependencies] -canyon_core = { workspace = true } - -tokio = { workspace = true } -tokio-util = { workspace = true } - -tokio-postgres = { workspace = true, optional = true } - -tiberius = { workspace = true, optional = true } -mysql_async = { workspace = true, optional = true } -mysql_common = { workspace = true, optional = true } - -futures = { workspace = true } -indexmap = { workspace = true } -lazy_static = { workspace = true } -toml = { workspace = true } -serde = { workspace = true } -async-std = { workspace = true, optional = true } -async-trait = { workspace = true } -walkdir = { workspace = true } -regex = { workspace = true } - -[features] -postgres = ["tokio-postgres", "canyon_core/postgres"] -mssql = ["tiberius", "async-std", "canyon_core/mssql"] -mysql = ["mysql_async","mysql_common", "canyon_core/mysql"] - - diff --git a/canyon_connection/src/conn_errors.rs b/canyon_connection/src/conn_errors.rs deleted file mode 100644 index a4f02c19..00000000 --- a/canyon_connection/src/conn_errors.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! Defines the Canyon-SQL custom connection error types - -/// Raised when a [`crate::datasources::DatasourceConfig`] isn't found given a user input -#[derive(Debug, Clone)] -pub struct DatasourceNotFound + ?Sized + std::fmt::Debug + Default> { - pub datasource_name: T, -} -impl + std::fmt::Debug + Default> From> for DatasourceNotFound { - fn from(value: Option) -> Self { - DatasourceNotFound { - datasource_name: value.unwrap_or_default(), - } - } -} -impl + std::fmt::Debug + Default> std::fmt::Display for DatasourceNotFound { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "Unable to found a datasource that matches: {:?}", - self.datasource_name - ) - } -} -impl + std::fmt::Debug + Default> std::error::Error for DatasourceNotFound {} diff --git a/canyon_core/Cargo.toml b/canyon_core/Cargo.toml index 4fb6e6fd..0ed42be7 100644 --- a/canyon_core/Cargo.toml +++ b/canyon_core/Cargo.toml @@ -20,6 +20,16 @@ async-std = { workspace = true, optional = true } async-trait = { workspace = true } regex = { workspace = true } +tokio = { workspace = true } +tokio-util = { workspace = true } + +futures = { workspace = true } +indexmap = { workspace = true } +lazy_static = { workspace = true } +toml = { workspace = true } +serde = { workspace = true } +walkdir = { workspace = true } + [features] postgres = ["tokio-postgres"] mssql = ["tiberius", "async-std"] diff --git a/canyon_core/src/connection/conn_errors.rs b/canyon_core/src/connection/conn_errors.rs new file mode 100644 index 00000000..9a67cc64 --- /dev/null +++ b/canyon_core/src/connection/conn_errors.rs @@ -0,0 +1,24 @@ +//! Defines the Canyon-SQL custom connection error types + +/// Raised when a [`crate::datasources::DatasourceConfig`] isn't found given a user input +#[derive(Debug, Clone)] +pub struct DatasourceNotFound<'a> { + pub datasource_name: &'a str, +} +impl<'a> From> for DatasourceNotFound<'a> { + fn from(value: Option<&'a str>) -> Self { + DatasourceNotFound { + datasource_name: value.unwrap_or_default(), // TODO: not default + } + } +} +impl<'a> std::fmt::Display for DatasourceNotFound<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "Unable to found a datasource that matches: {:?}", + self.datasource_name + ) + } +} +impl<'a> std::error::Error for DatasourceNotFound<'a> {} diff --git a/canyon_connection/src/database_type.rs b/canyon_core/src/connection/database_type.rs similarity index 72% rename from canyon_connection/src/database_type.rs rename to canyon_core/src/connection/database_type.rs index a319d512..8c71d2c4 100644 --- a/canyon_connection/src/database_type.rs +++ b/canyon_core/src/connection/database_type.rs @@ -1,6 +1,7 @@ use serde::Deserialize; -use crate::datasources::Auth; +use super::datasources::Auth; + /// Holds the current supported databases by Canyon-SQL #[derive(Deserialize, Debug, Eq, PartialEq, Clone, Copy)] @@ -20,11 +21,11 @@ impl From<&Auth> for DatabaseType { fn from(value: &Auth) -> Self { match value { #[cfg(feature = "postgres")] - crate::datasources::Auth::Postgres(_) => DatabaseType::PostgreSql, + Auth::Postgres(_) => DatabaseType::PostgreSql, #[cfg(feature = "mssql")] - crate::datasources::Auth::SqlServer(_) => DatabaseType::SqlServer, + Auth::SqlServer(_) => DatabaseType::SqlServer, #[cfg(feature = "mysql")] - crate::datasources::Auth::MySQL(_) => DatabaseType::MySQL, + Auth::MySQL(_) => DatabaseType::MySQL, } } } diff --git a/canyon_connection/src/datasources.rs b/canyon_core/src/connection/datasources.rs similarity index 99% rename from canyon_connection/src/datasources.rs rename to canyon_core/src/connection/datasources.rs index e118776c..2a988d7b 100644 --- a/canyon_connection/src/datasources.rs +++ b/canyon_core/src/connection/datasources.rs @@ -1,6 +1,7 @@ use serde::Deserialize; -use crate::database_type::DatabaseType; +use super::database_type::DatabaseType; + /// ``` #[test] @@ -179,7 +180,6 @@ pub enum Migrations { #[cfg(test)] mod datasources_tests { use super::*; - use crate::CanyonSqlConfig; /// Tests the behaviour of the `DatabaseType::from_datasource(...)` #[test] diff --git a/canyon_connection/src/db_clients/mod.rs b/canyon_core/src/connection/db_clients/mod.rs similarity index 100% rename from canyon_connection/src/db_clients/mod.rs rename to canyon_core/src/connection/db_clients/mod.rs diff --git a/canyon_connection/src/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs similarity index 96% rename from canyon_connection/src/db_clients/mssql.rs rename to canyon_core/src/connection/db_clients/mssql.rs index e27d22f1..2f2ef8bf 100644 --- a/canyon_connection/src/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -2,7 +2,7 @@ use async_std::net::TcpStream; use async_trait::async_trait; -use canyon_core::{query::DbConnection, query_parameters::QueryParameter, rows::CanyonRows}; +use crate::{query::DbConnection, query_parameters::QueryParameter, rows::CanyonRows}; use tiberius::Query; /// A connection with a `SqlServer` database diff --git a/canyon_connection/src/db_clients/mysql.rs b/canyon_core/src/connection/db_clients/mysql.rs similarity index 96% rename from canyon_connection/src/db_clients/mysql.rs rename to canyon_core/src/connection/db_clients/mysql.rs index b00dd435..a33a91ea 100644 --- a/canyon_connection/src/db_clients/mysql.rs +++ b/canyon_core/src/connection/db_clients/mysql.rs @@ -1,10 +1,8 @@ use async_trait::async_trait; -use canyon_core::query::DbConnection; #[cfg(feature = "mysql")] use mysql_async::Pool; -use canyon_core::query_parameters::QueryParameter; -use canyon_core::rows::CanyonRows; +use crate::{query::DbConnection, query_parameters::QueryParameter, rows::CanyonRows}; use mysql_async::Row; use mysql_common::constants::ColumnType; use mysql_common::row; diff --git a/canyon_connection/src/db_clients/postgresql.rs b/canyon_core/src/connection/db_clients/postgresql.rs similarity index 93% rename from canyon_connection/src/db_clients/postgresql.rs rename to canyon_core/src/connection/db_clients/postgresql.rs index 9dbfb811..ef04746d 100644 --- a/canyon_connection/src/db_clients/postgresql.rs +++ b/canyon_core/src/connection/db_clients/postgresql.rs @@ -1,5 +1,6 @@ use async_trait::async_trait; -use canyon_core::{query::DbConnection, query_parameters::QueryParameter, rows::CanyonRows}; +use crate::{query::DbConnection, query_parameters::QueryParameter, rows::CanyonRows}; + #[cfg(feature = "postgres")] use tokio_postgres::Client; diff --git a/canyon_connection/src/db_connector.rs b/canyon_core/src/connection/db_connector.rs similarity index 92% rename from canyon_connection/src/db_connector.rs rename to canyon_core/src/connection/db_connector.rs index f56eaf33..09ad3208 100644 --- a/canyon_connection/src/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -1,11 +1,12 @@ -use crate::database_type::DatabaseType; -use crate::datasources::DatasourceConfig; -use crate::db_clients::mssql::SqlServerConnection; -use crate::db_clients::mysql::MysqlConnection; -use crate::db_clients::postgresql::PostgreSqlConnection; -use canyon_core::query::DbConnection; -use canyon_core::query_parameters::QueryParameter; -use canyon_core::rows::CanyonRows; +use crate::connection::database_type::DatabaseType; +use crate::connection::datasources::DatasourceConfig; +use crate::connection::db_clients::mssql::SqlServerConnection; +use crate::connection::db_clients::mysql::MysqlConnection; +use crate::connection::db_clients::postgresql::PostgreSqlConnection; + +use crate::query::DbConnection; +use crate::query_parameters::QueryParameter; +use crate::rows::CanyonRows; use async_trait::async_trait; @@ -181,14 +182,14 @@ mod connection_helpers { } mod auth { - use crate::datasources::Auth; + use crate::connection::datasources::Auth; #[cfg(feature = "mysql")] - use crate::datasources::MySQLAuth; + use crate::connection::datasources::MySQLAuth; #[cfg(feature = "postgres")] - use crate::datasources::PostgresAuth; + use crate::connection::datasources::PostgresAuth; #[cfg(feature = "mssql")] - use crate::datasources::SqlServerAuth; + use crate::connection::datasources::SqlServerAuth; #[cfg(feature = "postgres")] pub fn extract_postgres_auth<'a>( diff --git a/canyon_connection/src/lib.rs b/canyon_core/src/connection/lib.rs similarity index 100% rename from canyon_connection/src/lib.rs rename to canyon_core/src/connection/lib.rs diff --git a/canyon_core/src/connection/mod.rs b/canyon_core/src/connection/mod.rs new file mode 100644 index 00000000..bd958c02 --- /dev/null +++ b/canyon_core/src/connection/mod.rs @@ -0,0 +1,159 @@ +#[cfg(feature = "postgres")] +pub extern crate tokio_postgres; + +#[cfg(feature = "mssql")] +pub extern crate async_std; +#[cfg(feature = "mssql")] +pub extern crate tiberius; + +#[cfg(feature = "mysql")] +pub extern crate mysql_async; + +pub extern crate futures; +pub extern crate lazy_static; +pub extern crate tokio; +pub extern crate tokio_util; + +pub mod conn_errors; +pub mod database_type; +pub mod datasources; +pub mod db_clients; +pub mod db_connector; + +use std::path::PathBuf; +use std::{error::Error, fs}; + +use conn_errors::DatasourceNotFound; +use datasources::{CanyonSqlConfig, DatasourceConfig}; +use db_connector::DatabaseConnection; +use indexmap::IndexMap; +use lazy_static::lazy_static; +use tokio::sync::{Mutex, MutexGuard}; +use walkdir::WalkDir; + +lazy_static! { + pub static ref CANYON_TOKIO_RUNTIME: tokio::runtime::Runtime = + tokio::runtime::Runtime::new() // TODO Make the config with the builder + .expect("Failed initializing the Canyon-SQL Tokio Runtime"); + + static ref RAW_CONFIG_FILE: String = fs::read_to_string(find_canyon_config_file()) + .expect("Error opening or reading the Canyon configuration file"); + static ref CONFIG_FILE: CanyonSqlConfig = toml::from_str(RAW_CONFIG_FILE.as_str()) + .expect("Error generating the configuration for Canyon-SQL"); + + pub static ref DATASOURCES: Vec< + DatasourceConfig> = + CONFIG_FILE.canyon_sql.datasources.clone(); + + pub static ref CACHED_DATABASE_CONN: Mutex> = + Mutex::new(IndexMap::new()); +} + +fn find_canyon_config_file() -> PathBuf { + for e in WalkDir::new(".") + .max_depth(2) + .into_iter() + .filter_map(|e| e.ok()) + { + let filename = e.file_name().to_str().unwrap(); // TODO: remove the .unwrap(). Use + // lowercase to allow Canyon.toml + if e.metadata().unwrap().is_file() + && filename.starts_with("canyon") + && filename.ends_with(".toml") + { + return e.path().to_path_buf(); + } + } + + panic!() // TODO: get rid out of this panic and return Err instead +} + +/// Convenient free function to initialize a kind of connection pool based on the datasources present defined +/// in the configuration file. +/// +/// This avoids Canyon to create a new connection to the database on every query, potentially avoiding bottlenecks +/// coming from the instantiation of that new conn every time. +/// +/// Note: We noticed with the integration tests that the [`tokio_postgres`] crate (PostgreSQL) is able to work in an async environment +/// with a new connection per query without no problem, but the [`tiberius`] crate (MSSQL) suffers a lot when it has continuous +/// statements with multiple queries, like and insert followed by a find by id to check if the insert query has done its +/// job done. +pub async fn init_connections_cache() { + for datasource in DATASOURCES.iter() { + CACHED_DATABASE_CONN.lock().await.insert( + &datasource.name, + DatabaseConnection::new(datasource) + .await + .unwrap_or_else(|_| { + panic!( + "Error pooling a new connection for the datasource: {:?}", + datasource.name + ) + }), + ); + } +} + +// TODO: idea. Should we leak the datasources config pull to the user, so we can be more flexible and let the +// user code determine whenever you can find a valid datasource via a concrete type instead of an string? + +// TODO: doc (main way for the user to obtain a db connection given a datasource identifier) +pub async fn get_database_connection_by_ds<'a>( + datasource_name: Option<&'a str>, +) -> Result> { + let ds = find_datasource_by_name_or_try_default(datasource_name)?; + DatabaseConnection::new(ds).await +} + +fn find_datasource_by_name_or_try_default<'a>( + datasource_name: Option<&'a str>, +) -> Result<&'a DatasourceConfig, DatasourceNotFound<'a>> { + datasource_name + .map_or_else( + || DATASOURCES.first(), + |ds_name| DATASOURCES.iter().find(|ds| ds.name.eq(ds_name)), + ) + .ok_or_else(|| DatasourceNotFound::from(datasource_name)) +} + +// TODO: create a new one that just receives a str and tries to find the ds config on the vec, so we can make a facade over the one above + +// TODO: get_cached_database_connection +// the idea behind this is that we can have a #cfg feature that offers the end user to let Canyon to automagically manage the connections +// to the db servers, instead of being the default behaviour +pub fn get_database_connection<'a>( + datasource_name: &str, + guarded_cache: &'a mut MutexGuard>, +) -> &'a mut DatabaseConnection { + if datasource_name.is_empty() { + guarded_cache + .get_mut( + DATASOURCES + .first() + .expect("We didn't found any valid datasource configuration. Check your `canyon.toml` file") + .name + .as_str() + ).unwrap_or_else(|| panic!("No default datasource found. Check your `canyon.toml` file")) + } else { + guarded_cache.get_mut(datasource_name) + .unwrap_or_else(|| + panic!("Canyon couldn't find a datasource in the pool with the argument provided: {datasource_name}") + ) + } +} + +pub fn get_database_config<'a>( + datasource_name: &str, + datasources_config: &'a [DatasourceConfig], +) -> &'a DatasourceConfig { + if datasource_name.is_empty() { + datasources_config + .first() + .unwrap_or_else(|| panic!("Not exist datasource")) + } else { + datasources_config + .iter() + .find(|dc| dc.name == datasource_name) + .unwrap_or_else(|| panic!("Not found datasource expected {datasource_name}")) + } +} diff --git a/canyon_connection/src/provisional_tests.rs b/canyon_core/src/connection/provisional_tests.rs similarity index 100% rename from canyon_connection/src/provisional_tests.rs rename to canyon_core/src/connection/provisional_tests.rs diff --git a/canyon_core/src/lib.rs b/canyon_core/src/lib.rs index e0dd521b..aa7f0fa3 100644 --- a/canyon_core/src/lib.rs +++ b/canyon_core/src/lib.rs @@ -9,6 +9,10 @@ pub extern crate tiberius; #[cfg(feature = "mysql")] pub extern crate mysql_async; +pub extern crate lazy_static; + + +pub mod connection; pub mod column; pub mod mapper; pub mod query; diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index 82845611..b73a6ca8 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -2,7 +2,7 @@ use std::fmt::Display; use async_trait::async_trait; -use crate::{query_parameters::QueryParameter, rows::CanyonRows}; +use crate::{connection::get_database_connection_by_ds, query_parameters::QueryParameter, rows::CanyonRows}; // TODO: in order to avoid the tiberius transmute, we should define other method that takes the db_conn as a mut ref @@ -28,16 +28,22 @@ pub trait Transaction { stmt: S, params: Z, input: I, - ) -> Result> + ) -> Result> where S: AsRef + Display + Sync + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, C: DbConnection + Sync + Send + 'a, - I: Into> + Sync + Send + 'a + I: Into> + Sync + Send + 'a { let transaction_input= input.into(); match transaction_input { - TransactionInput::DbConnection(mut conn) => { + TransactionInput::DbConnection(conn) => { + conn.launch(stmt.as_ref(), params.as_ref()).await + } + TransactionInput::DbConnectionRef(conn) => { + conn.launch(stmt.as_ref(), params.as_ref()).await + } + TransactionInput::DbConnectionRefMut(/* TODO: mut*/ conn) => { conn.launch(stmt.as_ref(), params.as_ref()).await } // TransactionInput::DatasourceConfig(ds) => { @@ -45,40 +51,54 @@ pub trait Transaction { // conn.launch(stmt.as_ref(), params).await // } TransactionInput::DatasourceName(ds_name) => { - let ds = get_database_connection_by_ds(Some(ds_name))?; - let mut conn = DatabaseConnection::new(ds).await?; - conn.launch(stmt.as_ref(), params).await + let conn = get_database_connection_by_ds(Some(ds_name)).await?; + conn.launch(stmt.as_ref(), params.as_ref()).await } + // _ => todo!() } } } -pub enum TransactionInput { +pub enum TransactionInput<'a, T: DbConnection> { DbConnection(T), + DbConnectionRef(&'a T), + DbConnectionRefMut(&'a mut T), // DatasourceConfig(DatasourceConfig), - DatasourceName(String), + DatasourceName(&'a str), } -impl From for TransactionInput { +impl<'a, T: DbConnection> From for TransactionInput<'a, T> { fn from(conn: T) -> Self { TransactionInput::DbConnection(conn) } } +impl<'a, T: DbConnection> From<&'a T> for TransactionInput<'a, T> { + fn from(conn: &'a T) -> Self { + TransactionInput::DbConnectionRef(conn) + } +} + +impl<'a, T: DbConnection> From<&'a mut T> for TransactionInput<'a, T> { + fn from(conn: &'a mut T) -> Self { + TransactionInput::DbConnectionRefMut(conn) + } +} + // impl From for TransactionInput { // fn from(ds: DatasourceConfig) -> Self { // TransactionInput::DatasourceConfig(ds) // } // } -impl From for TransactionInput { - fn from(ds_name: String) -> Self { - TransactionInput::DatasourceName(ds_name) - } -} +// impl<'a, T: DbConnection> From for TransactionInput<'a, T> { +// fn from(ds_name: String) -> TransactionInput<'a, T> { +// TransactionInput::DatasourceName(ds_name.as_str()) +// } +// } -impl From<&str> for TransactionInput { - fn from(ds_name: &str) -> Self { - TransactionInput::DatasourceName(ds_name.to_string()) +impl<'a, T: DbConnection> From<&'a str> for TransactionInput<'a, T> { + fn from(ds_name: &'a str) -> Self { + TransactionInput::DatasourceName(ds_name) } } diff --git a/canyon_core/src/query_parameters.rs b/canyon_core/src/query_parameters.rs index c38ce3f4..dde75109 100644 --- a/canyon_core/src/query_parameters.rs +++ b/canyon_core/src/query_parameters.rs @@ -19,7 +19,7 @@ pub trait QueryParameter<'a>: std::fmt::Debug + Sync + Send { fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue; } -/// The implementation of the [`canyon_connection::tiberius`] [`IntoSql`] for the +/// The implementation of the [`canyon_core::connection::tiberius`] [`IntoSql`] for the /// query parameters. /// /// This implementation is necessary because of the generic amplitude diff --git a/canyon_crud/Cargo.toml b/canyon_crud/Cargo.toml index 2b15487d..b774a91d 100644 --- a/canyon_crud/Cargo.toml +++ b/canyon_crud/Cargo.toml @@ -11,7 +11,6 @@ description.workspace = true [dependencies] canyon_core = { workspace = true } -canyon_connection = { workspace = true } tokio-postgres = { workspace = true, optional = true } tiberius = { workspace = true, optional = true } @@ -23,6 +22,6 @@ async-trait = { workspace = true } regex = { workspace = true } [features] -postgres = ["tokio-postgres", "canyon_core/postgres", "canyon_connection/postgres"] -mssql = ["tiberius", "canyon_core/mssql", "canyon_connection/mssql"] -mysql = ["mysql_async","mysql_common", "canyon_core/mysql", "canyon_connection/mysql"] +postgres = ["tokio-postgres", "canyon_core/postgres"] +mssql = ["tiberius", "canyon_core/mssql"] +mysql = ["mysql_async","mysql_common", "canyon_core/mysql"] diff --git a/canyon_crud/src/lib.rs b/canyon_crud/src/lib.rs index f5260756..dfee5da3 100644 --- a/canyon_crud/src/lib.rs +++ b/canyon_crud/src/lib.rs @@ -1,5 +1,4 @@ pub extern crate async_trait; -extern crate canyon_connection; pub mod bounds; pub mod crud; @@ -7,5 +6,5 @@ pub mod query_elements; pub use query_elements::operators::*; -pub use canyon_connection::{database_type::DatabaseType, datasources::*}; +pub use canyon_core::connection::{database_type::DatabaseType, datasources::*}; pub use chrono; diff --git a/canyon_crud/src/query_elements/operators.rs b/canyon_crud/src/query_elements/operators.rs index 2b067d18..7a613630 100644 --- a/canyon_crud/src/query_elements/operators.rs +++ b/canyon_crud/src/query_elements/operators.rs @@ -1,4 +1,4 @@ -use canyon_connection::database_type::DatabaseType; +use canyon_core::connection::database_type::DatabaseType; pub trait Operator { fn as_str(&self, placeholder_counter: usize, datasource_type: &DatabaseType) -> String; diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index a94aba12..9eacf89e 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; -use canyon_connection::{database_type::DatabaseType, get_database_config, DATASOURCES}; +use canyon_core::connection::{database_type::DatabaseType, get_database_config, DATASOURCES}; use canyon_core::{mapper::RowMapper, query::Transaction, query_parameters::QueryParameter}; use crate::{ diff --git a/canyon_macros/Cargo.toml b/canyon_macros/Cargo.toml index 1ba7d67b..bb7eb660 100755 --- a/canyon_macros/Cargo.toml +++ b/canyon_macros/Cargo.toml @@ -20,14 +20,13 @@ futures = { workspace = true } tokio = { workspace = true } canyon_core = { workspace = true } -canyon_connection = { workspace = true } canyon_crud = { workspace = true } canyon_entities = { workspace = true } canyon_migrations = { workspace = true, optional = true } [features] -postgres = ["canyon_core/postgres", "canyon_connection/postgres", "canyon_crud/postgres", "canyon_migrations/postgres"] -mssql = ["canyon_core/mssql", "canyon_connection/mssql", "canyon_crud/mssql", "canyon_migrations/mssql"] -mysql = ["canyon_core/mysql", "canyon_connection/mysql", "canyon_crud/mysql", "canyon_migrations/mysql"] +postgres = ["canyon_core/postgres", "canyon_crud/postgres", "canyon_migrations/postgres"] +mssql = ["canyon_core/mssql", "canyon_crud/mssql", "canyon_migrations/mssql"] +mysql = ["canyon_core/mysql", "canyon_crud/mysql", "canyon_migrations/mysql"] migrations = ["canyon_migrations"] diff --git a/canyon_macros/src/canyon_macro.rs b/canyon_macros/src/canyon_macro.rs index 626b8ebe..67cc89c6 100644 --- a/canyon_macros/src/canyon_macro.rs +++ b/canyon_macros/src/canyon_macro.rs @@ -1,7 +1,7 @@ //! Provides helpers to build the `#[canyon_macros::canyon]` procedural like attribute macro #![cfg(feature = "migrations")] -use canyon_connection::CANYON_TOKIO_RUNTIME; +use canyon_core::connection::CANYON_TOKIO_RUNTIME; use canyon_migrations::migrations::handler::Migrations; use canyon_migrations::{CM_QUERIES_TO_EXECUTE, QUERIES_TO_EXECUTE}; use proc_macro2::TokenStream; @@ -9,7 +9,7 @@ use quote::quote; pub fn main_with_queries() -> TokenStream { // TODO: migrations on main instead of main_with_queries CANYON_TOKIO_RUNTIME.block_on(async { - canyon_connection::init_connections_cache().await; // TODO: isn't this cache always initialized anyway? try to remove it + canyon_core::connection::init_connections_cache().await; // TODO: isn't this cache always initialized anyway? try to remove it Migrations::migrate().await; }); diff --git a/canyon_migrations/Cargo.toml b/canyon_migrations/Cargo.toml index 70927d21..b1b9c915 100644 --- a/canyon_migrations/Cargo.toml +++ b/canyon_migrations/Cargo.toml @@ -11,7 +11,6 @@ description.workspace = true [dependencies] canyon_core = { workspace = true } -canyon_connection = { workspace = true } canyon_crud = { workspace = true } canyon_entities = { workspace = true } @@ -32,7 +31,7 @@ quote = { workspace = true } syn = { version = "1.0.86", features = ["full", "parsing"] } # TODO Pending to refactor and upgrade [features] -postgres = ["tokio-postgres", "canyon_core/postgres", "canyon_connection/postgres", "canyon_crud/postgres"] -mssql = ["tiberius", "canyon_core/mssql", "canyon_connection/mssql", "canyon_crud/mssql"] -mysql = ["mysql_async","mysql_common", "canyon_core/mysql", "canyon_connection/mysql", "canyon_crud/mysql"] +postgres = ["tokio-postgres", "canyon_core/postgres", "canyon_crud/postgres"] +mssql = ["tiberius", "canyon_core/mssql", "canyon_crud/mssql"] +mysql = ["mysql_async","mysql_common", "canyon_core/mysql", "canyon_crud/mysql"] diff --git a/canyon_migrations/src/constants.rs b/canyon_migrations/src/constants.rs index 9f025762..103b68d5 100644 --- a/canyon_migrations/src/constants.rs +++ b/canyon_migrations/src/constants.rs @@ -171,7 +171,7 @@ pub mod sqlserver_type { pub mod mocked_data { use crate::migrations::information_schema::{ColumnMetadata, TableMetadata}; - use canyon_connection::lazy_static::lazy_static; + use canyon_core::lazy_static::lazy_static; lazy_static! { pub static ref TABLE_METADATA_LEAGUE_EX: TableMetadata = TableMetadata { diff --git a/canyon_migrations/src/lib.rs b/canyon_migrations/src/lib.rs index 5743cc8b..79988789 100644 --- a/canyon_migrations/src/lib.rs +++ b/canyon_migrations/src/lib.rs @@ -11,13 +11,12 @@ /// in order to perform the migrations pub mod migrations; -extern crate canyon_connection; extern crate canyon_crud; extern crate canyon_entities; mod constants; -use canyon_connection::lazy_static::lazy_static; +use canyon_core::lazy_static::lazy_static; use std::{collections::HashMap, sync::Mutex}; lazy_static! { diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index 5d2d5121..266a1251 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -1,11 +1,11 @@ -use canyon_connection::{ - datasources::Migrations as MigrationsStatus, db_connector::DatabaseConnection, DATASOURCES, -}; use canyon_core::{ column::Column, query::Transaction, row::{Row, RowOperations}, rows::CanyonRows, + connection::{ + datasources::Migrations as MigrationsStatus, db_connector::DatabaseConnection, DATASOURCES, + } }; use canyon_entities::CANYON_REGISTER_ENTITIES; use partialdebug::placeholder::PartialDebug; @@ -49,9 +49,9 @@ impl Migrations { ); let mut migrations_processor = MigrationsProcessor::default(); - let mut conn_cache = canyon_connection::CACHED_DATABASE_CONN.lock().await; - let db_conn = - canyon_connection::get_database_connection(&datasource.name, &mut conn_cache); + let mut conn_cache = canyon_core::connection::CACHED_DATABASE_CONN.lock().await; + let db_conn = // TODO: use the appropiated new way + canyon_core::connection::get_database_connection(&datasource.name, &mut conn_cache); let canyon_entities = CANYON_REGISTER_ENTITIES.lock().unwrap().to_vec(); let canyon_memory = CanyonMemory::remember(datasource, &canyon_entities).await; diff --git a/canyon_migrations/src/migrations/information_schema.rs b/canyon_migrations/src/migrations/information_schema.rs index 828fb8c7..dfcd5345 100644 --- a/canyon_migrations/src/migrations/information_schema.rs +++ b/canyon_migrations/src/migrations/information_schema.rs @@ -1,7 +1,7 @@ #[cfg(feature = "mssql")] -use canyon_connection::tiberius::ColumnType as TIB_TY; +use canyon_core::connection::tiberius::ColumnType as TIB_TY; // TODO: make them internal public reexports (only for Canyon) #[cfg(feature = "postgres")] -use canyon_connection::tokio_postgres::types::Type as TP_TYP; +use canyon_core::connection::tokio_postgres::types::Type as TP_TYP; use canyon_core::{ column::{Column, ColumnType}, row::{Row, RowOperations}, diff --git a/canyon_migrations/src/migrations/memory.rs b/canyon_migrations/src/migrations/memory.rs index 2131d2bd..4c2ea3a2 100644 --- a/canyon_migrations/src/migrations/memory.rs +++ b/canyon_migrations/src/migrations/memory.rs @@ -1,5 +1,5 @@ use crate::constants; -use canyon_connection::db_connector::DatabaseConnection; +use canyon_core::connection::db_connector::DatabaseConnection; use canyon_core::query::Transaction; use canyon_crud::{DatabaseType, DatasourceConfig}; use regex::Regex; @@ -66,8 +66,8 @@ impl CanyonMemory { ) -> Self { // TODO: can't we get the target DS while in the migrations at call site and avoid to // duplicate calls to the pool? - let mut conn_cache = canyon_connection::CACHED_DATABASE_CONN.lock().await; - let db_conn = canyon_connection::get_database_connection(&datasource.name, &mut conn_cache); + let mut conn_cache = canyon_core::connection::CACHED_DATABASE_CONN.lock().await; + let db_conn = canyon_core::connection::get_database_connection(&datasource.name, &mut conn_cache); // Creates the memory table if not exists Self::create_memory(&datasource.name, db_conn, &datasource.get_db_type()).await; diff --git a/canyon_migrations/src/migrations/processor.rs b/canyon_migrations/src/migrations/processor.rs index a4f15d87..25802eab 100644 --- a/canyon_migrations/src/migrations/processor.rs +++ b/canyon_migrations/src/migrations/processor.rs @@ -578,9 +578,9 @@ impl MigrationsProcessor { for query_to_execute in datasource.1 { let datasource_name = datasource.0; - let mut conn_cache = canyon_connection::CACHED_DATABASE_CONN.lock().await; + let mut conn_cache = canyon_core::connection::CACHED_DATABASE_CONN.lock().await; let db_conn = - canyon_connection::get_database_connection(datasource_name, &mut conn_cache); + canyon_core::connection::get_database_connection(datasource_name, &mut conn_cache); let res = Self::query(query_to_execute, [], db_conn).await; diff --git a/src/lib.rs b/src/lib.rs index 9ae9e30b..6c085d9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,4 @@ //! The root crate of the `Canyon-SQL` project. -extern crate canyon_connection; /// /// Here it's where all the available functionalities and features /// reaches the top most level, grouping them and making them visible @@ -30,15 +29,15 @@ pub mod macros { /// exposing them through the public API pub mod connection { #[cfg(feature = "postgres")] - pub use canyon_connection::db_connector::DatabaseConnection::Postgres; + pub use canyon_core::connection::db_connector::DatabaseConnection::Postgres; #[cfg(feature = "mssql")] - pub use canyon_connection::db_connector::DatabaseConnection::SqlServer; + pub use canyon_core::connection::db_connector::DatabaseConnection::SqlServer; #[cfg(feature = "mysql")] - pub use canyon_connection::db_connector::DatabaseConnection::MySQL; + pub use canyon_core::connection::db_connector::DatabaseConnection::MySQL; - pub use canyon_connection::*; + pub use canyon_core::connection::*; } pub mod core { @@ -66,20 +65,20 @@ pub mod query { /// Reexport the available database clients within Canyon pub mod db_clients { #[cfg(feature = "mysql")] - pub use canyon_connection::mysql_async; + pub use canyon_core::connection::mysql_async; #[cfg(feature = "mssql")] - pub use canyon_connection::tiberius; + pub use canyon_core::connection::tiberius; #[cfg(feature = "postgres")] - pub use canyon_connection::tokio_postgres; + pub use canyon_core::connection::tokio_postgres; } /// Reexport the needed runtime dependencies pub mod runtime { - pub use canyon_connection::futures; - pub use canyon_connection::init_connections_cache; - pub use canyon_connection::tokio; - pub use canyon_connection::tokio_util; - pub use canyon_connection::CANYON_TOKIO_RUNTIME; + pub use canyon_core::connection::futures; + pub use canyon_core::connection::init_connections_cache; + pub use canyon_core::connection::tokio; + pub use canyon_core::connection::tokio_util; + pub use canyon_core::connection::CANYON_TOKIO_RUNTIME; } /// Module for reexport the `chrono` crate with the allowed public and available types in Canyon From 9587d8492741d1a554a7af1f44fe713189a3a486 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 20 Jan 2025 16:06:00 +0100 Subject: [PATCH 024/155] feat: Allowing Transaction::query(...) to use DatasourceConfig types to fetch db_conn(s) --- canyon_core/src/query.rs | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index b73a6ca8..83fe3199 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -2,7 +2,7 @@ use std::fmt::Display; use async_trait::async_trait; -use crate::{connection::get_database_connection_by_ds, query_parameters::QueryParameter, rows::CanyonRows}; +use crate::{connection::{datasources::DatasourceConfig, db_connector::DatabaseConnection, get_database_connection_by_ds}, query_parameters::{self, QueryParameter}, rows::CanyonRows}; // TODO: in order to avoid the tiberius transmute, we should define other method that takes the db_conn as a mut ref @@ -36,25 +36,27 @@ pub trait Transaction { I: Into> + Sync + Send + 'a { let transaction_input= input.into(); + let statement = stmt.as_ref(); + let query_parameters = params.as_ref(); + match transaction_input { TransactionInput::DbConnection(conn) => { - conn.launch(stmt.as_ref(), params.as_ref()).await + conn.launch(statement, query_parameters).await } TransactionInput::DbConnectionRef(conn) => { - conn.launch(stmt.as_ref(), params.as_ref()).await + conn.launch(statement, query_parameters).await } TransactionInput::DbConnectionRefMut(/* TODO: mut*/ conn) => { - conn.launch(stmt.as_ref(), params.as_ref()).await + conn.launch(statement, query_parameters).await + } + TransactionInput::DatasourceConfig(ds) => { // TODO: add a new from_ds_config_mut for mssql + let conn = DatabaseConnection::new(&ds).await?; + conn.launch(statement, query_parameters).await } - // TransactionInput::DatasourceConfig(ds) => { - // let mut conn = DatabaseConnection::new(&ds).await?; - // conn.launch(stmt.as_ref(), params).await - // } TransactionInput::DatasourceName(ds_name) => { let conn = get_database_connection_by_ds(Some(ds_name)).await?; - conn.launch(stmt.as_ref(), params.as_ref()).await + conn.launch(statement, query_parameters).await } - // _ => todo!() } } } @@ -63,7 +65,7 @@ pub enum TransactionInput<'a, T: DbConnection> { DbConnection(T), DbConnectionRef(&'a T), DbConnectionRefMut(&'a mut T), - // DatasourceConfig(DatasourceConfig), + DatasourceConfig(&'a DatasourceConfig), DatasourceName(&'a str), } @@ -85,17 +87,11 @@ impl<'a, T: DbConnection> From<&'a mut T> for TransactionInput<'a, T> { } } -// impl From for TransactionInput { -// fn from(ds: DatasourceConfig) -> Self { -// TransactionInput::DatasourceConfig(ds) -// } -// } - -// impl<'a, T: DbConnection> From for TransactionInput<'a, T> { -// fn from(ds_name: String) -> TransactionInput<'a, T> { -// TransactionInput::DatasourceName(ds_name.as_str()) -// } -// } +impl<'a, T: DbConnection> From<&'a DatasourceConfig> for TransactionInput<'a, T> { + fn from(ds: &'a DatasourceConfig) -> Self { + TransactionInput::DatasourceConfig(ds) + } +} impl<'a, T: DbConnection> From<&'a str> for TransactionInput<'a, T> { fn from(ds_name: &'a str) -> Self { From d257aecc96cbc3b47decc85b0280176b51c4218d Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 20 Jan 2025 16:06:41 +0100 Subject: [PATCH 025/155] chore: removed unused trait Datasource for now --- canyon_core/src/query.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index 83fe3199..250e0733 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -16,8 +16,6 @@ pub trait DbConnection { ) -> Result>; } -pub trait Datasource: DbConnection{} - #[async_trait] pub trait Transaction { // provisional name From d01d0d1355607ba534b8b28bcff239b88291fe40 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 20 Jan 2025 17:11:24 +0100 Subject: [PATCH 026/155] feat(wip): disabling most of the CRUD operations to refactor them in a safe way, while removing unneded complexities and/or unneeded lifetime specifications --- canyon_core/src/query.rs | 32 +- canyon_crud/src/crud.rs | 84 +- canyon_macros/src/lib.rs | 120 +-- canyon_macros/src/query_operations/delete.rs | 136 +-- canyon_macros/src/query_operations/insert.rs | 402 ++++---- canyon_macros/src/query_operations/select.rs | 772 ++++++++-------- canyon_macros/src/query_operations/update.rs | 174 ++-- tests/crud/delete_operations.rs | 288 +++--- tests/crud/foreign_key_operations.rs | 326 +++---- tests/crud/insert_operations.rs | 582 ++++++------ tests/crud/querybuilder_operations.rs | 914 +++++++++---------- tests/crud/select_operations.rs | 207 ++--- tests/crud/update_operations.rs | 284 +++--- 13 files changed, 2164 insertions(+), 2157 deletions(-) diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index 250e0733..0676cf85 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -22,7 +22,7 @@ pub trait Transaction { /// Performs a query against the targeted database by the selected or /// the defaulted datasource, wrapping the resultant collection of entities /// in [`super::rows::CanyonRows`] - async fn query<'a, C, S, Z, I>( + async fn query<'a, S, Z, I>( stmt: S, params: Z, input: I, @@ -30,8 +30,7 @@ pub trait Transaction { where S: AsRef + Display + Sync + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, - C: DbConnection + Sync + Send + 'a, - I: Into> + Sync + Send + 'a + I: Into> + Sync + Send + 'a { let transaction_input= input.into(); let statement = stmt.as_ref(); @@ -52,46 +51,47 @@ pub trait Transaction { conn.launch(statement, query_parameters).await } TransactionInput::DatasourceName(ds_name) => { - let conn = get_database_connection_by_ds(Some(ds_name)).await?; + let sane_ds_name = if !ds_name.is_empty() { Some(ds_name) } else { None }; + let conn = get_database_connection_by_ds(sane_ds_name).await?; conn.launch(statement, query_parameters).await } } } } -pub enum TransactionInput<'a, T: DbConnection> { - DbConnection(T), - DbConnectionRef(&'a T), - DbConnectionRefMut(&'a mut T), +pub enum TransactionInput<'a> { + DbConnection(DatabaseConnection), + DbConnectionRef(&'a DatabaseConnection), + DbConnectionRefMut(&'a mut DatabaseConnection), DatasourceConfig(&'a DatasourceConfig), DatasourceName(&'a str), } -impl<'a, T: DbConnection> From for TransactionInput<'a, T> { - fn from(conn: T) -> Self { +impl<'a> From for TransactionInput<'a> { + fn from(conn: DatabaseConnection) -> Self { TransactionInput::DbConnection(conn) } } -impl<'a, T: DbConnection> From<&'a T> for TransactionInput<'a, T> { - fn from(conn: &'a T) -> Self { +impl<'a> From<&'a DatabaseConnection> for TransactionInput<'a> { + fn from(conn: &'a DatabaseConnection) -> Self { TransactionInput::DbConnectionRef(conn) } } -impl<'a, T: DbConnection> From<&'a mut T> for TransactionInput<'a, T> { - fn from(conn: &'a mut T) -> Self { +impl<'a> From<&'a mut DatabaseConnection> for TransactionInput<'a> { + fn from(conn: &'a mut DatabaseConnection) -> Self { TransactionInput::DbConnectionRefMut(conn) } } -impl<'a, T: DbConnection> From<&'a DatasourceConfig> for TransactionInput<'a, T> { +impl<'a> From<&'a DatasourceConfig> for TransactionInput<'a> { fn from(ds: &'a DatasourceConfig) -> Self { TransactionInput::DatasourceConfig(ds) } } -impl<'a, T: DbConnection> From<&'a str> for TransactionInput<'a, T> { +impl<'a> From<&'a str> for TransactionInput<'a> { fn from(ds_name: &'a str) -> Self { TransactionInput::DatasourceName(ds_name) } diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index ff9233f2..99e61ff4 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -26,70 +26,70 @@ pub trait CrudOperations: Transaction where T: CrudOperations + RowMapper, { - async fn find_all<'a>() -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>>; + async fn find_all() -> Result, Box<(dyn std::error::Error + Send + Sync)>>; async fn find_all_datasource<'a>( datasource_name: &'a str, - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>>; + ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>>; - async fn find_all_unchecked<'a>() -> Vec; + async fn find_all_unchecked() -> Vec; async fn find_all_unchecked_datasource<'a>(datasource_name: &'a str) -> Vec; - fn select_query<'a>() -> SelectQueryBuilder<'a, T>; + // fn select_query<'a>() -> SelectQueryBuilder<'a, T>; - fn select_query_datasource(datasource_name: &str) -> SelectQueryBuilder<'_, T>; + // fn select_query_datasource(datasource_name: &str) -> SelectQueryBuilder<'_, T>; - async fn count() -> Result>; + // async fn count<'a>() -> Result>; - async fn count_datasource<'a>( - datasource_name: &'a str, - ) -> Result>; + // async fn count_datasource<'a>( + // datasource_name: &'a str, + // ) -> Result>; - async fn find_by_pk<'a>( - value: &'a dyn QueryParameter<'a>, - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>>; + // async fn find_by_pk<'a>( + // value: &'a dyn QueryParameter<'a>, + // ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>>; - async fn find_by_pk_datasource<'a>( - value: &'a dyn QueryParameter<'a>, - datasource_name: &'a str, - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>>; + // async fn find_by_pk_datasource<'a>( + // value: &'a dyn QueryParameter<'a>, + // datasource_name: &'a str, + // ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>>; - async fn insert<'a>(&mut self) -> Result<(), Box>; + // async fn insert<'a>(&mut self) -> Result<(), Box>; - async fn insert_datasource<'a>( - &mut self, - datasource_name: &'a str, - ) -> Result<(), Box>; + // async fn insert_datasource<'a>( + // &mut self, + // datasource_name: &'a str, + // ) -> Result<(), Box>; - async fn multi_insert<'a>( - instances: &'a mut [&'a mut T], - ) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'static)>>; + // async fn multi_insert<'a>( + // instances: &'a mut [&'a mut T], + // ) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'a)>>; - async fn multi_insert_datasource<'a>( - instances: &'a mut [&'a mut T], - datasource_name: &'a str, - ) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'static)>>; + // async fn multi_insert_datasource<'a>( + // instances: &'a mut [&'a mut T], + // datasource_name: &'a str, + // ) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'a)>>; - async fn update(&self) -> Result<(), Box>; + // async fn update(&self) -> Result<(), Box>; - async fn update_datasource<'a>( - &self, - datasource_name: &'a str, - ) -> Result<(), Box>; + // async fn update_datasource<'a>( + // &self, + // datasource_name: &'a str, + // ) -> Result<(), Box>; - fn update_query<'a>() -> UpdateQueryBuilder<'a, T>; + // fn update_query<'a>() -> UpdateQueryBuilder<'a, T>; - fn update_query_datasource(datasource_name: &str) -> UpdateQueryBuilder<'_, T>; + // fn update_query_datasource(datasource_name: &str) -> UpdateQueryBuilder<'_, T>; - async fn delete(&self) -> Result<(), Box>; + // async fn delete(&self) -> Result<(), Box>; - async fn delete_datasource<'a>( - &self, - datasource_name: &'a str, - ) -> Result<(), Box>; + // async fn delete_datasource<'a>( + // &self, + // datasource_name: &'a str, + // ) -> Result<(), Box>; - fn delete_query<'a>() -> DeleteQueryBuilder<'a, T>; + // fn delete_query<'a>() -> DeleteQueryBuilder<'a, T>; - fn delete_query_datasource(datasource_name: &str) -> DeleteQueryBuilder<'_, T>; + // fn delete_query_datasource(datasource_name: &str) -> DeleteQueryBuilder<'_, T>; } diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index 03da45b5..80096bf9 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -1,5 +1,7 @@ +#![allow(dead_code)] extern crate proc_macro; + mod canyon_entity_macro; #[cfg(feature = "migrations")] use canyon_macro::main_with_queries; @@ -18,9 +20,11 @@ use query_operations::{ delete::{generate_delete_query_tokens, generate_delete_tokens}, insert::{generate_insert_tokens, generate_multiple_insert_tokens}, select::{ - generate_count_tokens, generate_find_all_query_tokens, generate_find_all_tokens, - generate_find_all_unchecked_tokens, generate_find_by_foreign_key_tokens, - generate_find_by_pk_tokens, generate_find_by_reverse_foreign_key_tokens, + // generate_count_tokens, generate_find_all_query_tokens, + generate_find_all_tokens, + generate_find_all_unchecked_tokens, + // generate_find_by_foreign_key_tokens, + // generate_find_by_pk_tokens, generate_find_by_reverse_foreign_key_tokens, }, update::{generate_update_query_tokens, generate_update_tokens}, }; @@ -249,13 +253,13 @@ fn impl_crud_operations_trait_for_struct( // Builds the find_all_result() query let _find_all_tokens = generate_find_all_tokens(macro_data, &table_schema_data); // Builds the find_all_query() query as a QueryBuilder - let _find_all_query_tokens = generate_find_all_query_tokens(macro_data, &table_schema_data); + // let _find_all_query_tokens = generate_find_all_query_tokens(macro_data, &table_schema_data); // Builds a COUNT(*) query over some table - let _count_tokens = generate_count_tokens(macro_data, &table_schema_data); + // let _count_tokens = generate_count_tokens(macro_data, &table_schema_data); // Builds the find_by_pk() query - let _find_by_pk_tokens = generate_find_by_pk_tokens(macro_data, &table_schema_data); + // let _find_by_pk_tokens = generate_find_by_pk_tokens(macro_data, &table_schema_data); // Builds the insert() query let _insert_tokens = generate_insert_tokens(macro_data, &table_schema_data); @@ -273,19 +277,19 @@ fn impl_crud_operations_trait_for_struct( // Builds the delete() query as a QueryBuilder let _delete_query_tokens = generate_delete_query_tokens(macro_data, &table_schema_data); - // Search by foreign (d) key as Vec, cause Canyon supports multiple fields having FK annotation - let _search_by_fk_tokens: Vec<(TokenStream, TokenStream)> = - generate_find_by_foreign_key_tokens(macro_data); - let fk_method_signatures = _search_by_fk_tokens.iter().map(|(sign, _)| sign); - let fk_method_implementations = _search_by_fk_tokens.iter().map(|(_, m_impl)| m_impl); + // // Search by foreign (d) key as Vec, cause Canyon supports multiple fields having FK annotation + // let _search_by_fk_tokens: Vec<(TokenStream, TokenStream)> = + // generate_find_by_foreign_key_tokens(macro_data); + // let fk_method_signatures = _search_by_fk_tokens.iter().map(|(sign, _)| sign); + // let fk_method_implementations = _search_by_fk_tokens.iter().map(|(_, m_impl)| m_impl); - // The tokens for generating the methods that enable Canyon to retrieve the child entities that are of T type - // given a parent entity U: ForeignKeyable, as an associated function for the child type (T) - let _search_by_revese_fk_tokens: Vec<(TokenStream, TokenStream)> = - generate_find_by_reverse_foreign_key_tokens(macro_data, &table_schema_data); - let rev_fk_method_signatures = _search_by_revese_fk_tokens.iter().map(|(sign, _)| sign); - let rev_fk_method_implementations = - _search_by_revese_fk_tokens.iter().map(|(_, m_impl)| m_impl); + // // The tokens for generating the methods that enable Canyon to retrieve the child entities that are of T type + // // given a parent entity U: ForeignKeyable, as an associated function for the child type (T) + // let _search_by_revese_fk_tokens: Vec<(TokenStream, TokenStream)> = + // generate_find_by_reverse_foreign_key_tokens(macro_data, &table_schema_data); + // let rev_fk_method_signatures = _search_by_revese_fk_tokens.iter().map(|(sign, _)| sign); + // let rev_fk_method_implementations = + // _search_by_revese_fk_tokens.iter().map(|(_, m_impl)| m_impl); // The autogenerated name for the trait that holds the fk and rev fk searches let fk_trait_ident = Ident::new( @@ -300,35 +304,37 @@ fn impl_crud_operations_trait_for_struct( // The find_all impl #_find_all_unchecked_tokens - // The find_all_query impl - #_find_all_query_tokens + // // The find_all_query impl + // #_find_all_query_tokens - // The COUNT(*) impl - #_count_tokens + // // The COUNT(*) impl + // #_count_tokens - // The find_by_pk impl - #_find_by_pk_tokens + // // The find_by_pk impl + // #_find_by_pk_tokens - // The insert impl - #_insert_tokens + // // The insert impl + // #_insert_tokens - // The insert of multiple entities impl - #_insert_multi_tokens + // // The insert of multiple entities impl + // #_insert_multi_tokens - // The update impl - #_update_tokens + // // The update impl + // #_update_tokens - // The update as a querybuilder impl - #_update_query_tokens + // // The update as a querybuilder impl + // #_update_query_tokens - // The delete impl - #_delete_tokens + // // The delete impl + // #_delete_tokens - // The delete as querybuilder impl - #_delete_query_tokens + // // The delete as querybuilder impl + // #_delete_query_tokens }; - let tokens = if !_search_by_fk_tokens.is_empty() { + // let tokens = if !_search_by_fk_tokens.is_empty() { + let a: Vec = vec![]; + let tokens = if a.is_empty() { quote! { #[canyon_sql::macros::async_trait] impl canyon_sql::crud::CrudOperations<#ty> for #ty { @@ -337,26 +343,26 @@ fn impl_crud_operations_trait_for_struct( impl canyon_sql::core::Transaction<#ty> for #ty {} - /// Hidden trait for generate the foreign key operations available - /// in Canyon without have to define them before hand in CrudOperations - /// because it's just impossible with the actual system (where the methods - /// are generated dynamically based on some properties of the `foreign_key` - /// annotation) - #[canyon_sql::macros::async_trait] - pub trait #fk_trait_ident<#ty> { - #(#fk_method_signatures)* - #(#rev_fk_method_signatures)* - } - #[canyon_sql::macros::async_trait] - impl #fk_trait_ident<#ty> for #ty - where #ty: - std::fmt::Debug + - canyon_sql::crud::CrudOperations<#ty> + - canyon_sql::core::RowMapper<#ty> - { - #(#fk_method_implementations)* - #(#rev_fk_method_implementations)* - } + // /// Hidden trait for generate the foreign key operations available + // /// in Canyon without have to define them before hand in CrudOperations + // /// because it's just impossible with the actual system (where the methods + // /// are generated dynamically based on some properties of the `foreign_key` + // /// annotation) + // #[canyon_sql::macros::async_trait] + // pub trait #fk_trait_ident<#ty> { + // #(#fk_method_signatures)* + // #(#rev_fk_method_signatures)* + // } + // #[canyon_sql::macros::async_trait] + // impl #fk_trait_ident<#ty> for #ty + // where #ty: + // std::fmt::Debug + + // canyon_sql::crud::CrudOperations<#ty> + + // canyon_sql::core::RowMapper<#ty> + // { + // #(#fk_method_implementations)* + // #(#rev_fk_method_implementations)* + // } } } else { quote! { diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index a31dcfb6..2594b3ea 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -22,59 +22,59 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri quote! { &self.#pk_field as &dyn canyon_sql::core::QueryParameter<'_> }; quote! { - /// Deletes from a database entity the row that matches - /// the current instance of a T type, returning a result - /// indicating a possible failure querying the database. - async fn delete(&self) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'static)>> { - <#ty as canyon_sql::core::Transaction<#ty>>::query( - format!("DELETE FROM {} WHERE {:?} = $1", #table_schema_data, #primary_key), - &[#pk_field_value], - "" - ).await?; + // /// Deletes from a database entity the row that matches + // /// the current instance of a T type, returning a result + // /// indicating a possible failure querying the database. + // async fn delete(&self) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'static)>> { + // <#ty as canyon_sql::core::Transaction<#ty>>::query( + // format!("DELETE FROM {} WHERE {:?} = $1", #table_schema_data, #primary_key), + // &[#pk_field_value], + // "" + // ).await?; - Ok(()) - } + // Ok(()) + // } - /// Deletes from a database entity the row that matches - /// the current instance of a T type, returning a result - /// indicating a possible failure querying the database with the specified datasource. - async fn delete_datasource<'a>(&self, datasource_name: &'a str) - -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'static)>> - { - <#ty as canyon_sql::core::Transaction<#ty>>::query( - format!("DELETE FROM {} WHERE {:?} = $1", #table_schema_data, #primary_key), - &[#pk_field_value], - datasource_name - ).await?; + // /// Deletes from a database entity the row that matches + // /// the current instance of a T type, returning a result + // /// indicating a possible failure querying the database with the specified datasource. + // async fn delete_datasource<'a>(&self, datasource_name: &'a str) + // -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'static)>> + // { + // <#ty as canyon_sql::core::Transaction<#ty>>::query( + // format!("DELETE FROM {} WHERE {:?} = $1", #table_schema_data, #primary_key), + // &[#pk_field_value], + // datasource_name + // ).await?; - Ok(()) - } + // Ok(()) + // } } } else { // Delete operation over an instance isn't available without declaring a primary key. // The delete querybuilder variant must be used for the case when there's no pk declared quote! { - async fn delete(&self) - -> Result<(), Box> - { - Err(std::io::Error::new( - std::io::ErrorKind::Unsupported, - "You can't use the 'delete' method on a \ - CanyonEntity that does not have a #[primary_key] annotation. \ - If you need to perform an specific search, use the Querybuilder instead." - ).into_inner().unwrap()) - } + // async fn delete(&self) + // -> Result<(), Box> + // { + // Err(std::io::Error::new( + // std::io::ErrorKind::Unsupported, + // "You can't use the 'delete' method on a \ + // CanyonEntity that does not have a #[primary_key] annotation. \ + // If you need to perform an specific search, use the Querybuilder instead." + // ).into_inner().unwrap()) + // } - async fn delete_datasource<'a>(&self, datasource_name: &'a str) - -> Result<(), Box> - { - Err(std::io::Error::new( - std::io::ErrorKind::Unsupported, - "You can't use the 'delete_datasource' method on a \ - CanyonEntity that does not have a #[primary_key] annotation. \ - If you need to perform an specific search, use the Querybuilder instead." - ).into_inner().unwrap()) - } + // async fn delete_datasource<'a>(&self, datasource_name: &'a str) + // -> Result<(), Box> + // { + // Err(std::io::Error::new( + // std::io::ErrorKind::Unsupported, + // "You can't use the 'delete_datasource' method on a \ + // CanyonEntity that does not have a #[primary_key] annotation. \ + // If you need to perform an specific search, use the Querybuilder instead." + // ).into_inner().unwrap()) + // } } } } @@ -88,30 +88,30 @@ pub fn generate_delete_query_tokens( let ty = macro_data.ty; quote! { - /// Generates a [`canyon_sql::query::DeleteQueryBuilder`] - /// that allows you to customize the query by adding parameters and constrains dynamically. - /// - /// It performs an `DELETE FROM table_name`, where `table_name` it's the name of your - /// entity but converted to the corresponding database convention, - /// unless concrete values are set on the available parameters of the - /// `canyon_macro(table_name = "table_name", schema = "schema")` - fn delete_query<'a>() -> canyon_sql::query::DeleteQueryBuilder<'a, #ty> { - canyon_sql::query::DeleteQueryBuilder::new(#table_schema_data, "") - } + // /// Generates a [`canyon_sql::query::DeleteQueryBuilder`] + // /// that allows you to customize the query by adding parameters and constrains dynamically. + // /// + // /// It performs an `DELETE FROM table_name`, where `table_name` it's the name of your + // /// entity but converted to the corresponding database convention, + // /// unless concrete values are set on the available parameters of the + // /// `canyon_macro(table_name = "table_name", schema = "schema")` + // fn delete_query<'a>() -> canyon_sql::query::DeleteQueryBuilder<'a, #ty> { + // canyon_sql::query::DeleteQueryBuilder::new(#table_schema_data, "") + // } - /// Generates a [`canyon_sql::query::DeleteQueryBuilder`] - /// that allows you to customize the query by adding parameters and constrains dynamically. - /// - /// It performs an `DELETE FROM table_name`, where `table_name` it's the name of your - /// entity but converted to the corresponding database convention, - /// unless concrete values are set on the available parameters of the - /// `canyon_macro(table_name = "table_name", schema = "schema")` - /// - /// The query it's made against the database with the configured datasource - /// described in the configuration file, and selected with the [`&str`] - /// passed as parameter. - fn delete_query_datasource<'a>(datasource_name: &'a str) -> canyon_sql::query::DeleteQueryBuilder<'a, #ty> { - canyon_sql::query::DeleteQueryBuilder::new(#table_schema_data, datasource_name) - } + // /// Generates a [`canyon_sql::query::DeleteQueryBuilder`] + // /// that allows you to customize the query by adding parameters and constrains dynamically. + // /// + // /// It performs an `DELETE FROM table_name`, where `table_name` it's the name of your + // /// entity but converted to the corresponding database convention, + // /// unless concrete values are set on the available parameters of the + // /// `canyon_macro(table_name = "table_name", schema = "schema")` + // /// + // /// The query it's made against the database with the configured datasource + // /// described in the configuration file, and selected with the [`&str`] + // /// passed as parameter. + // fn delete_query_datasource<'a>(datasource_name: &'a str) -> canyon_sql::query::DeleteQueryBuilder<'a, #ty> { + // canyon_sql::query::DeleteQueryBuilder::new(#table_schema_data, datasource_name) + // } } } diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index a3d03311..bface463 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -106,96 +106,96 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri }; quote! { - /// Inserts into a database entity the current data in `self`, generating a new - /// entry (row), returning the `PRIMARY KEY` = `self.` with the specified - /// datasource by it's `datasouce name`, defined in the configuration file. - /// - /// This `insert` operation needs a `&mut` reference. That's because typically, - /// an insert operation represents *new* data stored in the database, so, when - /// inserted, the database will generate a unique new value for the - /// `pk` field, having a unique identifier for every record, and it will - /// automatically assign that returned pk to `self.`. So, after the `insert` - /// operation, you instance will have the correct value that is the *PRIMARY KEY* - /// of the database row that represents. - /// - /// This operation returns a result type, indicating a possible failure querying the database. - /// - /// ## *Examples* - ///``` - /// let mut lec: League = League { - /// id: Default::default(), - /// ext_id: 1, - /// slug: "LEC".to_string(), - /// name: "League Europe Champions".to_string(), - /// region: "EU West".to_string(), - /// image_url: "https://lec.eu".to_string(), - /// }; - /// - /// println!("LEC before: {:?}", &lec); - /// - /// let ins_result = lec.insert_result().await; - /// - /// Now, we can handle the result returned, because it can contains a - /// critical error that may leads your program to panic - /// if let Ok(_) = ins_result { - /// println!("LEC after: {:?}", &lec); - /// } else { - /// eprintln!("{:?}", ins_result.err()) - /// } - /// ``` - /// - async fn insert<'a>(&mut self) - -> Result<(), Box> - { - let datasource_name = ""; - let mut values: Vec<&dyn canyon_sql::core::QueryParameter<'_>> = vec![#(#insert_values),*]; - #insert_transaction - } - - /// Inserts into a database entity the current data in `self`, generating a new - /// entry (row), returning the `PRIMARY KEY` = `self.` with the specified - /// datasource by it's `datasouce name`, defined in the configuration file. - /// - /// This `insert` operation needs a `&mut` reference. That's because typically, - /// an insert operation represents *new* data stored in the database, so, when - /// inserted, the database will generate a unique new value for the - /// `pk` field, having a unique identifier for every record, and it will - /// automatically assign that returned pk to `self.`. So, after the `insert` - /// operation, you instance will have the correct value that is the *PRIMARY KEY* - /// of the database row that represents. - /// - /// This operation returns a result type, indicating a possible failure querying the database. - /// - /// ## *Examples* - ///``` - /// let mut lec: League = League { - /// id: Default::default(), - /// ext_id: 1, - /// slug: "LEC".to_string(), - /// name: "League Europe Champions".to_string(), - /// region: "EU West".to_string(), - /// image_url: "https://lec.eu".to_string(), - /// }; - /// - /// println!("LEC before: {:?}", &lec); - /// - /// let ins_result = lec.insert_result().await; - /// - /// Now, we can handle the result returned, because it can contains a - /// critical error that may leads your program to panic - /// if let Ok(_) = ins_result { - /// println!("LEC after: {:?}", &lec); - /// } else { - /// eprintln!("{:?}", ins_result.err()) - /// } - /// ``` - /// - async fn insert_datasource<'a>(&mut self, datasource_name: &'a str) - -> Result<(), Box> - { - let mut values: Vec<&dyn canyon_sql::core::QueryParameter<'_>> = vec![#(#insert_values_cloned),*]; - #insert_transaction - } + // / Inserts into a database entity the current data in `self`, generating a new + // / entry (row), returning the `PRIMARY KEY` = `self.` with the specified + // / datasource by it's `datasouce name`, defined in the configuration file. + // / + // / This `insert` operation needs a `&mut` reference. That's because typically, + // / an insert operation represents *new* data stored in the database, so, when + // / inserted, the database will generate a unique new value for the + // / `pk` field, having a unique identifier for every record, and it will + // / automatically assign that returned pk to `self.`. So, after the `insert` + // / operation, you instance will have the correct value that is the *PRIMARY KEY* + // / of the database row that represents. + // / + // / This operation returns a result type, indicating a possible failure querying the database. + // / + // / ## *Examples* + // /``` + // / let mut lec: League = League { + // / id: Default::default(), + // / ext_id: 1, + // / slug: "LEC".to_string(), + // / name: "League Europe Champions".to_string(), + // / region: "EU West".to_string(), + // / image_url: "https://lec.eu".to_string(), + // / }; + // / + // / println!("LEC before: {:?}", &lec); + // / + // / let ins_result = lec.insert_result().await; + // / + // / Now, we can handle the result returned, because it can contains a + // / critical error that may leads your program to panic + // / if let Ok(_) = ins_result { + // / println!("LEC after: {:?}", &lec); + // / } else { + // / eprintln!("{:?}", ins_result.err()) + // / } + // / ``` + // / + // async fn insert<'a>(&mut self) + // -> Result<(), Box> + // { + // let datasource_name = ""; + // let mut values: Vec<&dyn canyon_sql::core::QueryParameter<'_>> = vec![#(#insert_values),*]; + // #insert_transaction + // } + + // / Inserts into a database entity the current data in `self`, generating a new + // / entry (row), returning the `PRIMARY KEY` = `self.` with the specified + // / datasource by it's `datasouce name`, defined in the configuration file. + // / + // / This `insert` operation needs a `&mut` reference. That's because typically, + // / an insert operation represents *new* data stored in the database, so, when + // / inserted, the database will generate a unique new value for the + // / `pk` field, having a unique identifier for every record, and it will + // / automatically assign that returned pk to `self.`. So, after the `insert` + // / operation, you instance will have the correct value that is the *PRIMARY KEY* + // / of the database row that represents. + // / + // / This operation returns a result type, indicating a possible failure querying the database. + // / + // / ## *Examples* + // /``` + // / let mut lec: League = League { + // / id: Default::default(), + // / ext_id: 1, + // / slug: "LEC".to_string(), + // / name: "League Europe Champions".to_string(), + // / region: "EU West".to_string(), + // / image_url: "https://lec.eu".to_string(), + // / }; + // / + // / println!("LEC before: {:?}", &lec); + // / + // / let ins_result = lec.insert_result().await; + // / + // / Now, we can handle the result returned, because it can contains a + // / critical error that may leads your program to panic + // / if let Ok(_) = ins_result { + // / println!("LEC after: {:?}", &lec); + // / } else { + // / eprintln!("{:?}", ins_result.err()) + // / } + // / ``` + // / + // async fn insert_datasource<'a>(&mut self, datasource_name: &'a str) + // -> Result<(), Box> + // { + // let mut values: Vec<&dyn canyon_sql::core::QueryParameter<'_>> = vec![#(#insert_values_cloned),*]; + // #insert_transaction + // } } } @@ -404,116 +404,116 @@ pub fn generate_multiple_insert_tokens( }; quote! { - /// Inserts multiple instances of some type `T` into its related table. - /// - /// ``` - /// let mut new_league = League { - /// id: Default::default(), - /// ext_id: 392489032, - /// slug: "League10".to_owned(), - /// name: "League10also".to_owned(), - /// region: "Turkey".to_owned(), - /// image_url: "https://www.sdklafjsd.com".to_owned() - /// }; - /// let mut new_league2 = League { - /// id: Default::default(), - /// ext_id: 392489032, - /// slug: "League11".to_owned(), - /// name: "League11also".to_owned(), - /// region: "LDASKJF".to_owned(), - /// image_url: "https://www.sdklafjsd.com".to_owned() - /// }; - /// let mut new_league3 = League { - /// id: Default::default(), - /// ext_id: 9687392489032, - /// slug: "League3".to_owned(), - /// name: "3League".to_owned(), - /// region: "EU".to_owned(), - /// image_url: "https://www.lag.com".to_owned() - /// }; - /// - /// League::insert_multiple( - /// &mut [&mut new_league, &mut new_league2, &mut new_league3] - /// ).await - /// .ok(); - /// ``` - async fn multi_insert<'a>(instances: &'a mut [&'a mut #ty]) -> ( - Result<(), Box> - ) { - use canyon_sql::core::QueryParameter; - let datasource_name = ""; - - let mut final_values: Vec>> = Vec::new(); - for instance in instances.iter() { - let intermediate: &[&dyn QueryParameter<'_>] = &[#(#macro_fields),*]; - - let mut longer_lived: Vec<&dyn QueryParameter<'_>> = Vec::new(); - for value in intermediate.into_iter() { - longer_lived.push(*value) - } - - final_values.push(longer_lived) - } - - let mut mapped_fields: String = String::new(); - - #multi_insert_transaction - } - - /// Inserts multiple instances of some type `T` into its related table with the specified - /// datasource by it's `datasouce name`, defined in the configuration file. - /// - /// ``` - /// let mut new_league = League { - /// id: Default::default(), - /// ext_id: 392489032, - /// slug: "League10".to_owned(), - /// name: "League10also".to_owned(), - /// region: "Turkey".to_owned(), - /// image_url: "https://www.sdklafjsd.com".to_owned() - /// }; - /// let mut new_league2 = League { - /// id: Default::default(), - /// ext_id: 392489032, - /// slug: "League11".to_owned(), - /// name: "League11also".to_owned(), - /// region: "LDASKJF".to_owned(), - /// image_url: "https://www.sdklafjsd.com".to_owned() - /// }; - /// let mut new_league3 = League { - /// id: Default::default(), - /// ext_id: 9687392489032, - /// slug: "League3".to_owned(), - /// name: "3League".to_owned(), - /// region: "EU".to_owned(), - /// image_url: "https://www.lag.com".to_owned() - /// }; - /// - /// League::insert_multiple( - /// &mut [&mut new_league, &mut new_league2, &mut new_league3] - /// ).await - /// .ok(); - /// ``` - async fn multi_insert_datasource<'a>(instances: &'a mut [&'a mut #ty], datasource_name: &'a str) -> ( - Result<(), Box> - ) { - use canyon_sql::core::QueryParameter; - - let mut final_values: Vec>> = Vec::new(); - for instance in instances.iter() { - let intermediate: &[&dyn QueryParameter<'_>] = &[#(#macro_fields_cloned),*]; - - let mut longer_lived: Vec<&dyn QueryParameter<'_>> = Vec::new(); - for value in intermediate.into_iter() { - longer_lived.push(*value) - } - - final_values.push(longer_lived) - } - - let mut mapped_fields: String = String::new(); - - #multi_insert_transaction - } + // /// Inserts multiple instances of some type `T` into its related table. + // /// + // /// ``` + // /// let mut new_league = League { + // /// id: Default::default(), + // /// ext_id: 392489032, + // /// slug: "League10".to_owned(), + // /// name: "League10also".to_owned(), + // /// region: "Turkey".to_owned(), + // /// image_url: "https://www.sdklafjsd.com".to_owned() + // /// }; + // /// let mut new_league2 = League { + // /// id: Default::default(), + // /// ext_id: 392489032, + // /// slug: "League11".to_owned(), + // /// name: "League11also".to_owned(), + // /// region: "LDASKJF".to_owned(), + // /// image_url: "https://www.sdklafjsd.com".to_owned() + // /// }; + // /// let mut new_league3 = League { + // /// id: Default::default(), + // /// ext_id: 9687392489032, + // /// slug: "League3".to_owned(), + // /// name: "3League".to_owned(), + // /// region: "EU".to_owned(), + // /// image_url: "https://www.lag.com".to_owned() + // /// }; + // /// + // /// League::insert_multiple( + // /// &mut [&mut new_league, &mut new_league2, &mut new_league3] + // /// ).await + // /// .ok(); + // /// ``` + // // async fn multi_insert<'a>(instances: &'a mut [&'a mut #ty]) -> ( + // // Result<(), Box> + // // ) { + // // use canyon_sql::core::QueryParameter; + // // let datasource_name = ""; + + // // let mut final_values: Vec>> = Vec::new(); + // // for instance in instances.iter() { + // // let intermediate: &[&dyn QueryParameter<'_>] = &[#(#macro_fields),*]; + + // // let mut longer_lived: Vec<&dyn QueryParameter<'_>> = Vec::new(); + // // for value in intermediate.into_iter() { + // // longer_lived.push(*value) + // // } + + // // final_values.push(longer_lived) + // // } + + // // let mut mapped_fields: String = String::new(); + + // // #multi_insert_transaction + // // } + + // /// Inserts multiple instances of some type `T` into its related table with the specified + // /// datasource by it's `datasouce name`, defined in the configuration file. + // /// + // /// ``` + // /// let mut new_league = League { + // /// id: Default::default(), + // /// ext_id: 392489032, + // /// slug: "League10".to_owned(), + // /// name: "League10also".to_owned(), + // /// region: "Turkey".to_owned(), + // /// image_url: "https://www.sdklafjsd.com".to_owned() + // /// }; + // /// let mut new_league2 = League { + // /// id: Default::default(), + // /// ext_id: 392489032, + // /// slug: "League11".to_owned(), + // /// name: "League11also".to_owned(), + // /// region: "LDASKJF".to_owned(), + // /// image_url: "https://www.sdklafjsd.com".to_owned() + // /// }; + // /// let mut new_league3 = League { + // /// id: Default::default(), + // /// ext_id: 9687392489032, + // /// slug: "League3".to_owned(), + // /// name: "3League".to_owned(), + // /// region: "EU".to_owned(), + // /// image_url: "https://www.lag.com".to_owned() + // /// }; + // /// + // /// League::insert_multiple( + // /// &mut [&mut new_league, &mut new_league2, &mut new_league3] + // /// ).await + // /// .ok(); + // /// ``` + // async fn multi_insert_datasource<'a>(instances: &'a mut [&'a mut #ty], datasource_name: &'a str) -> ( + // Result<(), Box> + // ) { + // use canyon_sql::core::QueryParameter; + + // let mut final_values: Vec>> = Vec::new(); + // for instance in instances.iter() { + // let intermediate: &[&dyn QueryParameter<'_>] = &[#(#macro_fields_cloned),*]; + + // let mut longer_lived: Vec<&dyn QueryParameter<'_>> = Vec::new(); + // for value in intermediate.into_iter() { + // longer_lived.push(*value) + // } + + // final_values.push(longer_lived) + // } + + // let mut mapped_fields: String = String::new(); + + // #multi_insert_transaction + // } } } diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index 82f718e9..a330ee90 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -20,7 +20,7 @@ pub fn generate_find_all_unchecked_tokens( /// the name of your entity but converted to the corresponding /// database convention. P.ej. PostgreSQL prefers table names declared /// with snake_case identifiers. - async fn find_all_unchecked<'a>() -> Vec<#ty> { + async fn find_all_unchecked() -> Vec<#ty> { <#ty as canyon_sql::core::Transaction<#ty>>::query( #stmt, &[], @@ -64,8 +64,8 @@ pub fn generate_find_all_tokens( /// the name of your entity but converted to the corresponding /// database convention. P.ej. PostgreSQL prefers table names declared /// with snake_case identifiers. - async fn find_all<'a>() -> - Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> + async fn find_all() -> + Result, Box<(dyn std::error::Error + Send + Sync)>> { Ok( <#ty as canyon_sql::core::Transaction<#ty>>::query( @@ -90,7 +90,7 @@ pub fn generate_find_all_tokens( /// querying the database, or, if no errors happens, a Vec containing /// the data found. async fn find_all_datasource<'a>(datasource_name: &'a str) -> - Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> + Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> { Ok( <#ty as canyon_sql::core::Transaction<#ty>>::query( @@ -104,385 +104,385 @@ pub fn generate_find_all_tokens( } } -/// Same as above, but with a [`canyon_sql::query::QueryBuilder`] -pub fn generate_find_all_query_tokens( - macro_data: &MacroTokens<'_>, - table_schema_data: &String, -) -> TokenStream { - let ty = macro_data.ty; - - quote! { - /// Generates a [`canyon_sql::query::SelectQueryBuilder`] - /// that allows you to customize the query by adding parameters and constrains dynamically. - /// - /// It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your - /// entity but converted to the corresponding database convention, - /// unless concrete values are set on the available parameters of the - /// `canyon_macro(table_name = "table_name", schema = "schema")` - fn select_query<'a>() -> canyon_sql::query::SelectQueryBuilder<'a, #ty> { - canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, "") - } - - /// Generates a [`canyon_sql::query::SelectQueryBuilder`] - /// that allows you to customize the query by adding parameters and constrains dynamically. - /// - /// It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your - /// entity but converted to the corresponding database convention, - /// unless concrete values are set on the available parameters of the - /// `canyon_macro(table_name = "table_name", schema = "schema")` - /// - /// The query it's made against the database with the configured datasource - /// described in the configuration file, and selected with the [`&str`] - /// passed as parameter. - fn select_query_datasource<'a>(datasource_name: &'a str) -> canyon_sql::query::SelectQueryBuilder<'a, #ty> { - canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, datasource_name) - } - } -} - -/// Performs a COUNT(*) query over some table, returning a [`Result`] wrapping -/// a possible success or error coming from the database -pub fn generate_count_tokens( - macro_data: &MacroTokens<'_>, - table_schema_data: &String, -) -> TokenStream { - let ty = macro_data.ty; - let ty_str = &ty.to_string(); - let stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); - - let result_handling = quote! { - #[cfg(feature="postgres")] - canyon_sql::core::CanyonRows::Postgres(mut v) => Ok( - v.remove(0).get::<&str, i64>("count") - ), - #[cfg(feature="mssql")] - canyon_sql::core::CanyonRows::Tiberius(mut v) => - v.remove(0) - .get::(0) - .map(|c| c as i64) - .ok_or(format!("Failure in the COUNT query for MSSQL for: {}", #ty_str).into()) - .into(), - #[cfg(feature="mysql")] - canyon_sql::core::CanyonRows::MySQL(mut v) => v.remove(0) - .get::(0) - .ok_or(format!("Failure in the COUNT query for MYSQL for: {}", #ty_str).into()), - _ => panic!() // TODO remove when the generics will be refactored - }; - - quote! { - /// Performs a COUNT(*) query over some table, returning a [`Result`] rather than panicking, - /// wrapping a possible success or error coming from the database - async fn count() -> Result> { - let count = <#ty as canyon_sql::core::Transaction<#ty>>::query( - #stmt, - &[], - "" - ).await?; - - match count { - #result_handling - } - } - - /// Performs a COUNT(*) query over some table, returning a [`Result`] rather than panicking, - /// wrapping a possible success or error coming from the database with the specified datasource - async fn count_datasource<'a>(datasource_name: &'a str) -> Result> { - let count = <#ty as canyon_sql::core::Transaction<#ty>>::query( - #stmt, - &[], - datasource_name - ).await?; - - match count { - #result_handling - } - } - } -} - -/// Generates the TokenStream for build the __find_by_pk() CRUD operation -pub fn generate_find_by_pk_tokens( - macro_data: &MacroTokens<'_>, - table_schema_data: &String, -) -> TokenStream { - let ty = macro_data.ty; - let pk = macro_data.get_primary_key_annotation().unwrap_or_default(); - let stmt = format!("SELECT * FROM {table_schema_data} WHERE {pk} = $1"); - - // Disabled if there's no `primary_key` annotation - if pk.is_empty() { - return quote! { - async fn find_by_pk<'a>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) - -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> - { - Err( - std::io::Error::new( - std::io::ErrorKind::Unsupported, - "You can't use the 'find_by_pk' associated function on a \ - CanyonEntity that does not have a #[primary_key] annotation. \ - If you need to perform an specific search, use the Querybuilder instead." - ).into_inner().unwrap() - ) - } - - async fn find_by_pk_datasource<'a>( - value: &'a dyn canyon_sql::core::QueryParameter<'a>, - datasource_name: &'a str - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { - Err( - std::io::Error::new( - std::io::ErrorKind::Unsupported, - "You can't use the 'find_by_pk_datasource' associated function on a \ - CanyonEntity that does not have a #[primary_key] annotation. \ - If you need to perform an specific search, use the Querybuilder instead." - ).into_inner().unwrap() - ) - } - }; - } - - let result_handling = quote! { - match result { - n if n.len() == 0 => Ok(None), - _ => Ok( - Some(result.into_results::<#ty>().remove(0)) - ) - } - }; - - quote! { - /// Finds an element on the queried table that matches the - /// value of the field annotated with the `primary_key` attribute, - /// filtering by the column that it's declared as the primary - /// key on the database. - /// - /// This operation it's only available if the [`CanyonEntity`] contains - /// some field declared as primary key. - /// - /// Also, returns a [`Result, Error>`], wrapping a possible failure - /// querying the database, or, if no errors happens, a success containing - /// and Option with the data found wrapped in the Some(T) variant, - /// or None if the value isn't found on the table. - async fn find_by_pk<'a>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) -> - Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> - { - let result = <#ty as canyon_sql::core::Transaction<#ty>>::query( - #stmt, - vec![value], - "" - ).await?; - - #result_handling - } - - /// Finds an element on the queried table that matches the - /// value of the field annotated with the `primary_key` attribute, - /// filtering by the column that it's declared as the primary - /// key on the database. - /// - /// The query it's made against the database with the configured datasource - /// described in the configuration file, and selected with the [`&str`] - /// passed as parameter. - /// - /// This operation it's only available if the [`CanyonEntity`] contains - /// some field declared as primary key. - /// - /// Also, returns a [`Result, Error>`], wrapping a possible failure - /// querying the database, or, if no errors happens, a success containing - /// and Option with the data found wrapped in the Some(T) variant, - /// or None if the value isn't found on the table. - async fn find_by_pk_datasource<'a>( - value: &'a dyn canyon_sql::core::QueryParameter<'a>, - datasource_name: &'a str - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { - - let result = <#ty as canyon_sql::core::Transaction<#ty>>::query( - #stmt, - vec![value], - datasource_name - ).await?; - - #result_handling - } - } -} - -/// Generates the TokenStream for build the search by foreign key feature, also as a method instance -/// of a T type of as an associated function of same T type, but wrapped as a Result, representing -/// a possible failure querying the database, a bad or missing FK annotation or a missed ForeignKeyable -/// derive macro on the parent side of the relation -pub fn generate_find_by_foreign_key_tokens( - macro_data: &MacroTokens<'_>, -) -> Vec<(TokenStream, TokenStream)> { - let mut fk_quotes: Vec<(TokenStream, TokenStream)> = Vec::new(); - - for (field_ident, fk_annot) in macro_data.get_fk_annotations().iter() { - if let EntityFieldAnnotation::ForeignKey(table, column) = fk_annot { - let method_name = "search_".to_owned() + table; - - // TODO this is not a good implementation. We must try to capture the - // related entity in some way, and compare it with something else - let fk_ty = database_table_name_to_struct_ident(table); - - // Generate and identifier for the method based on the convention of "search_related_types" - // where types is a placeholder for the plural name of the type referenced - let method_name_ident = - proc_macro2::Ident::new(&method_name, proc_macro2::Span::call_site()); - let method_name_ident_ds = proc_macro2::Ident::new( - &format!("{}_datasource", &method_name), - proc_macro2::Span::call_site(), - ); - let quoted_method_signature: TokenStream = quote! { - async fn #method_name_ident(&self) -> - Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> - }; - let quoted_datasource_method_signature: TokenStream = quote! { - async fn #method_name_ident_ds<'a>(&self, datasource_name: &'a str) -> - Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> - }; - - let stmt = format!( - "SELECT * FROM {} WHERE {} = $1", - table, - format!("\"{column}\"").as_str(), - ); - let result_handler = quote! { - match result { - n if n.len() == 0 => Ok(None), - _ => Ok(Some( - result.into_results::<#fk_ty>().remove(0) - )) - } - }; - - fk_quotes.push(( - quote! { #quoted_method_signature; }, - quote! { - /// Searches the parent entity (if exists) for this type - #quoted_method_signature { - let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( - #stmt, - &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], - "" - ).await?; - - #result_handler - } - }, - )); - - fk_quotes.push(( - quote! { #quoted_datasource_method_signature; }, - quote! { - /// Searches the parent entity (if exists) for this type with the specified datasource - #quoted_datasource_method_signature { - let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( - #stmt, - &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], - datasource_name - ).await?; - - #result_handler - } - }, - )); - } - } - - fk_quotes -} - -/// Generates the TokenStream for build the __search_by_foreign_key() CRUD -/// associated function, but wrapped as a Result, representing -/// a possible failure querying the database, a bad or missing FK annotation or a missed ForeignKeyable -/// derive macro on the parent side of the relation -pub fn generate_find_by_reverse_foreign_key_tokens( - macro_data: &MacroTokens<'_>, - table_schema_data: &String, -) -> Vec<(TokenStream, TokenStream)> { - let mut rev_fk_quotes: Vec<(TokenStream, TokenStream)> = Vec::new(); - let ty = macro_data.ty; - - for (field_ident, fk_annot) in macro_data.get_fk_annotations().iter() { - if let EntityFieldAnnotation::ForeignKey(table, column) = fk_annot { - let method_name = format!("search_{table}_childrens"); - - // Generate and identifier for the method based on the convention of "search_by__" (note the double underscore) - // plus the 'table_name' property of the ForeignKey annotation - let method_name_ident = - proc_macro2::Ident::new(&method_name, proc_macro2::Span::call_site()); - let method_name_ident_ds = proc_macro2::Ident::new( - &format!("{}_datasource", &method_name), - proc_macro2::Span::call_site(), - ); - let quoted_method_signature: TokenStream = quote! { - async fn #method_name_ident<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send>(value: &F) -> - Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> - }; - let quoted_datasource_method_signature: TokenStream = quote! { - async fn #method_name_ident_ds<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send> - (value: &F, datasource_name: &'a str) -> - Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> - }; - - let f_ident = field_ident.to_string(); - - rev_fk_quotes.push(( - quote! { #quoted_method_signature; }, - quote! { - /// Given a parent entity T annotated with the derive proc macro `ForeignKeyable`, - /// performns a search to find the children that belong to that concrete parent. - #quoted_method_signature - { - let lookage_value = value.get_fk_column(#column) - .expect(format!( - "Column: {:?} not found in type: {:?}", #column, #table - ).as_str()); - - let stmt = format!( - "SELECT * FROM {} WHERE {} = $1", - #table_schema_data, - format!("\"{}\"", #f_ident).as_str() - ); - - Ok(<#ty as canyon_sql::core::Transaction<#ty>>::query( - stmt, - &[lookage_value], - "" - ).await?.into_results::<#ty>()) - } - }, - )); - - rev_fk_quotes.push(( - quote! { #quoted_datasource_method_signature; }, - quote! { - /// Given a parent entity T annotated with the derive proc macro `ForeignKeyable`, - /// performns a search to find the children that belong to that concrete parent - /// with the specified datasource. - #quoted_datasource_method_signature - { - let lookage_value = value.get_fk_column(#column) - .expect(format!( - "Column: {:?} not found in type: {:?}", #column, #table - ).as_str()); - - let stmt = format!( - "SELECT * FROM {} WHERE {} = $1", - #table_schema_data, - format!("\"{}\"", #f_ident).as_str() - ); - - Ok(<#ty as canyon_sql::core::Transaction<#ty>>::query( - stmt, - &[lookage_value], - datasource_name - ).await?.into_results::<#ty>()) - } - }, - )); - } - } - - rev_fk_quotes -} +// /// Same as above, but with a [`canyon_sql::query::QueryBuilder`] +// pub fn generate_find_all_query_tokens( +// macro_data: &MacroTokens<'_>, +// table_schema_data: &String, +// ) -> TokenStream { +// let ty = macro_data.ty; + +// quote! { +// /// Generates a [`canyon_sql::query::SelectQueryBuilder`] +// /// that allows you to customize the query by adding parameters and constrains dynamically. +// /// +// /// It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your +// /// entity but converted to the corresponding database convention, +// /// unless concrete values are set on the available parameters of the +// /// `canyon_macro(table_name = "table_name", schema = "schema")` +// fn select_query<'a>() -> canyon_sql::query::SelectQueryBuilder<'a, #ty> { +// canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, "") +// } + +// /// Generates a [`canyon_sql::query::SelectQueryBuilder`] +// /// that allows you to customize the query by adding parameters and constrains dynamically. +// /// +// /// It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your +// /// entity but converted to the corresponding database convention, +// /// unless concrete values are set on the available parameters of the +// /// `canyon_macro(table_name = "table_name", schema = "schema")` +// /// +// /// The query it's made against the database with the configured datasource +// /// described in the configuration file, and selected with the [`&str`] +// /// passed as parameter. +// fn select_query_datasource<'a>(datasource_name: &'a str) -> canyon_sql::query::SelectQueryBuilder<'a, #ty> { +// canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, datasource_name) +// } +// } +// } + +// /// Performs a COUNT(*) query over some table, returning a [`Result`] wrapping +// /// a possible success or error coming from the database +// pub fn generate_count_tokens( +// macro_data: &MacroTokens<'_>, +// table_schema_data: &String, +// ) -> TokenStream { +// let ty = macro_data.ty; +// let ty_str = &ty.to_string(); +// let stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); + +// let result_handling = quote! { +// #[cfg(feature="postgres")] +// canyon_sql::core::CanyonRows::Postgres(mut v) => Ok( +// v.remove(0).get::<&str, i64>("count") +// ), +// #[cfg(feature="mssql")] +// canyon_sql::core::CanyonRows::Tiberius(mut v) => +// v.remove(0) +// .get::(0) +// .map(|c| c as i64) +// .ok_or(format!("Failure in the COUNT query for MSSQL for: {}", #ty_str).into()) +// .into(), +// #[cfg(feature="mysql")] +// canyon_sql::core::CanyonRows::MySQL(mut v) => v.remove(0) +// .get::(0) +// .ok_or(format!("Failure in the COUNT query for MYSQL for: {}", #ty_str).into()), +// _ => panic!() // TODO remove when the generics will be refactored +// }; + +// quote! { +// /// Performs a COUNT(*) query over some table, returning a [`Result`] rather than panicking, +// /// wrapping a possible success or error coming from the database +// async fn count() -> Result> { +// let count = <#ty as canyon_sql::core::Transaction<#ty>>::query( +// #stmt, +// &[], +// "" +// ).await?; + +// match count { +// #result_handling +// } +// } + +// /// Performs a COUNT(*) query over some table, returning a [`Result`] rather than panicking, +// /// wrapping a possible success or error coming from the database with the specified datasource +// async fn count_datasource<'a>(datasource_name: &'a str) -> Result> { +// let count = <#ty as canyon_sql::core::Transaction<#ty>>::query( +// #stmt, +// &[], +// datasource_name +// ).await?; + +// match count { +// #result_handling +// } +// } +// } +// } + +// /// Generates the TokenStream for build the __find_by_pk() CRUD operation +// pub fn generate_find_by_pk_tokens( +// macro_data: &MacroTokens<'_>, +// table_schema_data: &String, +// ) -> TokenStream { +// let ty = macro_data.ty; +// let pk = macro_data.get_primary_key_annotation().unwrap_or_default(); +// let stmt = format!("SELECT * FROM {table_schema_data} WHERE {pk} = $1"); + +// // Disabled if there's no `primary_key` annotation +// if pk.is_empty() { +// return quote! { +// async fn find_by_pk<'a>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) +// -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> +// { +// Err( +// std::io::Error::new( +// std::io::ErrorKind::Unsupported, +// "You can't use the 'find_by_pk' associated function on a \ +// CanyonEntity that does not have a #[primary_key] annotation. \ +// If you need to perform an specific search, use the Querybuilder instead." +// ).into_inner().unwrap() +// ) +// } + +// async fn find_by_pk_datasource<'a>( +// value: &'a dyn canyon_sql::core::QueryParameter<'a>, +// datasource_name: &'a str +// ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { +// Err( +// std::io::Error::new( +// std::io::ErrorKind::Unsupported, +// "You can't use the 'find_by_pk_datasource' associated function on a \ +// CanyonEntity that does not have a #[primary_key] annotation. \ +// If you need to perform an specific search, use the Querybuilder instead." +// ).into_inner().unwrap() +// ) +// } +// }; +// } + +// let result_handling = quote! { +// match result { +// n if n.len() == 0 => Ok(None), +// _ => Ok( +// Some(result.into_results::<#ty>().remove(0)) +// ) +// } +// }; + +// quote! { +// /// Finds an element on the queried table that matches the +// /// value of the field annotated with the `primary_key` attribute, +// /// filtering by the column that it's declared as the primary +// /// key on the database. +// /// +// /// This operation it's only available if the [`CanyonEntity`] contains +// /// some field declared as primary key. +// /// +// /// Also, returns a [`Result, Error>`], wrapping a possible failure +// /// querying the database, or, if no errors happens, a success containing +// /// and Option with the data found wrapped in the Some(T) variant, +// /// or None if the value isn't found on the table. +// async fn find_by_pk<'a>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) -> +// Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> +// { +// let result = <#ty as canyon_sql::core::Transaction<#ty>>::query( +// #stmt, +// vec![value], +// "" +// ).await?; + +// #result_handling +// } + +// /// Finds an element on the queried table that matches the +// /// value of the field annotated with the `primary_key` attribute, +// /// filtering by the column that it's declared as the primary +// /// key on the database. +// /// +// /// The query it's made against the database with the configured datasource +// /// described in the configuration file, and selected with the [`&str`] +// /// passed as parameter. +// /// +// /// This operation it's only available if the [`CanyonEntity`] contains +// /// some field declared as primary key. +// /// +// /// Also, returns a [`Result, Error>`], wrapping a possible failure +// /// querying the database, or, if no errors happens, a success containing +// /// and Option with the data found wrapped in the Some(T) variant, +// /// or None if the value isn't found on the table. +// async fn find_by_pk_datasource<'a>( +// value: &'a dyn canyon_sql::core::QueryParameter<'a>, +// datasource_name: &'a str +// ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { + +// let result = <#ty as canyon_sql::core::Transaction<#ty>>::query( +// #stmt, +// vec![value], +// datasource_name +// ).await?; + +// #result_handling +// } +// } +// } + +// /// Generates the TokenStream for build the search by foreign key feature, also as a method instance +// /// of a T type of as an associated function of same T type, but wrapped as a Result, representing +// /// a possible failure querying the database, a bad or missing FK annotation or a missed ForeignKeyable +// /// derive macro on the parent side of the relation +// pub fn generate_find_by_foreign_key_tokens( +// macro_data: &MacroTokens<'_>, +// ) -> Vec<(TokenStream, TokenStream)> { +// let mut fk_quotes: Vec<(TokenStream, TokenStream)> = Vec::new(); + +// for (field_ident, fk_annot) in macro_data.get_fk_annotations().iter() { +// if let EntityFieldAnnotation::ForeignKey(table, column) = fk_annot { +// let method_name = "search_".to_owned() + table; + +// // TODO this is not a good implementation. We must try to capture the +// // related entity in some way, and compare it with something else +// let fk_ty = database_table_name_to_struct_ident(table); + +// // Generate and identifier for the method based on the convention of "search_related_types" +// // where types is a placeholder for the plural name of the type referenced +// let method_name_ident = +// proc_macro2::Ident::new(&method_name, proc_macro2::Span::call_site()); +// let method_name_ident_ds = proc_macro2::Ident::new( +// &format!("{}_datasource", &method_name), +// proc_macro2::Span::call_site(), +// ); +// let quoted_method_signature: TokenStream = quote! { +// async fn #method_name_ident(&self) -> +// Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> +// }; +// let quoted_datasource_method_signature: TokenStream = quote! { +// async fn #method_name_ident_ds<'a>(&self, datasource_name: &'a str) -> +// Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> +// }; + +// let stmt = format!( +// "SELECT * FROM {} WHERE {} = $1", +// table, +// format!("\"{column}\"").as_str(), +// ); +// let result_handler = quote! { +// match result { +// n if n.len() == 0 => Ok(None), +// _ => Ok(Some( +// result.into_results::<#fk_ty>().remove(0) +// )) +// } +// }; + +// fk_quotes.push(( +// quote! { #quoted_method_signature; }, +// quote! { +// /// Searches the parent entity (if exists) for this type +// #quoted_method_signature { +// let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( +// #stmt, +// &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], +// "" +// ).await?; + +// #result_handler +// } +// }, +// )); + +// fk_quotes.push(( +// quote! { #quoted_datasource_method_signature; }, +// quote! { +// /// Searches the parent entity (if exists) for this type with the specified datasource +// #quoted_datasource_method_signature { +// let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( +// #stmt, +// &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], +// datasource_name +// ).await?; + +// #result_handler +// } +// }, +// )); +// } +// } + +// fk_quotes +// } + +// /// Generates the TokenStream for build the __search_by_foreign_key() CRUD +// /// associated function, but wrapped as a Result, representing +// /// a possible failure querying the database, a bad or missing FK annotation or a missed ForeignKeyable +// /// derive macro on the parent side of the relation +// pub fn generate_find_by_reverse_foreign_key_tokens( +// macro_data: &MacroTokens<'_>, +// table_schema_data: &String, +// ) -> Vec<(TokenStream, TokenStream)> { +// let mut rev_fk_quotes: Vec<(TokenStream, TokenStream)> = Vec::new(); +// let ty = macro_data.ty; + +// for (field_ident, fk_annot) in macro_data.get_fk_annotations().iter() { +// if let EntityFieldAnnotation::ForeignKey(table, column) = fk_annot { +// let method_name = format!("search_{table}_childrens"); + +// // Generate and identifier for the method based on the convention of "search_by__" (note the double underscore) +// // plus the 'table_name' property of the ForeignKey annotation +// let method_name_ident = +// proc_macro2::Ident::new(&method_name, proc_macro2::Span::call_site()); +// let method_name_ident_ds = proc_macro2::Ident::new( +// &format!("{}_datasource", &method_name), +// proc_macro2::Span::call_site(), +// ); +// let quoted_method_signature: TokenStream = quote! { +// async fn #method_name_ident<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send>(value: &F) -> +// Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> +// }; +// let quoted_datasource_method_signature: TokenStream = quote! { +// async fn #method_name_ident_ds<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send> +// (value: &F, datasource_name: &'a str) -> +// Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> +// }; + +// let f_ident = field_ident.to_string(); + +// rev_fk_quotes.push(( +// quote! { #quoted_method_signature; }, +// quote! { +// /// Given a parent entity T annotated with the derive proc macro `ForeignKeyable`, +// /// performns a search to find the children that belong to that concrete parent. +// #quoted_method_signature +// { +// let lookage_value = value.get_fk_column(#column) +// .expect(format!( +// "Column: {:?} not found in type: {:?}", #column, #table +// ).as_str()); + +// let stmt = format!( +// "SELECT * FROM {} WHERE {} = $1", +// #table_schema_data, +// format!("\"{}\"", #f_ident).as_str() +// ); + +// Ok(<#ty as canyon_sql::core::Transaction<#ty>>::query( +// stmt, +// &[lookage_value], +// "" +// ).await?.into_results::<#ty>()) +// } +// }, +// )); + +// rev_fk_quotes.push(( +// quote! { #quoted_datasource_method_signature; }, +// quote! { +// /// Given a parent entity T annotated with the derive proc macro `ForeignKeyable`, +// /// performns a search to find the children that belong to that concrete parent +// /// with the specified datasource. +// #quoted_datasource_method_signature +// { +// let lookage_value = value.get_fk_column(#column) +// .expect(format!( +// "Column: {:?} not found in type: {:?}", #column, #table +// ).as_str()); + +// let stmt = format!( +// "SELECT * FROM {} WHERE {} = $1", +// #table_schema_data, +// format!("\"{}\"", #f_ident).as_str() +// ); + +// Ok(<#ty as canyon_sql::core::Transaction<#ty>>::query( +// stmt, +// &[lookage_value], +// datasource_name +// ).await?.into_results::<#ty>()) +// } +// }, +// )); +// } +// } + +// rev_fk_quotes +// } diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index fc775a4c..b1db7c4e 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -31,43 +31,43 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri .expect("Update method failed to retrieve the index of the primary key"); quote! { - /// Updates a database record that matches - /// the current instance of a T type, returning a result - /// indicating a possible failure querying the database. - async fn update(&self) -> Result<(), Box> { - let stmt = format!( - "UPDATE {} SET {} WHERE {} = ${:?}", - #table_schema_data, #str_columns_values, #primary_key, #pk_index + 1 - ); - let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = &[#(#update_values),*]; - - <#ty as canyon_sql::core::Transaction<#ty>>::query( - stmt, update_values, "" - ).await?; - - Ok(()) - } - - - /// Updates a database record that matches - /// the current instance of a T type, returning a result - /// indicating a possible failure querying the database with the - /// specified datasource - async fn update_datasource<'a>(&self, datasource_name: &'a str) - -> Result<(), Box> - { - let stmt = format!( - "UPDATE {} SET {} WHERE {} = ${:?}", - #table_schema_data, #str_columns_values, #primary_key, #pk_index + 1 - ); - let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = &[#(#update_values_cloned),*]; - - <#ty as canyon_sql::core::Transaction<#ty>>::query( - stmt, update_values, datasource_name - ).await?; - - Ok(()) - } + // /// Updates a database record that matches + // /// the current instance of a T type, returning a result + // /// indicating a possible failure querying the database. + // async fn update(&self) -> Result<(), Box> { + // let stmt = format!( + // "UPDATE {} SET {} WHERE {} = ${:?}", + // #table_schema_data, #str_columns_values, #primary_key, #pk_index + 1 + // ); + // let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = &[#(#update_values),*]; + + // <#ty as canyon_sql::core::Transaction<#ty>>::query( + // stmt, update_values, "" + // ).await?; + + // Ok(()) + // } + + + // /// Updates a database record that matches + // /// the current instance of a T type, returning a result + // /// indicating a possible failure querying the database with the + // /// specified datasource + // async fn update_datasource<'a>(&self, datasource_name: &'a str) + // -> Result<(), Box> + // { + // let stmt = format!( + // "UPDATE {} SET {} WHERE {} = ${:?}", + // #table_schema_data, #str_columns_values, #primary_key, #pk_index + 1 + // ); + // let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = &[#(#update_values_cloned),*]; + + // <#ty as canyon_sql::core::Transaction<#ty>>::query( + // stmt, update_values, datasource_name + // ).await?; + + // Ok(()) + // } } } else { // If there's no primary key, update method over self won't be available. @@ -75,31 +75,31 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri // TODO Returning an error should be a provisional way of doing this quote! { - async fn update(&self) - -> Result<(), Box> - { - Err( - std::io::Error::new( - std::io::ErrorKind::Unsupported, - "You can't use the 'update' method on a \ - CanyonEntity that does not have a #[primary_key] annotation. \ - If you need to perform an specific search, use the Querybuilder instead." - ).into_inner().unwrap() - ) - } - - async fn update_datasource<'a>(&self, datasource_name: &'a str) - -> Result<(), Box> - { - Err( - std::io::Error::new( - std::io::ErrorKind::Unsupported, - "You can't use the 'update_datasource' method on a \ - CanyonEntity that does not have a #[primary_key] annotation. \ - If you need to perform an specific search, use the Querybuilder instead." - ).into_inner().unwrap() - ) - } + // async fn update(&self) + // -> Result<(), Box> + // { + // Err( + // std::io::Error::new( + // std::io::ErrorKind::Unsupported, + // "You can't use the 'update' method on a \ + // CanyonEntity that does not have a #[primary_key] annotation. \ + // If you need to perform an specific search, use the Querybuilder instead." + // ).into_inner().unwrap() + // ) + // } + + // async fn update_datasource<'a>(&self, datasource_name: &'a str) + // -> Result<(), Box> + // { + // Err( + // std::io::Error::new( + // std::io::ErrorKind::Unsupported, + // "You can't use the 'update_datasource' method on a \ + // CanyonEntity that does not have a #[primary_key] annotation. \ + // If you need to perform an specific search, use the Querybuilder instead." + // ).into_inner().unwrap() + // ) + // } } } } @@ -113,30 +113,30 @@ pub fn generate_update_query_tokens( let ty = macro_data.ty; quote! { - /// Generates a [`canyon_sql::query::UpdateQueryBuilder`] - /// that allows you to customize the query by adding parameters and constrains dynamically. - /// - /// It performs an `UPDATE table_name`, where `table_name` it's the name of your - /// entity but converted to the corresponding database convention, - /// unless concrete values are set on the available parameters of the - /// `canyon_macro(table_name = "table_name", schema = "schema")` - fn update_query<'a>() -> canyon_sql::query::UpdateQueryBuilder<'a, #ty> { - canyon_sql::query::UpdateQueryBuilder::new(#table_schema_data, "") - } - - /// Generates a [`canyon_sql::query::UpdateQueryBuilder`] - /// that allows you to customize the query by adding parameters and constrains dynamically. - /// - /// It performs an `UPDATE table_name`, where `table_name` it's the name of your - /// entity but converted to the corresponding database convention, - /// unless concrete values are set on the available parameters of the - /// `canyon_macro(table_name = "table_name", schema = "schema")` - /// - /// The query it's made against the database with the configured datasource - /// described in the configuration file, and selected with the [`&str`] - /// passed as parameter. - fn update_query_datasource<'a>(datasource_name: &'a str) -> canyon_sql::query::UpdateQueryBuilder<'a, #ty> { - canyon_sql::query::UpdateQueryBuilder::new(#table_schema_data, datasource_name) - } + // /// Generates a [`canyon_sql::query::UpdateQueryBuilder`] + // /// that allows you to customize the query by adding parameters and constrains dynamically. + // /// + // /// It performs an `UPDATE table_name`, where `table_name` it's the name of your + // /// entity but converted to the corresponding database convention, + // /// unless concrete values are set on the available parameters of the + // /// `canyon_macro(table_name = "table_name", schema = "schema")` + // fn update_query<'a>() -> canyon_sql::query::UpdateQueryBuilder<'a, #ty> { + // canyon_sql::query::UpdateQueryBuilder::new(#table_schema_data, "") + // } + + // /// Generates a [`canyon_sql::query::UpdateQueryBuilder`] + // /// that allows you to customize the query by adding parameters and constrains dynamically. + // /// + // /// It performs an `UPDATE table_name`, where `table_name` it's the name of your + // /// entity but converted to the corresponding database convention, + // /// unless concrete values are set on the available parameters of the + // /// `canyon_macro(table_name = "table_name", schema = "schema")` + // /// + // /// The query it's made against the database with the configured datasource + // /// described in the configuration file, and selected with the [`&str`] + // /// passed as parameter. + // fn update_query_datasource<'a>(datasource_name: &'a str) -> canyon_sql::query::UpdateQueryBuilder<'a, #ty> { + // canyon_sql::query::UpdateQueryBuilder::new(#table_schema_data, datasource_name) + // } } } diff --git a/tests/crud/delete_operations.rs b/tests/crud/delete_operations.rs index 5c1f5c1c..584895ba 100644 --- a/tests/crud/delete_operations.rs +++ b/tests/crud/delete_operations.rs @@ -1,159 +1,159 @@ -//! Integration tests for the CRUD operations available in `Canyon` that -//! generates and executes *INSERT* statements -use canyon_sql::crud::CrudOperations; +// //! Integration tests for the CRUD operations available in `Canyon` that +// //! generates and executes *INSERT* statements +// use canyon_sql::crud::CrudOperations; -#[cfg(feature = "mysql")] -use crate::constants::MYSQL_DS; -#[cfg(feature = "postgres")] -use crate::constants::PSQL_DS; -#[cfg(feature = "mssql")] -use crate::constants::SQL_SERVER_DS; +// #[cfg(feature = "mysql")] +// use crate::constants::MYSQL_DS; +// #[cfg(feature = "postgres")] +// use crate::constants::PSQL_DS; +// #[cfg(feature = "mssql")] +// use crate::constants::SQL_SERVER_DS; -use crate::tests_models::league::*; +// use crate::tests_models::league::*; -/// Deletes a row from the database that is mapped into some instance of a `T` entity. -/// -/// The `t.delete(&self)` operation is only enabled for types that -/// has, at least, one of it's fields annotated with a `#[primary_key]` -/// operation, because we use that concrete field to construct the clause that targets -/// that entity. -/// -/// Attempt of usage the `t.delete(&self)` method on an entity without `#[primary_key]` -/// will raise a runtime error. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_delete_method_operation() { - // For test the delete, we will insert a new instance of the database, and then, - // after inspect it, we will proceed to delete it - let mut new_league: League = League { - id: Default::default(), - ext_id: 7892635306594_i64, - slug: "some-new-league".to_string(), - name: "Some New League".to_string(), - region: "Bahía de cochinos".to_string(), - image_url: "https://nobodyspectsandimage.io".to_string(), - }; +// /// Deletes a row from the database that is mapped into some instance of a `T` entity. +// /// +// /// The `t.delete(&self)` operation is only enabled for types that +// /// has, at least, one of it's fields annotated with a `#[primary_key]` +// /// operation, because we use that concrete field to construct the clause that targets +// /// that entity. +// /// +// /// Attempt of usage the `t.delete(&self)` method on an entity without `#[primary_key]` +// /// will raise a runtime error. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_delete_method_operation() { +// // For test the delete, we will insert a new instance of the database, and then, +// // after inspect it, we will proceed to delete it +// let mut new_league: League = League { +// id: Default::default(), +// ext_id: 7892635306594_i64, +// slug: "some-new-league".to_string(), +// name: "Some New League".to_string(), +// region: "Bahía de cochinos".to_string(), +// image_url: "https://nobodyspectsandimage.io".to_string(), +// }; - // We insert the instance on the database, on the `League` entity - new_league.insert().await.expect("Failed insert operation"); +// // We insert the instance on the database, on the `League` entity +// new_league.insert().await.expect("Failed insert operation"); - assert_eq!( - new_league.id, - League::find_by_pk_datasource(&new_league.id, PSQL_DS) - .await - .expect("Request error") - .expect("None value") - .id - ); +// assert_eq!( +// new_league.id, +// League::find_by_pk_datasource(&new_league.id, PSQL_DS) +// .await +// .expect("Request error") +// .expect("None value") +// .id +// ); - // Now that we have an instance mapped to some entity by a primary key, we can now - // remove that entry from the database with the delete operation - new_league - .delete() - .await - .expect("Failed to delete the operation"); +// // Now that we have an instance mapped to some entity by a primary key, we can now +// // remove that entry from the database with the delete operation +// new_league +// .delete() +// .await +// .expect("Failed to delete the operation"); - // To check the success, we can query by the primary key value and check if, after unwrap() - // the result of the operation, the find by primary key contains Some(v) or None - // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> - assert_eq!( - League::find_by_pk(&new_league.id) - .await - .expect("Unwrapping the result, letting the Option"), - None - ); -} +// // To check the success, we can query by the primary key value and check if, after unwrap() +// // the result of the operation, the find by primary key contains Some(v) or None +// // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> +// assert_eq!( +// League::find_by_pk(&new_league.id) +// .await +// .expect("Unwrapping the result, letting the Option"), +// None +// ); +// } -/// Same as the delete test, but performing the operations with the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_delete_datasource_mssql_method_operation() { - // For test the delete, we will insert a new instance of the database, and then, - // after inspect it, we will proceed to delete it - let mut new_league: League = League { - id: Default::default(), - ext_id: 7892635306594_i64, - slug: "some-new-league".to_string(), - name: "Some New League".to_string(), - region: "Bahía de cochinos".to_string(), - image_url: "https://nobodyspectsandimage.io".to_string(), - }; +// /// Same as the delete test, but performing the operations with the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_delete_datasource_mssql_method_operation() { +// // For test the delete, we will insert a new instance of the database, and then, +// // after inspect it, we will proceed to delete it +// let mut new_league: League = League { +// id: Default::default(), +// ext_id: 7892635306594_i64, +// slug: "some-new-league".to_string(), +// name: "Some New League".to_string(), +// region: "Bahía de cochinos".to_string(), +// image_url: "https://nobodyspectsandimage.io".to_string(), +// }; - // We insert the instance on the database, on the `League` entity - new_league - .insert_datasource(SQL_SERVER_DS) - .await - .expect("Failed insert operation"); - assert_eq!( - new_league.id, - League::find_by_pk_datasource(&new_league.id, SQL_SERVER_DS) - .await - .expect("Request error") - .expect("None value") - .id - ); +// // We insert the instance on the database, on the `League` entity +// new_league +// .insert_datasource(SQL_SERVER_DS) +// .await +// .expect("Failed insert operation"); +// assert_eq!( +// new_league.id, +// League::find_by_pk_datasource(&new_league.id, SQL_SERVER_DS) +// .await +// .expect("Request error") +// .expect("None value") +// .id +// ); - // Now that we have an instance mapped to some entity by a primary key, we can now - // remove that entry from the database with the delete operation - new_league - .delete_datasource(SQL_SERVER_DS) - .await - .expect("Failed to delete the operation"); +// // Now that we have an instance mapped to some entity by a primary key, we can now +// // remove that entry from the database with the delete operation +// new_league +// .delete_datasource(SQL_SERVER_DS) +// .await +// .expect("Failed to delete the operation"); - // To check the success, we can query by the primary key value and check if, after unwrap() - // the result of the operation, the find by primary key contains Some(v) or None - // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> - assert_eq!( - League::find_by_pk_datasource(&new_league.id, SQL_SERVER_DS) - .await - .expect("Unwrapping the result, letting the Option"), - None - ); -} +// // To check the success, we can query by the primary key value and check if, after unwrap() +// // the result of the operation, the find by primary key contains Some(v) or None +// // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> +// assert_eq!( +// League::find_by_pk_datasource(&new_league.id, SQL_SERVER_DS) +// .await +// .expect("Unwrapping the result, letting the Option"), +// None +// ); +// } -/// Same as the delete test, but performing the operations with the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_delete_datasource_mysql_method_operation() { - // For test the delete, we will insert a new instance of the database, and then, - // after inspect it, we will proceed to delete it - let mut new_league: League = League { - id: Default::default(), - ext_id: 7892635306594_i64, - slug: "some-new-league".to_string(), - name: "Some New League".to_string(), - region: "Bahía de cochinos".to_string(), - image_url: "https://nobodyspectsandimage.io".to_string(), - }; +// /// Same as the delete test, but performing the operations with the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_delete_datasource_mysql_method_operation() { +// // For test the delete, we will insert a new instance of the database, and then, +// // after inspect it, we will proceed to delete it +// let mut new_league: League = League { +// id: Default::default(), +// ext_id: 7892635306594_i64, +// slug: "some-new-league".to_string(), +// name: "Some New League".to_string(), +// region: "Bahía de cochinos".to_string(), +// image_url: "https://nobodyspectsandimage.io".to_string(), +// }; - // We insert the instance on the database, on the `League` entity - new_league - .insert_datasource(MYSQL_DS) - .await - .expect("Failed insert operation"); - assert_eq!( - new_league.id, - League::find_by_pk_datasource(&new_league.id, MYSQL_DS) - .await - .expect("Request error") - .expect("None value") - .id - ); +// // We insert the instance on the database, on the `League` entity +// new_league +// .insert_datasource(MYSQL_DS) +// .await +// .expect("Failed insert operation"); +// assert_eq!( +// new_league.id, +// League::find_by_pk_datasource(&new_league.id, MYSQL_DS) +// .await +// .expect("Request error") +// .expect("None value") +// .id +// ); - // Now that we have an instance mapped to some entity by a primary key, we can now - // remove that entry from the database with the delete operation - new_league - .delete_datasource(MYSQL_DS) - .await - .expect("Failed to delete the operation"); +// // Now that we have an instance mapped to some entity by a primary key, we can now +// // remove that entry from the database with the delete operation +// new_league +// .delete_datasource(MYSQL_DS) +// .await +// .expect("Failed to delete the operation"); - // To check the success, we can query by the primary key value and check if, after unwrap() - // the result of the operation, the find by primary key contains Some(v) or None - // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> - assert_eq!( - League::find_by_pk_datasource(&new_league.id, MYSQL_DS) - .await - .expect("Unwrapping the result, letting the Option"), - None - ); -} +// // To check the success, we can query by the primary key value and check if, after unwrap() +// // the result of the operation, the find by primary key contains Some(v) or None +// // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> +// assert_eq!( +// League::find_by_pk_datasource(&new_league.id, MYSQL_DS) +// .await +// .expect("Unwrapping the result, letting the Option"), +// None +// ); +// } diff --git a/tests/crud/foreign_key_operations.rs b/tests/crud/foreign_key_operations.rs index 87630ad1..8f286fb0 100644 --- a/tests/crud/foreign_key_operations.rs +++ b/tests/crud/foreign_key_operations.rs @@ -1,163 +1,163 @@ -/// Integration tests for the CRUD operations available in `Canyon` that -/// generates and executes *SELECT* statements based on a entity -/// annotated with the `#[foreign_key(... args)]` annotation looking -/// for the related data with some entity `U` that acts as is parent, where `U` -/// impls `ForeignKeyable` (isn't required, but it won't unlock the -/// reverse search features parent -> child, only the child -> parent ones). -/// -/// Names of the foreign key methods are autogenerated for the direct and -/// reverse side of the implementations. -/// For more info: TODO -> Link to the docs of the foreign key chapter -use canyon_sql::crud::CrudOperations; - -#[cfg(feature = "mssql")] -use crate::constants::MYSQL_DS; -#[cfg(feature = "mssql")] -use crate::constants::SQL_SERVER_DS; - -use crate::tests_models::league::*; -use crate::tests_models::tournament::*; - -/// Given an entity `T` which has some field declaring a foreign key relation -/// with some another entity `U`, for example, performs a search to find -/// what is the parent type `U` of `T` -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_search_by_foreign_key() { - let some_tournament: Tournament = Tournament::find_by_pk(&1) - .await - .expect("Result variant of the query is err") - .expect("No result found for the given parameter"); - - // We can get the parent entity for the retrieved child instance - let parent_entity: Option = some_tournament - .search_league() - .await - .expect("Result variant of the query is err"); - - if let Some(league) = parent_entity { - assert_eq!(some_tournament.league, league.id) - } else { - assert_eq!(parent_entity, None) - } -} - -/// Same as the search by foreign key, but with the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_search_by_foreign_key_datasource_mssql() { - let some_tournament: Tournament = Tournament::find_by_pk_datasource(&10, SQL_SERVER_DS) - .await - .expect("Result variant of the query is err") - .expect("No result found for the given parameter"); - - // We can get the parent entity for the retrieved child instance - let parent_entity: Option = some_tournament - .search_league_datasource(SQL_SERVER_DS) - .await - .expect("Result variant of the query is err"); - - // These are tests, and we could unwrap the result contained in the option, because - // it always should exist that search for the data inserted when the docker starts. - // But, just for change the style a little bit and offer more options about how to - // handle things done with Canyon - if let Some(league) = parent_entity { - assert_eq!(some_tournament.league, league.id) - } else { - assert_eq!(parent_entity, None) - } -} - -/// Same as the search by foreign key, but with the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_search_by_foreign_key_datasource_mysql() { - let some_tournament: Tournament = Tournament::find_by_pk_datasource(&10, MYSQL_DS) - .await - .expect("Result variant of the query is err") - .expect("No result found for the given parameter"); - - // We can get the parent entity for the retrieved child instance - let parent_entity: Option = some_tournament - .search_league_datasource(MYSQL_DS) - .await - .expect("Result variant of the query is err"); - - // These are tests, and we could unwrap the result contained in the option, because - // it always should exist that search for the data inserted when the docker starts. - // But, just for change the style a little bit and offer more options about how to - // handle things done with Canyon - if let Some(league) = parent_entity { - assert_eq!(some_tournament.league, league.id) - } else { - assert_eq!(parent_entity, None) - } -} - -/// Given an entity `U` that is know as the "parent" side of the relation with another -/// entity `T`, for example, we can ask to the parent for the childrens that belongs -/// to `U`. -/// -/// For this to work, `U`, the parent, must have derived the `ForeignKeyable` proc macro -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_search_reverse_side_foreign_key() { - let some_league: League = League::find_by_pk(&1) - .await - .expect("Result variant of the query is err") - .expect("No result found for the given parameter"); - - // Computes how many tournaments are pointing to the retrieved league - let child_tournaments: Vec = Tournament::search_league_childrens(&some_league) - .await - .expect("Result variant of the query is err"); - - assert!(!child_tournaments.is_empty()); - child_tournaments - .iter() - .for_each(|t| assert_eq!(t.league, some_league.id)); -} - -/// Same as the search by the reverse side of a foreign key relation -/// but with the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_search_reverse_side_foreign_key_datasource_mssql() { - let some_league: League = League::find_by_pk_datasource(&1, SQL_SERVER_DS) - .await - .expect("Result variant of the query is err") - .expect("No result found for the given parameter"); - - // Computes how many tournaments are pointing to the retrieved league - let child_tournaments: Vec = - Tournament::search_league_childrens_datasource(&some_league, SQL_SERVER_DS) - .await - .expect("Result variant of the query is err"); - - assert!(!child_tournaments.is_empty()); - child_tournaments - .iter() - .for_each(|t| assert_eq!(t.league, some_league.id)); -} - -/// Same as the search by the reverse side of a foreign key relation -/// but with the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_search_reverse_side_foreign_key_datasource_mysql() { - let some_league: League = League::find_by_pk_datasource(&1, MYSQL_DS) - .await - .expect("Result variant of the query is err") - .expect("No result found for the given parameter"); - - // Computes how many tournaments are pointing to the retrieved league - let child_tournaments: Vec = - Tournament::search_league_childrens_datasource(&some_league, MYSQL_DS) - .await - .expect("Result variant of the query is err"); - - assert!(!child_tournaments.is_empty()); - child_tournaments - .iter() - .for_each(|t| assert_eq!(t.league, some_league.id)); -} +// /// Integration tests for the CRUD operations available in `Canyon` that +// /// generates and executes *SELECT* statements based on a entity +// /// annotated with the `#[foreign_key(... args)]` annotation looking +// /// for the related data with some entity `U` that acts as is parent, where `U` +// /// impls `ForeignKeyable` (isn't required, but it won't unlock the +// /// reverse search features parent -> child, only the child -> parent ones). +// /// +// /// Names of the foreign key methods are autogenerated for the direct and +// /// reverse side of the implementations. +// /// For more info: TODO -> Link to the docs of the foreign key chapter +// use canyon_sql::crud::CrudOperations; + +// #[cfg(feature = "mssql")] +// use crate::constants::MYSQL_DS; +// #[cfg(feature = "mssql")] +// use crate::constants::SQL_SERVER_DS; + +// use crate::tests_models::league::*; +// use crate::tests_models::tournament::*; + +// /// Given an entity `T` which has some field declaring a foreign key relation +// /// with some another entity `U`, for example, performs a search to find +// /// what is the parent type `U` of `T` +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_search_by_foreign_key() { +// let some_tournament: Tournament = Tournament::find_by_pk(&1) +// .await +// .expect("Result variant of the query is err") +// .expect("No result found for the given parameter"); + +// // We can get the parent entity for the retrieved child instance +// let parent_entity: Option = some_tournament +// .search_league() +// .await +// .expect("Result variant of the query is err"); + +// if let Some(league) = parent_entity { +// assert_eq!(some_tournament.league, league.id) +// } else { +// assert_eq!(parent_entity, None) +// } +// } + +// /// Same as the search by foreign key, but with the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_search_by_foreign_key_datasource_mssql() { +// let some_tournament: Tournament = Tournament::find_by_pk_datasource(&10, SQL_SERVER_DS) +// .await +// .expect("Result variant of the query is err") +// .expect("No result found for the given parameter"); + +// // We can get the parent entity for the retrieved child instance +// let parent_entity: Option = some_tournament +// .search_league_datasource(SQL_SERVER_DS) +// .await +// .expect("Result variant of the query is err"); + +// // These are tests, and we could unwrap the result contained in the option, because +// // it always should exist that search for the data inserted when the docker starts. +// // But, just for change the style a little bit and offer more options about how to +// // handle things done with Canyon +// if let Some(league) = parent_entity { +// assert_eq!(some_tournament.league, league.id) +// } else { +// assert_eq!(parent_entity, None) +// } +// } + +// /// Same as the search by foreign key, but with the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_search_by_foreign_key_datasource_mysql() { +// let some_tournament: Tournament = Tournament::find_by_pk_datasource(&10, MYSQL_DS) +// .await +// .expect("Result variant of the query is err") +// .expect("No result found for the given parameter"); + +// // We can get the parent entity for the retrieved child instance +// let parent_entity: Option = some_tournament +// .search_league_datasource(MYSQL_DS) +// .await +// .expect("Result variant of the query is err"); + +// // These are tests, and we could unwrap the result contained in the option, because +// // it always should exist that search for the data inserted when the docker starts. +// // But, just for change the style a little bit and offer more options about how to +// // handle things done with Canyon +// if let Some(league) = parent_entity { +// assert_eq!(some_tournament.league, league.id) +// } else { +// assert_eq!(parent_entity, None) +// } +// } + +// /// Given an entity `U` that is know as the "parent" side of the relation with another +// /// entity `T`, for example, we can ask to the parent for the childrens that belongs +// /// to `U`. +// /// +// /// For this to work, `U`, the parent, must have derived the `ForeignKeyable` proc macro +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_search_reverse_side_foreign_key() { +// let some_league: League = League::find_by_pk(&1) +// .await +// .expect("Result variant of the query is err") +// .expect("No result found for the given parameter"); + +// // Computes how many tournaments are pointing to the retrieved league +// let child_tournaments: Vec = Tournament::search_league_childrens(&some_league) +// .await +// .expect("Result variant of the query is err"); + +// assert!(!child_tournaments.is_empty()); +// child_tournaments +// .iter() +// .for_each(|t| assert_eq!(t.league, some_league.id)); +// } + +// /// Same as the search by the reverse side of a foreign key relation +// /// but with the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_search_reverse_side_foreign_key_datasource_mssql() { +// let some_league: League = League::find_by_pk_datasource(&1, SQL_SERVER_DS) +// .await +// .expect("Result variant of the query is err") +// .expect("No result found for the given parameter"); + +// // Computes how many tournaments are pointing to the retrieved league +// let child_tournaments: Vec = +// Tournament::search_league_childrens_datasource(&some_league, SQL_SERVER_DS) +// .await +// .expect("Result variant of the query is err"); + +// assert!(!child_tournaments.is_empty()); +// child_tournaments +// .iter() +// .for_each(|t| assert_eq!(t.league, some_league.id)); +// } + +// /// Same as the search by the reverse side of a foreign key relation +// /// but with the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_search_reverse_side_foreign_key_datasource_mysql() { +// let some_league: League = League::find_by_pk_datasource(&1, MYSQL_DS) +// .await +// .expect("Result variant of the query is err") +// .expect("No result found for the given parameter"); + +// // Computes how many tournaments are pointing to the retrieved league +// let child_tournaments: Vec = +// Tournament::search_league_childrens_datasource(&some_league, MYSQL_DS) +// .await +// .expect("Result variant of the query is err"); + +// assert!(!child_tournaments.is_empty()); +// child_tournaments +// .iter() +// .for_each(|t| assert_eq!(t.league, some_league.id)); +// } diff --git a/tests/crud/insert_operations.rs b/tests/crud/insert_operations.rs index 13e2747e..510e3b7a 100644 --- a/tests/crud/insert_operations.rs +++ b/tests/crud/insert_operations.rs @@ -1,317 +1,317 @@ -//! Integration tests for the CRUD operations available in `Canyon` that -//! generates and executes *INSERT* statements -use canyon_sql::crud::CrudOperations; +// //! Integration tests for the CRUD operations available in `Canyon` that +// //! generates and executes *INSERT* statements +// use canyon_sql::crud::CrudOperations; -#[cfg(feature = "mysql")] -use crate::constants::MYSQL_DS; -#[cfg(feature = "mssql")] -use crate::constants::SQL_SERVER_DS; +// #[cfg(feature = "mysql")] +// use crate::constants::MYSQL_DS; +// #[cfg(feature = "mssql")] +// use crate::constants::SQL_SERVER_DS; -use crate::tests_models::league::*; +// use crate::tests_models::league::*; -/// Inserts a new record on the database, given an entity that is -/// annotated with `#[canyon_entity]` macro over a *T* type. -/// -/// For insert a new record on a database, the *insert* operation needs -/// some special requirements: -/// > - We need a mutable instance of `T`. If the operation completes -/// successfully, the insert operation will automatically set the autogenerated -/// value for the `primary_key` annotated field in it. -/// -/// > - It's considered a good practice to initialize that concrete field with -/// the `Default` trait, because the value on the primary key field will be -/// ignored at the execution time of the insert, and updated with the autogenerated -/// value by the database. -/// -/// By default, the `#[primary_key]` annotation means autogenerated and autoincremental. -/// You can configure not autoincremental via macro annotation parameters (please, -/// refer to the docs [here]() for more info.) -/// -/// If the type hasn't a `#[primary_key]` annotation, or the annotation contains -/// an argument specifying not autoincremental behaviour, all the fields will be -/// inserted on the database and no returning value will be placed in any field. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_insert_operation() { - let mut new_league: League = League { - id: Default::default(), - ext_id: 7892635306594_i64, - slug: "some-new-league".to_string(), - name: "Some New League".to_string(), - region: "Bahía de cochinos".to_string(), - image_url: "https://nobodyspectsandimage.io".to_string(), - }; +// /// Inserts a new record on the database, given an entity that is +// /// annotated with `#[canyon_entity]` macro over a *T* type. +// /// +// /// For insert a new record on a database, the *insert* operation needs +// /// some special requirements: +// /// > - We need a mutable instance of `T`. If the operation completes +// /// successfully, the insert operation will automatically set the autogenerated +// /// value for the `primary_key` annotated field in it. +// /// +// /// > - It's considered a good practice to initialize that concrete field with +// /// the `Default` trait, because the value on the primary key field will be +// /// ignored at the execution time of the insert, and updated with the autogenerated +// /// value by the database. +// /// +// /// By default, the `#[primary_key]` annotation means autogenerated and autoincremental. +// /// You can configure not autoincremental via macro annotation parameters (please, +// /// refer to the docs [here]() for more info.) +// /// +// /// If the type hasn't a `#[primary_key]` annotation, or the annotation contains +// /// an argument specifying not autoincremental behaviour, all the fields will be +// /// inserted on the database and no returning value will be placed in any field. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_insert_operation() { +// let mut new_league: League = League { +// id: Default::default(), +// ext_id: 7892635306594_i64, +// slug: "some-new-league".to_string(), +// name: "Some New League".to_string(), +// region: "Bahía de cochinos".to_string(), +// image_url: "https://nobodyspectsandimage.io".to_string(), +// }; - // We insert the instance on the database, on the `League` entity - new_league.insert().await.expect("Failed insert operation"); +// // We insert the instance on the database, on the `League` entity +// new_league.insert().await.expect("Failed insert operation"); - // Now, in the `id` field of the instance, we have the autogenerated - // value for the primary key field, which is id. So, we can query the - // database again with the find by primary key operation to check if - // the value was really inserted - let inserted_league = League::find_by_pk(&new_league.id) - .await - .expect("Failed the query to the database") - .expect("No entity found for the primary key value passed in"); +// // Now, in the `id` field of the instance, we have the autogenerated +// // value for the primary key field, which is id. So, we can query the +// // database again with the find by primary key operation to check if +// // the value was really inserted +// let inserted_league = League::find_by_pk(&new_league.id) +// .await +// .expect("Failed the query to the database") +// .expect("No entity found for the primary key value passed in"); - assert_eq!(new_league.id, inserted_league.id); -} +// assert_eq!(new_league.id, inserted_league.id); +// } -/// Same as the insert operation above, but targeting the database defined in -/// the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_insert_datasource_mssql_operation() { - let mut new_league: League = League { - id: Default::default(), - ext_id: 7892635306594_i64, - slug: "some-new-league".to_string(), - name: "Some New League".to_string(), - region: "Bahía de cochinos".to_string(), - image_url: "https://nobodyspectsandimage.io".to_string(), - }; +// /// Same as the insert operation above, but targeting the database defined in +// /// the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_insert_datasource_mssql_operation() { +// let mut new_league: League = League { +// id: Default::default(), +// ext_id: 7892635306594_i64, +// slug: "some-new-league".to_string(), +// name: "Some New League".to_string(), +// region: "Bahía de cochinos".to_string(), +// image_url: "https://nobodyspectsandimage.io".to_string(), +// }; - // We insert the instance on the database, on the `League` entity - new_league - .insert_datasource(SQL_SERVER_DS) - .await - .expect("Failed insert datasource operation"); +// // We insert the instance on the database, on the `League` entity +// new_league +// .insert_datasource(SQL_SERVER_DS) +// .await +// .expect("Failed insert datasource operation"); - // Now, in the `id` field of the instance, we have the autogenerated - // value for the primary key field, which is id. So, we can query the - // database again with the find by primary key operation to check if - // the value was really inserted - let inserted_league = League::find_by_pk_datasource(&new_league.id, SQL_SERVER_DS) - .await - .expect("Failed the query to the database") - .expect("No entity found for the primary key value passed in"); +// // Now, in the `id` field of the instance, we have the autogenerated +// // value for the primary key field, which is id. So, we can query the +// // database again with the find by primary key operation to check if +// // the value was really inserted +// let inserted_league = League::find_by_pk_datasource(&new_league.id, SQL_SERVER_DS) +// .await +// .expect("Failed the query to the database") +// .expect("No entity found for the primary key value passed in"); - assert_eq!(new_league.id, inserted_league.id); -} +// assert_eq!(new_league.id, inserted_league.id); +// } -/// Same as the insert operation above, but targeting the database defined in -/// the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_insert_datasource_mysql_operation() { - let mut new_league: League = League { - id: Default::default(), - ext_id: 7892635306594_i64, - slug: "some-new-league".to_string(), - name: "Some New League".to_string(), - region: "Bahía de cochinos".to_string(), - image_url: "https://nobodyspectsandimage.io".to_string(), - }; +// /// Same as the insert operation above, but targeting the database defined in +// /// the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_insert_datasource_mysql_operation() { +// let mut new_league: League = League { +// id: Default::default(), +// ext_id: 7892635306594_i64, +// slug: "some-new-league".to_string(), +// name: "Some New League".to_string(), +// region: "Bahía de cochinos".to_string(), +// image_url: "https://nobodyspectsandimage.io".to_string(), +// }; - // We insert the instance on the database, on the `League` entity - new_league - .insert_datasource(MYSQL_DS) - .await - .expect("Failed insert datasource operation"); +// // We insert the instance on the database, on the `League` entity +// new_league +// .insert_datasource(MYSQL_DS) +// .await +// .expect("Failed insert datasource operation"); - // Now, in the `id` field of the instance, we have the autogenerated - // value for the primary key field, which is id. So, we can query the - // database again with the find by primary key operation to check if - // the value was really inserted - let inserted_league = League::find_by_pk_datasource(&new_league.id, MYSQL_DS) - .await - .expect("Failed the query to the database") - .expect("No entity found for the primary key value passed in"); +// // Now, in the `id` field of the instance, we have the autogenerated +// // value for the primary key field, which is id. So, we can query the +// // database again with the find by primary key operation to check if +// // the value was really inserted +// let inserted_league = League::find_by_pk_datasource(&new_league.id, MYSQL_DS) +// .await +// .expect("Failed the query to the database") +// .expect("No entity found for the primary key value passed in"); - assert_eq!(new_league.id, inserted_league.id); -} +// assert_eq!(new_league.id, inserted_league.id); +// } -/// The multi insert operation is a shorthand for insert multiple instances of *T* -/// in the database at once. -/// -/// It works pretty much the same that the insert operation, with the same behaviour -/// of the `#[primary_key]` annotation over some field. It will auto set the primary -/// key field with the autogenerated value on the database on the insert operation, but -/// for every entity passed in as an array of mutable instances of `T`. -/// -/// The instances without `#[primary_key]` inserts all the values on the instaqce fields -/// on the database. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_multi_insert_operation() { - let mut new_league_mi: League = League { - id: Default::default(), - ext_id: 54376478_i64, - slug: "some-new-random-league".to_string(), - name: "Some New Random League".to_string(), - region: "Unknown".to_string(), - image_url: "https://what-a-league.io".to_string(), - }; - let mut new_league_mi_2: League = League { - id: Default::default(), - ext_id: 3475689769678906_i64, - slug: "new-league-2".to_string(), - name: "New League 2".to_string(), - region: "Really unknown".to_string(), - image_url: "https://what-an-unknown-league.io".to_string(), - }; - let mut new_league_mi_3: League = League { - id: Default::default(), - ext_id: 46756867_i64, - slug: "a-new-multinsert".to_string(), - name: "New League 3".to_string(), - region: "The dark side of the moon".to_string(), - image_url: "https://interplanetary-league.io".to_string(), - }; +// /// The multi insert operation is a shorthand for insert multiple instances of *T* +// /// in the database at once. +// /// +// /// It works pretty much the same that the insert operation, with the same behaviour +// /// of the `#[primary_key]` annotation over some field. It will auto set the primary +// /// key field with the autogenerated value on the database on the insert operation, but +// /// for every entity passed in as an array of mutable instances of `T`. +// /// +// /// The instances without `#[primary_key]` inserts all the values on the instaqce fields +// /// on the database. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_multi_insert_operation() { +// let mut new_league_mi: League = League { +// id: Default::default(), +// ext_id: 54376478_i64, +// slug: "some-new-random-league".to_string(), +// name: "Some New Random League".to_string(), +// region: "Unknown".to_string(), +// image_url: "https://what-a-league.io".to_string(), +// }; +// let mut new_league_mi_2: League = League { +// id: Default::default(), +// ext_id: 3475689769678906_i64, +// slug: "new-league-2".to_string(), +// name: "New League 2".to_string(), +// region: "Really unknown".to_string(), +// image_url: "https://what-an-unknown-league.io".to_string(), +// }; +// let mut new_league_mi_3: League = League { +// id: Default::default(), +// ext_id: 46756867_i64, +// slug: "a-new-multinsert".to_string(), +// name: "New League 3".to_string(), +// region: "The dark side of the moon".to_string(), +// image_url: "https://interplanetary-league.io".to_string(), +// }; - // Insert the instance as database entities - new_league_mi - .insert() - .await - .expect("Failed insert datasource operation"); - new_league_mi_2 - .insert() - .await - .expect("Failed insert datasource operation"); - new_league_mi_3 - .insert() - .await - .expect("Failed insert datasource operation"); +// // Insert the instance as database entities +// new_league_mi +// .insert() +// .await +// .expect("Failed insert datasource operation"); +// new_league_mi_2 +// .insert() +// .await +// .expect("Failed insert datasource operation"); +// new_league_mi_3 +// .insert() +// .await +// .expect("Failed insert datasource operation"); - // Recover the inserted data by primary key - let inserted_league = League::find_by_pk(&new_league_mi.id) - .await - .expect("[1] - Failed the query to the database") - .expect("[1] - No entity found for the primary key value passed in"); - let inserted_league_2 = League::find_by_pk(&new_league_mi_2.id) - .await - .expect("[2] - Failed the query to the database") - .expect("[2] - No entity found for the primary key value passed in"); - let inserted_league_3 = League::find_by_pk(&new_league_mi_3.id) - .await - .expect("[3] - Failed the query to the database") - .expect("[3] - No entity found for the primary key value passed in"); +// // Recover the inserted data by primary key +// let inserted_league = League::find_by_pk(&new_league_mi.id) +// .await +// .expect("[1] - Failed the query to the database") +// .expect("[1] - No entity found for the primary key value passed in"); +// let inserted_league_2 = League::find_by_pk(&new_league_mi_2.id) +// .await +// .expect("[2] - Failed the query to the database") +// .expect("[2] - No entity found for the primary key value passed in"); +// let inserted_league_3 = League::find_by_pk(&new_league_mi_3.id) +// .await +// .expect("[3] - Failed the query to the database") +// .expect("[3] - No entity found for the primary key value passed in"); - assert_eq!(new_league_mi.id, inserted_league.id); - assert_eq!(new_league_mi_2.id, inserted_league_2.id); - assert_eq!(new_league_mi_3.id, inserted_league_3.id); -} +// assert_eq!(new_league_mi.id, inserted_league.id); +// assert_eq!(new_league_mi_2.id, inserted_league_2.id); +// assert_eq!(new_league_mi_3.id, inserted_league_3.id); +// } -/// Same as the multi insert above, but with the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_multi_insert_datasource_mssql_operation() { - let mut new_league_mi: League = League { - id: Default::default(), - ext_id: 54376478_i64, - slug: "some-new-random-league".to_string(), - name: "Some New Random League".to_string(), - region: "Unknown".to_string(), - image_url: "https://what-a-league.io".to_string(), - }; - let mut new_league_mi_2: League = League { - id: Default::default(), - ext_id: 3475689769678906_i64, - slug: "new-league-2".to_string(), - name: "New League 2".to_string(), - region: "Really unknown".to_string(), - image_url: "https://what-an-unknown-league.io".to_string(), - }; - let mut new_league_mi_3: League = League { - id: Default::default(), - ext_id: 46756867_i64, - slug: "a-new-multinsert".to_string(), - name: "New League 3".to_string(), - region: "The dark side of the moon".to_string(), - image_url: "https://interplanetary-league.io".to_string(), - }; +// /// Same as the multi insert above, but with the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_multi_insert_datasource_mssql_operation() { +// let mut new_league_mi: League = League { +// id: Default::default(), +// ext_id: 54376478_i64, +// slug: "some-new-random-league".to_string(), +// name: "Some New Random League".to_string(), +// region: "Unknown".to_string(), +// image_url: "https://what-a-league.io".to_string(), +// }; +// let mut new_league_mi_2: League = League { +// id: Default::default(), +// ext_id: 3475689769678906_i64, +// slug: "new-league-2".to_string(), +// name: "New League 2".to_string(), +// region: "Really unknown".to_string(), +// image_url: "https://what-an-unknown-league.io".to_string(), +// }; +// let mut new_league_mi_3: League = League { +// id: Default::default(), +// ext_id: 46756867_i64, +// slug: "a-new-multinsert".to_string(), +// name: "New League 3".to_string(), +// region: "The dark side of the moon".to_string(), +// image_url: "https://interplanetary-league.io".to_string(), +// }; - // Insert the instance as database entities - new_league_mi - .insert_datasource(SQL_SERVER_DS) - .await - .expect("Failed insert datasource operation"); - new_league_mi_2 - .insert_datasource(SQL_SERVER_DS) - .await - .expect("Failed insert datasource operation"); - new_league_mi_3 - .insert_datasource(SQL_SERVER_DS) - .await - .expect("Failed insert datasource operation"); +// // Insert the instance as database entities +// new_league_mi +// .insert_datasource(SQL_SERVER_DS) +// .await +// .expect("Failed insert datasource operation"); +// new_league_mi_2 +// .insert_datasource(SQL_SERVER_DS) +// .await +// .expect("Failed insert datasource operation"); +// new_league_mi_3 +// .insert_datasource(SQL_SERVER_DS) +// .await +// .expect("Failed insert datasource operation"); - // Recover the inserted data by primary key - let inserted_league = League::find_by_pk_datasource(&new_league_mi.id, SQL_SERVER_DS) - .await - .expect("[1] - Failed the query to the database") - .expect("[1] - No entity found for the primary key value passed in"); - let inserted_league_2 = League::find_by_pk_datasource(&new_league_mi_2.id, SQL_SERVER_DS) - .await - .expect("[2] - Failed the query to the database") - .expect("[2] - No entity found for the primary key value passed in"); - let inserted_league_3 = League::find_by_pk_datasource(&new_league_mi_3.id, SQL_SERVER_DS) - .await - .expect("[3] - Failed the query to the database") - .expect("[3] - No entity found for the primary key value passed in"); +// // Recover the inserted data by primary key +// let inserted_league = League::find_by_pk_datasource(&new_league_mi.id, SQL_SERVER_DS) +// .await +// .expect("[1] - Failed the query to the database") +// .expect("[1] - No entity found for the primary key value passed in"); +// let inserted_league_2 = League::find_by_pk_datasource(&new_league_mi_2.id, SQL_SERVER_DS) +// .await +// .expect("[2] - Failed the query to the database") +// .expect("[2] - No entity found for the primary key value passed in"); +// let inserted_league_3 = League::find_by_pk_datasource(&new_league_mi_3.id, SQL_SERVER_DS) +// .await +// .expect("[3] - Failed the query to the database") +// .expect("[3] - No entity found for the primary key value passed in"); - assert_eq!(new_league_mi.id, inserted_league.id); - assert_eq!(new_league_mi_2.id, inserted_league_2.id); - assert_eq!(new_league_mi_3.id, inserted_league_3.id); -} +// assert_eq!(new_league_mi.id, inserted_league.id); +// assert_eq!(new_league_mi_2.id, inserted_league_2.id); +// assert_eq!(new_league_mi_3.id, inserted_league_3.id); +// } -/// Same as the multi insert above, but with the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_multi_insert_datasource_mysql_operation() { - let mut new_league_mi: League = League { - id: Default::default(), - ext_id: 54376478_i64, - slug: "some-new-random-league".to_string(), - name: "Some New Random League".to_string(), - region: "Unknown".to_string(), - image_url: "https://what-a-league.io".to_string(), - }; - let mut new_league_mi_2: League = League { - id: Default::default(), - ext_id: 3475689769678906_i64, - slug: "new-league-2".to_string(), - name: "New League 2".to_string(), - region: "Really unknown".to_string(), - image_url: "https://what-an-unknown-league.io".to_string(), - }; - let mut new_league_mi_3: League = League { - id: Default::default(), - ext_id: 46756867_i64, - slug: "a-new-multinsert".to_string(), - name: "New League 3".to_string(), - region: "The dark side of the moon".to_string(), - image_url: "https://interplanetary-league.io".to_string(), - }; +// /// Same as the multi insert above, but with the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_multi_insert_datasource_mysql_operation() { +// let mut new_league_mi: League = League { +// id: Default::default(), +// ext_id: 54376478_i64, +// slug: "some-new-random-league".to_string(), +// name: "Some New Random League".to_string(), +// region: "Unknown".to_string(), +// image_url: "https://what-a-league.io".to_string(), +// }; +// let mut new_league_mi_2: League = League { +// id: Default::default(), +// ext_id: 3475689769678906_i64, +// slug: "new-league-2".to_string(), +// name: "New League 2".to_string(), +// region: "Really unknown".to_string(), +// image_url: "https://what-an-unknown-league.io".to_string(), +// }; +// let mut new_league_mi_3: League = League { +// id: Default::default(), +// ext_id: 46756867_i64, +// slug: "a-new-multinsert".to_string(), +// name: "New League 3".to_string(), +// region: "The dark side of the moon".to_string(), +// image_url: "https://interplanetary-league.io".to_string(), +// }; - // Insert the instance as database entities - new_league_mi - .insert_datasource(MYSQL_DS) - .await - .expect("Failed insert datasource operation"); - new_league_mi_2 - .insert_datasource(MYSQL_DS) - .await - .expect("Failed insert datasource operation"); - new_league_mi_3 - .insert_datasource(MYSQL_DS) - .await - .expect("Failed insert datasource operation"); +// // Insert the instance as database entities +// new_league_mi +// .insert_datasource(MYSQL_DS) +// .await +// .expect("Failed insert datasource operation"); +// new_league_mi_2 +// .insert_datasource(MYSQL_DS) +// .await +// .expect("Failed insert datasource operation"); +// new_league_mi_3 +// .insert_datasource(MYSQL_DS) +// .await +// .expect("Failed insert datasource operation"); - // Recover the inserted data by primary key - let inserted_league = League::find_by_pk_datasource(&new_league_mi.id, MYSQL_DS) - .await - .expect("[1] - Failed the query to the database") - .expect("[1] - No entity found for the primary key value passed in"); - let inserted_league_2 = League::find_by_pk_datasource(&new_league_mi_2.id, MYSQL_DS) - .await - .expect("[2] - Failed the query to the database") - .expect("[2] - No entity found for the primary key value passed in"); - let inserted_league_3 = League::find_by_pk_datasource(&new_league_mi_3.id, MYSQL_DS) - .await - .expect("[3] - Failed the query to the database") - .expect("[3] - No entity found for the primary key value passed in"); +// // Recover the inserted data by primary key +// let inserted_league = League::find_by_pk_datasource(&new_league_mi.id, MYSQL_DS) +// .await +// .expect("[1] - Failed the query to the database") +// .expect("[1] - No entity found for the primary key value passed in"); +// let inserted_league_2 = League::find_by_pk_datasource(&new_league_mi_2.id, MYSQL_DS) +// .await +// .expect("[2] - Failed the query to the database") +// .expect("[2] - No entity found for the primary key value passed in"); +// let inserted_league_3 = League::find_by_pk_datasource(&new_league_mi_3.id, MYSQL_DS) +// .await +// .expect("[3] - Failed the query to the database") +// .expect("[3] - No entity found for the primary key value passed in"); - assert_eq!(new_league_mi.id, inserted_league.id); - assert_eq!(new_league_mi_2.id, inserted_league_2.id); - assert_eq!(new_league_mi_3.id, inserted_league_3.id); -} +// assert_eq!(new_league_mi.id, inserted_league.id); +// assert_eq!(new_league_mi_2.id, inserted_league_2.id); +// assert_eq!(new_league_mi_3.id, inserted_league_3.id); +// } diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index f2dc8b57..a4dcc262 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -1,457 +1,457 @@ -#[cfg(feature = "mysql")] -use crate::constants::MYSQL_DS; -#[cfg(feature = "mssql")] -use crate::constants::SQL_SERVER_DS; - -/// Tests for the QueryBuilder available operations within Canyon. -/// -/// QueryBuilder are the way of obtain more flexibility that with -/// the default generated queries, essentially for build the queries -/// with the SQL filters -/// -use canyon_sql::{ - crud::CrudOperations, - query::{operators::Comp, operators::Like, ops::QueryBuilder}, -}; - -use crate::tests_models::league::*; -use crate::tests_models::player::*; -use crate::tests_models::tournament::*; - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[canyon_sql::macros::canyon_tokio_test] -fn test_generated_sql_by_the_select_querybuilder() { - let mut select_with_joins = League::select_query(); - select_with_joins - .inner_join("tournament", "league.id", "tournament.league_id") - .left_join("team", "tournament.id", "player.tournament_id") - .r#where(LeagueFieldValue::id(&7), Comp::Gt) - .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) - .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); - // .query() - // .await; - // NOTE: We don't have in the docker the generated relationships - // with the joins, so for now, we are just going to check that the - // generated SQL by the SelectQueryBuilder is the spected - assert_eq!( - select_with_joins.read_sql(), - "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder() { - // Find all the leagues with ID less or equals that 7 - // and where it's region column value is equals to 'Korea' - let filtered_leagues_result: Result, _> = League::select_query() - .r#where(LeagueFieldValue::id(&50), Comp::LtEq) - .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) - .query() - .await; - - let filtered_leagues: Vec = filtered_leagues_result.unwrap(); - assert!(!filtered_leagues.is_empty()); - - let league_idx_0 = filtered_leagues.first().unwrap(); - assert_eq!(league_idx_0.id, 34); - assert_eq!(league_idx_0.region, "KOREA"); -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_fulllike() { - // Find all the leagues with "LC" in their name - let mut filtered_leagues_result = League::select_query(); - filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_fulllike_datasource_mssql() { - // Find all the leagues with "LC" in their name - let mut filtered_leagues_result = League::select_query_datasource(SQL_SERVER_DS); - filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_fulllike_datasource_mysql() { - // Find all the leagues with "LC" in their name - let mut filtered_leagues_result = League::select_query_datasource(MYSQL_DS); - filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR) ,'%')" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_leftlike() { - // Find all the leagues whose name ends with "CK" - let mut filtered_leagues_result = League::select_query(); - filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_leftlike_datasource_mssql() { - // Find all the leagues whose name ends with "CK" - let mut filtered_leagues_result = League::select_query(); - filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_leftlike_datasource_mysql() { - // Find all the leagues whose name ends with "CK" - let mut filtered_leagues_result = League::select_query_datasource(MYSQL_DS); - filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR))" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_rightlike() { - // Find all the leagues whose name starts with "LC" - let mut filtered_leagues_result = League::select_query(); - filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_rightlike_datasource_mssql() { - // Find all the leagues whose name starts with "LC" - let mut filtered_leagues_result = League::select_query_datasource(SQL_SERVER_DS); - filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" - ) -} -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_rightlike_datasource_mysql() { - // Find all the leagues whose name starts with "LC" - let mut filtered_leagues_result = League::select_query_datasource(MYSQL_DS); - filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS CHAR) ,'%')" - ) -} - -/// Same than the above but with the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_datasource_mssql() { - // Find all the players where its ID column value is greater that 50 - let filtered_find_players = Player::select_query_datasource(SQL_SERVER_DS) - .r#where(PlayerFieldValue::id(&50), Comp::Gt) - .query() - .await; - - assert!(!filtered_find_players.unwrap().is_empty()); -} - -/// Same than the above but with the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_datasource_mysql() { - // Find all the players where its ID column value is greater that 50 - let filtered_find_players = Player::select_query_datasource(MYSQL_DS) - .r#where(PlayerFieldValue::id(&50), Comp::Gt) - .query() - .await; - - assert!(!filtered_find_players.unwrap().is_empty()); -} - -/// Updates the values of the range on entries defined by the constraint parameters -/// in the database entity -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_update_with_querybuilder() { - // Find all the leagues with ID less or equals that 7 - // and where it's region column value is equals to 'Korea' - let mut q = League::update_query(); - q.set(&[ - (LeagueField::slug, "Updated with the QueryBuilder"), - (LeagueField::name, "Random"), - ]) - .r#where(LeagueFieldValue::id(&1), Comp::Gt) - .and(LeagueFieldValue::id(&8), Comp::Lt); - - /* NOTE: Family of QueryBuilders are clone, useful in case of need to read the generated SQL - let qpr = q.clone(); - println!("PSQL: {:?}", qpr.read_sql()); - */ - - // We can now back to the original an throw the query - q.query() - .await - .expect("Failed to update records with the querybuilder"); - - let found_updated_values = League::select_query() - .r#where(LeagueFieldValue::id(&1), Comp::Gt) - .and(LeagueFieldValue::id(&7), Comp::Lt) - .query() - .await - .expect("Failed to retrieve database League entries with the querybuilder"); - - found_updated_values - .iter() - .for_each(|league| assert_eq!(league.slug, "Updated with the QueryBuilder")); -} - -/// Same as above, but with the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_update_with_querybuilder_datasource_mssql() { - // Find all the leagues with ID less or equals that 7 - // and where it's region column value is equals to 'Korea' - let mut q = Player::update_query_datasource(SQL_SERVER_DS); - q.set(&[ - (PlayerField::summoner_name, "Random updated player name"), - (PlayerField::first_name, "I am an updated first name"), - ]) - .r#where(PlayerFieldValue::id(&1), Comp::Gt) - .and(PlayerFieldValue::id(&8), Comp::Lt) - .query() - .await - .expect("Failed to update records with the querybuilder"); - - let found_updated_values = Player::select_query_datasource(SQL_SERVER_DS) - .r#where(PlayerFieldValue::id(&1), Comp::Gt) - .and(PlayerFieldValue::id(&7), Comp::LtEq) - .query() - .await - .expect("Failed to retrieve database League entries with the querybuilder"); - - found_updated_values.iter().for_each(|player| { - assert_eq!(player.summoner_name, "Random updated player name"); - assert_eq!(player.first_name, "I am an updated first name"); - }); -} - -/// Same as above, but with the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_update_with_querybuilder_datasource_mysql() { - // Find all the leagues with ID less or equals that 7 - // and where it's region column value is equals to 'Korea' - - let mut q = Player::update_query_datasource(MYSQL_DS); - q.set(&[ - (PlayerField::summoner_name, "Random updated player name"), - (PlayerField::first_name, "I am an updated first name"), - ]) - .r#where(PlayerFieldValue::id(&1), Comp::Gt) - .and(PlayerFieldValue::id(&8), Comp::Lt) - .query() - .await - .expect("Failed to update records with the querybuilder"); - - let found_updated_values = Player::select_query_datasource(MYSQL_DS) - .r#where(PlayerFieldValue::id(&1), Comp::Gt) - .and(PlayerFieldValue::id(&7), Comp::LtEq) - .query() - .await - .expect("Failed to retrieve database League entries with the querybuilder"); - - found_updated_values.iter().for_each(|player| { - assert_eq!(player.summoner_name, "Random updated player name"); - assert_eq!(player.first_name, "I am an updated first name"); - }); -} - -/// Deletes entries from the mapped entity `T` that are in the ranges filtered -/// with the QueryBuilder -/// -/// Note if the database is persisted (not created and destroyed on every docker or -/// GitHub Action wake up), it won't delete things that already have been deleted, -/// but this isn't an error. They just don't exists. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_delete_with_querybuilder() { - Tournament::delete_query() - .r#where(TournamentFieldValue::id(&14), Comp::Gt) - .and(TournamentFieldValue::id(&16), Comp::Lt) - .query() - .await - .expect("Error connecting with the database on the delete operation"); - - assert_eq!(Tournament::find_by_pk(&15).await.unwrap(), None); -} - -/// Same as the above delete, but with the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_delete_with_querybuilder_datasource_mssql() { - Player::delete_query_datasource(SQL_SERVER_DS) - .r#where(PlayerFieldValue::id(&120), Comp::Gt) - .and(PlayerFieldValue::id(&130), Comp::Lt) - .query() - .await - .expect("Error connecting with the database when we are going to delete data! :)"); - - assert!(Player::select_query_datasource(SQL_SERVER_DS) - .r#where(PlayerFieldValue::id(&122), Comp::Eq) - .query() - .await - .unwrap() - .is_empty()); -} - -/// Same as the above delete, but with the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_delete_with_querybuilder_datasource_mysql() { - Player::delete_query_datasource(MYSQL_DS) - .r#where(PlayerFieldValue::id(&120), Comp::Gt) - .and(PlayerFieldValue::id(&130), Comp::Lt) - .query() - .await - .expect("Error connecting with the database when we are going to delete data! :)"); - - assert!(Player::select_query_datasource(MYSQL_DS) - .r#where(PlayerFieldValue::id(&122), Comp::Eq) - .query() - .await - .unwrap() - .is_empty()); -} - -/// Tests for the generated SQL query after use the -/// WHERE clause -#[canyon_sql::macros::canyon_tokio_test] -fn test_where_clause() { - let mut l = League::select_query(); - l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq); - - assert_eq!(l.read_sql(), "SELECT * FROM league WHERE name = $1") -} - -/// Tests for the generated SQL query after use the -/// AND clause -#[canyon_sql::macros::canyon_tokio_test] -fn test_and_clause() { - let mut l = League::select_query(); - l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) - .and(LeagueFieldValue::id(&10), Comp::LtEq); - - assert_eq!( - l.read_sql().trim(), - "SELECT * FROM league WHERE name = $1 AND id <= $2" - ) -} - -/// Tests for the generated SQL query after use the -/// AND clause -#[canyon_sql::macros::canyon_tokio_test] -fn test_and_clause_with_in_constraint() { - let mut l = League::select_query(); - l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) - .and_values_in(LeagueField::id, &[1, 7, 10]); - - assert_eq!( - l.read_sql().trim(), - "SELECT * FROM league WHERE name = $1 AND id IN ($1, $2, $3)" - ) -} - -/// Tests for the generated SQL query after use the -/// AND clause -#[canyon_sql::macros::canyon_tokio_test] -fn test_or_clause() { - let mut l = League::select_query(); - l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) - .or(LeagueFieldValue::id(&10), Comp::LtEq); - - assert_eq!( - l.read_sql().trim(), - "SELECT * FROM league WHERE name = $1 OR id <= $2" - ) -} - -/// Tests for the generated SQL query after use the -/// AND clause -#[canyon_sql::macros::canyon_tokio_test] -fn test_or_clause_with_in_constraint() { - let mut l = League::select_query(); - l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) - .or_values_in(LeagueField::id, &[1, 7, 10]); - - assert_eq!( - l.read_sql(), - "SELECT * FROM league WHERE name = $1 OR id IN ($1, $2, $3)" - ) -} - -/// Tests for the generated SQL query after use the -/// AND clause -#[canyon_sql::macros::canyon_tokio_test] -fn test_order_by_clause() { - let mut l = League::select_query(); - l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) - .order_by(LeagueField::id, false); - - assert_eq!( - l.read_sql(), - "SELECT * FROM league WHERE name = $1 ORDER BY id" - ) -} +// #[cfg(feature = "mysql")] +// use crate::constants::MYSQL_DS; +// #[cfg(feature = "mssql")] +// use crate::constants::SQL_SERVER_DS; + +// /// Tests for the QueryBuilder available operations within Canyon. +// /// +// /// QueryBuilder are the way of obtain more flexibility that with +// /// the default generated queries, essentially for build the queries +// /// with the SQL filters +// /// +// use canyon_sql::{ +// crud::CrudOperations, +// query::{operators::Comp, operators::Like, ops::QueryBuilder}, +// }; + +// use crate::tests_models::league::*; +// use crate::tests_models::player::*; +// use crate::tests_models::tournament::*; + +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_generated_sql_by_the_select_querybuilder() { +// let mut select_with_joins = League::select_query(); +// select_with_joins +// .inner_join("tournament", "league.id", "tournament.league_id") +// .left_join("team", "tournament.id", "player.tournament_id") +// .r#where(LeagueFieldValue::id(&7), Comp::Gt) +// .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) +// .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); +// // .query() +// // .await; +// // NOTE: We don't have in the docker the generated relationships +// // with the joins, so for now, we are just going to check that the +// // generated SQL by the SelectQueryBuilder is the spected +// assert_eq!( +// select_with_joins.read_sql(), +// "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" +// ) +// } + +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder() { +// // Find all the leagues with ID less or equals that 7 +// // and where it's region column value is equals to 'Korea' +// let filtered_leagues_result: Result, _> = League::select_query() +// .r#where(LeagueFieldValue::id(&50), Comp::LtEq) +// .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) +// .query() +// .await; + +// let filtered_leagues: Vec = filtered_leagues_result.unwrap(); +// assert!(!filtered_leagues.is_empty()); + +// let league_idx_0 = filtered_leagues.first().unwrap(); +// assert_eq!(league_idx_0.id, 34); +// assert_eq!(league_idx_0.region, "KOREA"); +// } + +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_fulllike() { +// // Find all the leagues with "LC" in their name +// let mut filtered_leagues_result = League::select_query(); +// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); + +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" +// ) +// } + +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_fulllike_datasource_mssql() { +// // Find all the leagues with "LC" in their name +// let mut filtered_leagues_result = League::select_query_datasource(SQL_SERVER_DS); +// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); + +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" +// ) +// } + +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_fulllike_datasource_mysql() { +// // Find all the leagues with "LC" in their name +// let mut filtered_leagues_result = League::select_query_datasource(MYSQL_DS); +// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); + +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR) ,'%')" +// ) +// } + +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_leftlike() { +// // Find all the leagues whose name ends with "CK" +// let mut filtered_leagues_result = League::select_query(); +// filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); + +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" +// ) +// } + +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_leftlike_datasource_mssql() { +// // Find all the leagues whose name ends with "CK" +// let mut filtered_leagues_result = League::select_query(); +// filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); + +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" +// ) +// } + +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_leftlike_datasource_mysql() { +// // Find all the leagues whose name ends with "CK" +// let mut filtered_leagues_result = League::select_query_datasource(MYSQL_DS); +// filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); + +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR))" +// ) +// } + +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_rightlike() { +// // Find all the leagues whose name starts with "LC" +// let mut filtered_leagues_result = League::select_query(); +// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); + +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" +// ) +// } + +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_rightlike_datasource_mssql() { +// // Find all the leagues whose name starts with "LC" +// let mut filtered_leagues_result = League::select_query_datasource(SQL_SERVER_DS); +// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); + +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" +// ) +// } +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_rightlike_datasource_mysql() { +// // Find all the leagues whose name starts with "LC" +// let mut filtered_leagues_result = League::select_query_datasource(MYSQL_DS); +// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); + +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS CHAR) ,'%')" +// ) +// } + +// /// Same than the above but with the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_datasource_mssql() { +// // Find all the players where its ID column value is greater that 50 +// let filtered_find_players = Player::select_query_datasource(SQL_SERVER_DS) +// .r#where(PlayerFieldValue::id(&50), Comp::Gt) +// .query() +// .await; + +// assert!(!filtered_find_players.unwrap().is_empty()); +// } + +// /// Same than the above but with the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_datasource_mysql() { +// // Find all the players where its ID column value is greater that 50 +// let filtered_find_players = Player::select_query_datasource(MYSQL_DS) +// .r#where(PlayerFieldValue::id(&50), Comp::Gt) +// .query() +// .await; + +// assert!(!filtered_find_players.unwrap().is_empty()); +// } + +// /// Updates the values of the range on entries defined by the constraint parameters +// /// in the database entity +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_update_with_querybuilder() { +// // Find all the leagues with ID less or equals that 7 +// // and where it's region column value is equals to 'Korea' +// let mut q = League::update_query(); +// q.set(&[ +// (LeagueField::slug, "Updated with the QueryBuilder"), +// (LeagueField::name, "Random"), +// ]) +// .r#where(LeagueFieldValue::id(&1), Comp::Gt) +// .and(LeagueFieldValue::id(&8), Comp::Lt); + +// /* NOTE: Family of QueryBuilders are clone, useful in case of need to read the generated SQL +// let qpr = q.clone(); +// println!("PSQL: {:?}", qpr.read_sql()); +// */ + +// // We can now back to the original an throw the query +// q.query() +// .await +// .expect("Failed to update records with the querybuilder"); + +// let found_updated_values = League::select_query() +// .r#where(LeagueFieldValue::id(&1), Comp::Gt) +// .and(LeagueFieldValue::id(&7), Comp::Lt) +// .query() +// .await +// .expect("Failed to retrieve database League entries with the querybuilder"); + +// found_updated_values +// .iter() +// .for_each(|league| assert_eq!(league.slug, "Updated with the QueryBuilder")); +// } + +// /// Same as above, but with the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_update_with_querybuilder_datasource_mssql() { +// // Find all the leagues with ID less or equals that 7 +// // and where it's region column value is equals to 'Korea' +// let mut q = Player::update_query_datasource(SQL_SERVER_DS); +// q.set(&[ +// (PlayerField::summoner_name, "Random updated player name"), +// (PlayerField::first_name, "I am an updated first name"), +// ]) +// .r#where(PlayerFieldValue::id(&1), Comp::Gt) +// .and(PlayerFieldValue::id(&8), Comp::Lt) +// .query() +// .await +// .expect("Failed to update records with the querybuilder"); + +// let found_updated_values = Player::select_query_datasource(SQL_SERVER_DS) +// .r#where(PlayerFieldValue::id(&1), Comp::Gt) +// .and(PlayerFieldValue::id(&7), Comp::LtEq) +// .query() +// .await +// .expect("Failed to retrieve database League entries with the querybuilder"); + +// found_updated_values.iter().for_each(|player| { +// assert_eq!(player.summoner_name, "Random updated player name"); +// assert_eq!(player.first_name, "I am an updated first name"); +// }); +// } + +// /// Same as above, but with the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_update_with_querybuilder_datasource_mysql() { +// // Find all the leagues with ID less or equals that 7 +// // and where it's region column value is equals to 'Korea' + +// let mut q = Player::update_query_datasource(MYSQL_DS); +// q.set(&[ +// (PlayerField::summoner_name, "Random updated player name"), +// (PlayerField::first_name, "I am an updated first name"), +// ]) +// .r#where(PlayerFieldValue::id(&1), Comp::Gt) +// .and(PlayerFieldValue::id(&8), Comp::Lt) +// .query() +// .await +// .expect("Failed to update records with the querybuilder"); + +// let found_updated_values = Player::select_query_datasource(MYSQL_DS) +// .r#where(PlayerFieldValue::id(&1), Comp::Gt) +// .and(PlayerFieldValue::id(&7), Comp::LtEq) +// .query() +// .await +// .expect("Failed to retrieve database League entries with the querybuilder"); + +// found_updated_values.iter().for_each(|player| { +// assert_eq!(player.summoner_name, "Random updated player name"); +// assert_eq!(player.first_name, "I am an updated first name"); +// }); +// } + +// /// Deletes entries from the mapped entity `T` that are in the ranges filtered +// /// with the QueryBuilder +// /// +// /// Note if the database is persisted (not created and destroyed on every docker or +// /// GitHub Action wake up), it won't delete things that already have been deleted, +// /// but this isn't an error. They just don't exists. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_delete_with_querybuilder() { +// Tournament::delete_query() +// .r#where(TournamentFieldValue::id(&14), Comp::Gt) +// .and(TournamentFieldValue::id(&16), Comp::Lt) +// .query() +// .await +// .expect("Error connecting with the database on the delete operation"); + +// assert_eq!(Tournament::find_by_pk(&15).await.unwrap(), None); +// } + +// /// Same as the above delete, but with the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_delete_with_querybuilder_datasource_mssql() { +// Player::delete_query_datasource(SQL_SERVER_DS) +// .r#where(PlayerFieldValue::id(&120), Comp::Gt) +// .and(PlayerFieldValue::id(&130), Comp::Lt) +// .query() +// .await +// .expect("Error connecting with the database when we are going to delete data! :)"); + +// assert!(Player::select_query_datasource(SQL_SERVER_DS) +// .r#where(PlayerFieldValue::id(&122), Comp::Eq) +// .query() +// .await +// .unwrap() +// .is_empty()); +// } + +// /// Same as the above delete, but with the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_delete_with_querybuilder_datasource_mysql() { +// Player::delete_query_datasource(MYSQL_DS) +// .r#where(PlayerFieldValue::id(&120), Comp::Gt) +// .and(PlayerFieldValue::id(&130), Comp::Lt) +// .query() +// .await +// .expect("Error connecting with the database when we are going to delete data! :)"); + +// assert!(Player::select_query_datasource(MYSQL_DS) +// .r#where(PlayerFieldValue::id(&122), Comp::Eq) +// .query() +// .await +// .unwrap() +// .is_empty()); +// } + +// /// Tests for the generated SQL query after use the +// /// WHERE clause +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_where_clause() { +// let mut l = League::select_query(); +// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq); + +// assert_eq!(l.read_sql(), "SELECT * FROM league WHERE name = $1") +// } + +// /// Tests for the generated SQL query after use the +// /// AND clause +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_and_clause() { +// let mut l = League::select_query(); +// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) +// .and(LeagueFieldValue::id(&10), Comp::LtEq); + +// assert_eq!( +// l.read_sql().trim(), +// "SELECT * FROM league WHERE name = $1 AND id <= $2" +// ) +// } + +// /// Tests for the generated SQL query after use the +// /// AND clause +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_and_clause_with_in_constraint() { +// let mut l = League::select_query(); +// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) +// .and_values_in(LeagueField::id, &[1, 7, 10]); + +// assert_eq!( +// l.read_sql().trim(), +// "SELECT * FROM league WHERE name = $1 AND id IN ($1, $2, $3)" +// ) +// } + +// /// Tests for the generated SQL query after use the +// /// AND clause +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_or_clause() { +// let mut l = League::select_query(); +// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) +// .or(LeagueFieldValue::id(&10), Comp::LtEq); + +// assert_eq!( +// l.read_sql().trim(), +// "SELECT * FROM league WHERE name = $1 OR id <= $2" +// ) +// } + +// /// Tests for the generated SQL query after use the +// /// AND clause +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_or_clause_with_in_constraint() { +// let mut l = League::select_query(); +// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) +// .or_values_in(LeagueField::id, &[1, 7, 10]); + +// assert_eq!( +// l.read_sql(), +// "SELECT * FROM league WHERE name = $1 OR id IN ($1, $2, $3)" +// ) +// } + +// /// Tests for the generated SQL query after use the +// /// AND clause +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_order_by_clause() { +// let mut l = League::select_query(); +// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) +// .order_by(LeagueField::id, false); + +// assert_eq!( +// l.read_sql(), +// "SELECT * FROM league WHERE name = $1 ORDER BY id" +// ) +// } diff --git a/tests/crud/select_operations.rs b/tests/crud/select_operations.rs index f3342c02..878936e7 100644 --- a/tests/crud/select_operations.rs +++ b/tests/crud/select_operations.rs @@ -58,6 +58,7 @@ fn test_crud_find_all_datasource_mssql() { fn test_crud_find_all_datasource_mysql() { let find_all_result: Result, Box> = League::find_all_datasource(MYSQL_DS).await; + // Connection doesn't return an error assert!(!find_all_result.is_err()); assert!(!find_all_result.unwrap().is_empty()); @@ -72,106 +73,106 @@ fn test_crud_find_all_unchecked_datasource() { assert!(!find_all_result.is_empty()); } -/// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -/// defined with the #[primary_key] attribute over some field of the type. -/// -/// Uses the *default datasource*. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_by_pk() { - let find_by_pk_result: Result, Box> = - League::find_by_pk(&1).await; - assert!(find_by_pk_result.as_ref().unwrap().is_some()); - - let some_league = find_by_pk_result.unwrap().unwrap(); - assert_eq!(some_league.id, 1); - assert_eq!(some_league.ext_id, 100695891328981122_i64); - assert_eq!(some_league.slug, "european-masters"); - assert_eq!(some_league.name, "European Masters"); - assert_eq!(some_league.region, "EUROPE"); - assert_eq!( - some_league.image_url, - "http://static.lolesports.com/leagues/EM_Bug_Outline1.png" - ); -} - -/// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -/// defined with the #[primary_key] attribute over some field of the type. -/// -/// Uses the *specified datasource mssql* in the second parameter of the function call. -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_by_pk_datasource_mssql() { - let find_by_pk_result: Result, Box> = - League::find_by_pk_datasource(&27, SQL_SERVER_DS).await; - assert!(find_by_pk_result.as_ref().unwrap().is_some()); - - let some_league = find_by_pk_result.unwrap().unwrap(); - assert_eq!(some_league.id, 27); - assert_eq!(some_league.ext_id, 107898214974993351_i64); - assert_eq!(some_league.slug, "college_championship"); - assert_eq!(some_league.name, "College Championship"); - assert_eq!(some_league.region, "NORTH AMERICA"); - assert_eq!( - some_league.image_url, - "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" - ); -} - -/// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -/// defined with the #[primary_key] attribute over some field of the type. -/// -/// Uses the *specified datasource mysql* in the second parameter of the function call. -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_by_pk_datasource_mysql() { - let find_by_pk_result: Result, Box> = - League::find_by_pk_datasource(&27, MYSQL_DS).await; - assert!(find_by_pk_result.as_ref().unwrap().is_some()); - - let some_league = find_by_pk_result.unwrap().unwrap(); - assert_eq!(some_league.id, 27); - assert_eq!(some_league.ext_id, 107898214974993351_i64); - assert_eq!(some_league.slug, "college_championship"); - assert_eq!(some_league.name, "College Championship"); - assert_eq!(some_league.region, "NORTH AMERICA"); - assert_eq!( - some_league.image_url, - "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" - ); -} - -/// Counts how many rows contains an entity on the target database. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_count_operation() { - assert_eq!( - League::find_all().await.unwrap().len() as i64, - League::count().await.unwrap() - ); -} - -/// Counts how many rows contains an entity on the target database using -/// the specified datasource mssql -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_count_datasource_operation_mssql() { - assert_eq!( - League::find_all_datasource(SQL_SERVER_DS) - .await - .unwrap() - .len() as i64, - League::count_datasource(SQL_SERVER_DS).await.unwrap() - ); -} - -/// Counts how many rows contains an entity on the target database using -/// the specified datasource mysql -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_count_datasource_operation_mysql() { - assert_eq!( - League::find_all_datasource(MYSQL_DS).await.unwrap().len() as i64, - League::count_datasource(MYSQL_DS).await.unwrap() - ); -} +// /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +// /// defined with the #[primary_key] attribute over some field of the type. +// /// +// /// Uses the *default datasource*. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_by_pk() { +// let find_by_pk_result: Result, Box> = +// League::find_by_pk(&1).await; +// assert!(find_by_pk_result.as_ref().unwrap().is_some()); + +// let some_league = find_by_pk_result.unwrap().unwrap(); +// assert_eq!(some_league.id, 1); +// assert_eq!(some_league.ext_id, 100695891328981122_i64); +// assert_eq!(some_league.slug, "european-masters"); +// assert_eq!(some_league.name, "European Masters"); +// assert_eq!(some_league.region, "EUROPE"); +// assert_eq!( +// some_league.image_url, +// "http://static.lolesports.com/leagues/EM_Bug_Outline1.png" +// ); +// } + +// /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +// /// defined with the #[primary_key] attribute over some field of the type. +// /// +// /// Uses the *specified datasource mssql* in the second parameter of the function call. +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_by_pk_datasource_mssql() { +// let find_by_pk_result: Result, Box> = +// League::find_by_pk_datasource(&27, SQL_SERVER_DS).await; +// assert!(find_by_pk_result.as_ref().unwrap().is_some()); + +// let some_league = find_by_pk_result.unwrap().unwrap(); +// assert_eq!(some_league.id, 27); +// assert_eq!(some_league.ext_id, 107898214974993351_i64); +// assert_eq!(some_league.slug, "college_championship"); +// assert_eq!(some_league.name, "College Championship"); +// assert_eq!(some_league.region, "NORTH AMERICA"); +// assert_eq!( +// some_league.image_url, +// "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" +// ); +// } + +// /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +// /// defined with the #[primary_key] attribute over some field of the type. +// /// +// /// Uses the *specified datasource mysql* in the second parameter of the function call. +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_by_pk_datasource_mysql() { +// let find_by_pk_result: Result, Box> = +// League::find_by_pk_datasource(&27, MYSQL_DS).await; +// assert!(find_by_pk_result.as_ref().unwrap().is_some()); + +// let some_league = find_by_pk_result.unwrap().unwrap(); +// assert_eq!(some_league.id, 27); +// assert_eq!(some_league.ext_id, 107898214974993351_i64); +// assert_eq!(some_league.slug, "college_championship"); +// assert_eq!(some_league.name, "College Championship"); +// assert_eq!(some_league.region, "NORTH AMERICA"); +// assert_eq!( +// some_league.image_url, +// "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" +// ); +// } + +// /// Counts how many rows contains an entity on the target database. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_count_operation() { +// assert_eq!( +// League::find_all().await.unwrap().len() as i64, +// League::count().await.unwrap() +// ); +// } + +// /// Counts how many rows contains an entity on the target database using +// /// the specified datasource mssql +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_count_datasource_operation_mssql() { +// assert_eq!( +// League::find_all_datasource(SQL_SERVER_DS) +// .await +// .unwrap() +// .len() as i64, +// League::count_datasource(SQL_SERVER_DS).await.unwrap() +// ); +// } + +// /// Counts how many rows contains an entity on the target database using +// /// the specified datasource mysql +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_count_datasource_operation_mysql() { +// assert_eq!( +// League::find_all_datasource(MYSQL_DS).await.unwrap().len() as i64, +// League::count_datasource(MYSQL_DS).await.unwrap() +// ); +// } diff --git a/tests/crud/update_operations.rs b/tests/crud/update_operations.rs index dfc4af15..36b8c090 100644 --- a/tests/crud/update_operations.rs +++ b/tests/crud/update_operations.rs @@ -1,142 +1,142 @@ -use crate::tests_models::league::*; -// Integration tests for the CRUD operations available in `Canyon` that -/// generates and executes *UPDATE* statements -use canyon_sql::crud::CrudOperations; - -#[cfg(feature = "mysql")] -use crate::constants::MYSQL_DS; -#[cfg(feature = "mssql")] -use crate::constants::SQL_SERVER_DS; - -/// Update operation is a *CRUD* method defined for some entity `T`, that works by appliying -/// some change to a Rust's entity instance, and persisting them into the database. -/// -/// The `t.update(&self)` operation is only enabled for types that -/// has, at least, one of it's fields annotated with a `#[primary_key]` -/// operation, because we use that concrete field to construct the clause that targets -/// that entity. -/// -/// Attempt of usage the `t.update(&self)` method on an entity without `#[primary_key]` -/// will raise a runtime error. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_update_method_operation() { - // We first retrieve some entity from the database. Note that we must make - // the retrieved instance mutable of clone it to a new mutable resource - let mut updt_candidate: League = League::find_by_pk(&1) - .await - .expect("[1] - Failed the query to the database") - .expect("[1] - No entity found for the primary key value passed in"); - - // The ext_id field value is extracted from the sql scripts under the - // docker/sql folder. We are retrieving the first entity inserted at the - // wake up time of the database, and now checking some of its properties. - assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); - - // Modify the value, and perform the update - let updt_value: i64 = 593064_i64; - updt_candidate.ext_id = updt_value; - updt_candidate - .update() - .await - .expect("Failed the update operation"); - - // Retrieve it again, and check if the value was really updated - let updt_entity: League = League::find_by_pk(&1) - .await - .expect("[2] - Failed the query to the database") - .expect("[2] - No entity found for the primary key value passed in"); - - assert_eq!(updt_entity.ext_id, updt_value); - - // We rollback the changes to the initial value to don't broke other tests - // the next time that will run - updt_candidate.ext_id = 100695891328981122_i64; - updt_candidate - .update() - .await - .expect("Failed the restablish initial value update operation"); -} - -/// Same as the above test, but with the specified datasource. -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_update_datasource_mssql_method_operation() { - // We first retrieve some entity from the database. Note that we must make - // the retrieved instance mutable of clone it to a new mutable resource - let mut updt_candidate: League = League::find_by_pk_datasource(&1, SQL_SERVER_DS) - .await - .expect("[1] - Failed the query to the database") - .expect("[1] - No entity found for the primary key value passed in"); - - // The ext_id field value is extracted from the sql scripts under the - // docker/sql folder. We are retrieving the first entity inserted at the - // wake up time of the database, and now checking some of its properties. - assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); - - // Modify the value, and perform the update - let updt_value: i64 = 59306442534_i64; - updt_candidate.ext_id = updt_value; - updt_candidate - .update_datasource(SQL_SERVER_DS) - .await - .expect("Failed the update operation"); - - // Retrieve it again, and check if the value was really updated - let updt_entity: League = League::find_by_pk_datasource(&1, SQL_SERVER_DS) - .await - .expect("[2] - Failed the query to the database") - .expect("[2] - No entity found for the primary key value passed in"); - - assert_eq!(updt_entity.ext_id, updt_value); - - // We rollback the changes to the initial value to don't broke other tests - // the next time that will run - updt_candidate.ext_id = 100695891328981122_i64; - updt_candidate - .update_datasource(SQL_SERVER_DS) - .await - .expect("Failed to restablish the initial value update operation"); -} - -/// Same as the above test, but with the specified datasource. -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_update_datasource_mysql_method_operation() { - // We first retrieve some entity from the database. Note that we must make - // the retrieved instance mutable of clone it to a new mutable resource - - let mut updt_candidate: League = League::find_by_pk_datasource(&1, MYSQL_DS) - .await - .expect("[1] - Failed the query to the database") - .expect("[1] - No entity found for the primary key value passed in"); - - // The ext_id field value is extracted from the sql scripts under the - // docker/sql folder. We are retrieving the first entity inserted at the - // wake up time of the database, and now checking some of its properties. - assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); - - // Modify the value, and perform the update - let updt_value: i64 = 59306442534_i64; - updt_candidate.ext_id = updt_value; - updt_candidate - .update_datasource(MYSQL_DS) - .await - .expect("Failed the update operation"); - - // Retrieve it again, and check if the value was really updated - let updt_entity: League = League::find_by_pk_datasource(&1, MYSQL_DS) - .await - .expect("[2] - Failed the query to the database") - .expect("[2] - No entity found for the primary key value passed in"); - - assert_eq!(updt_entity.ext_id, updt_value); - - // We rollback the changes to the initial value to don't broke other tests - // the next time that will run - updt_candidate.ext_id = 100695891328981122_i64; - updt_candidate - .update_datasource(MYSQL_DS) - .await - .expect("Failed to restablish the initial value update operation"); -} +// use crate::tests_models::league::*; +// // Integration tests for the CRUD operations available in `Canyon` that +// /// generates and executes *UPDATE* statements +// use canyon_sql::crud::CrudOperations; + +// #[cfg(feature = "mysql")] +// use crate::constants::MYSQL_DS; +// #[cfg(feature = "mssql")] +// use crate::constants::SQL_SERVER_DS; + +// /// Update operation is a *CRUD* method defined for some entity `T`, that works by appliying +// /// some change to a Rust's entity instance, and persisting them into the database. +// /// +// /// The `t.update(&self)` operation is only enabled for types that +// /// has, at least, one of it's fields annotated with a `#[primary_key]` +// /// operation, because we use that concrete field to construct the clause that targets +// /// that entity. +// /// +// /// Attempt of usage the `t.update(&self)` method on an entity without `#[primary_key]` +// /// will raise a runtime error. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_update_method_operation() { +// // We first retrieve some entity from the database. Note that we must make +// // the retrieved instance mutable of clone it to a new mutable resource +// let mut updt_candidate: League = League::find_by_pk(&1) +// .await +// .expect("[1] - Failed the query to the database") +// .expect("[1] - No entity found for the primary key value passed in"); + +// // The ext_id field value is extracted from the sql scripts under the +// // docker/sql folder. We are retrieving the first entity inserted at the +// // wake up time of the database, and now checking some of its properties. +// assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); + +// // Modify the value, and perform the update +// let updt_value: i64 = 593064_i64; +// updt_candidate.ext_id = updt_value; +// updt_candidate +// .update() +// .await +// .expect("Failed the update operation"); + +// // Retrieve it again, and check if the value was really updated +// let updt_entity: League = League::find_by_pk(&1) +// .await +// .expect("[2] - Failed the query to the database") +// .expect("[2] - No entity found for the primary key value passed in"); + +// assert_eq!(updt_entity.ext_id, updt_value); + +// // We rollback the changes to the initial value to don't broke other tests +// // the next time that will run +// updt_candidate.ext_id = 100695891328981122_i64; +// updt_candidate +// .update() +// .await +// .expect("Failed the restablish initial value update operation"); +// } + +// /// Same as the above test, but with the specified datasource. +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_update_datasource_mssql_method_operation() { +// // We first retrieve some entity from the database. Note that we must make +// // the retrieved instance mutable of clone it to a new mutable resource +// let mut updt_candidate: League = League::find_by_pk_datasource(&1, SQL_SERVER_DS) +// .await +// .expect("[1] - Failed the query to the database") +// .expect("[1] - No entity found for the primary key value passed in"); + +// // The ext_id field value is extracted from the sql scripts under the +// // docker/sql folder. We are retrieving the first entity inserted at the +// // wake up time of the database, and now checking some of its properties. +// assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); + +// // Modify the value, and perform the update +// let updt_value: i64 = 59306442534_i64; +// updt_candidate.ext_id = updt_value; +// updt_candidate +// .update_datasource(SQL_SERVER_DS) +// .await +// .expect("Failed the update operation"); + +// // Retrieve it again, and check if the value was really updated +// let updt_entity: League = League::find_by_pk_datasource(&1, SQL_SERVER_DS) +// .await +// .expect("[2] - Failed the query to the database") +// .expect("[2] - No entity found for the primary key value passed in"); + +// assert_eq!(updt_entity.ext_id, updt_value); + +// // We rollback the changes to the initial value to don't broke other tests +// // the next time that will run +// updt_candidate.ext_id = 100695891328981122_i64; +// updt_candidate +// .update_datasource(SQL_SERVER_DS) +// .await +// .expect("Failed to restablish the initial value update operation"); +// } + +// /// Same as the above test, but with the specified datasource. +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_update_datasource_mysql_method_operation() { +// // We first retrieve some entity from the database. Note that we must make +// // the retrieved instance mutable of clone it to a new mutable resource + +// let mut updt_candidate: League = League::find_by_pk_datasource(&1, MYSQL_DS) +// .await +// .expect("[1] - Failed the query to the database") +// .expect("[1] - No entity found for the primary key value passed in"); + +// // The ext_id field value is extracted from the sql scripts under the +// // docker/sql folder. We are retrieving the first entity inserted at the +// // wake up time of the database, and now checking some of its properties. +// assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); + +// // Modify the value, and perform the update +// let updt_value: i64 = 59306442534_i64; +// updt_candidate.ext_id = updt_value; +// updt_candidate +// .update_datasource(MYSQL_DS) +// .await +// .expect("Failed the update operation"); + +// // Retrieve it again, and check if the value was really updated +// let updt_entity: League = League::find_by_pk_datasource(&1, MYSQL_DS) +// .await +// .expect("[2] - Failed the query to the database") +// .expect("[2] - No entity found for the primary key value passed in"); + +// assert_eq!(updt_entity.ext_id, updt_value); + +// // We rollback the changes to the initial value to don't broke other tests +// // the next time that will run +// updt_candidate.ext_id = 100695891328981122_i64; +// updt_candidate +// .update_datasource(MYSQL_DS) +// .await +// .expect("Failed to restablish the initial value update operation"); +// } From c5932254af99601c8b718c80adc6e754e1af9bc3 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 21 Jan 2025 00:12:17 +0100 Subject: [PATCH 027/155] feat: introducing the IntoResults trait, being implemeted over Result providing a better fluent api over the returned results from the database(s) --- canyon_core/src/mapper.rs | 8 ++++++++ canyon_core/src/rows.rs | 11 ++++++++++- canyon_macros/src/lib.rs | 4 ++++ canyon_macros/src/query_operations/select.rs | 2 +- 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/canyon_core/src/mapper.rs b/canyon_core/src/mapper.rs index e7493934..bf0f47f9 100644 --- a/canyon_core/src/mapper.rs +++ b/canyon_core/src/mapper.rs @@ -9,3 +9,11 @@ pub trait RowMapper: Sized { #[cfg(feature = "mysql")] fn deserialize_mysql(row: &mysql_async::Row) -> T; } + +pub type CanyonError = Box<(dyn std::error::Error + Send + Sync)>; // TODO: convert this into a + // real error +pub trait IntoResults { + fn into_results(self) -> Result, CanyonError> + where + T: RowMapper; +} diff --git a/canyon_core/src/rows.rs b/canyon_core/src/rows.rs index dd176b23..6f8aa535 100644 --- a/canyon_core/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -5,7 +5,7 @@ use tiberius::{self}; #[cfg(feature = "postgres")] use tokio_postgres::{self}; -use crate::mapper::RowMapper; +use crate::mapper::{CanyonError, IntoResults, RowMapper}; /// Lightweight wrapper over the collection of results of the different crates /// supported by Canyon-SQL. @@ -22,6 +22,15 @@ pub enum CanyonRows { MySQL(Vec), } +impl IntoResults for Result { + fn into_results(self) -> Result, CanyonError> + where + T: RowMapper + { + self.map(move |rows| rows.into_results::()) + } +} + impl CanyonRows { #[cfg(feature = "postgres")] pub fn get_postgres_rows(&self) -> &Vec { diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index 80096bf9..ae1f2514 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -336,6 +336,8 @@ fn impl_crud_operations_trait_for_struct( let a: Vec = vec![]; let tokens = if a.is_empty() { quote! { + use canyon_sql::core::IntoResults; + #[canyon_sql::macros::async_trait] impl canyon_sql::crud::CrudOperations<#ty> for #ty { #crud_operations_tokens @@ -366,6 +368,8 @@ fn impl_crud_operations_trait_for_struct( } } else { quote! { + use canyon_sql::core::IntoResults; + #[canyon_sql::macros::async_trait] impl canyon_sql::crud::CrudOperations<#ty> for #ty { #crud_operations_tokens diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index a330ee90..2e1c6e98 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -26,8 +26,8 @@ pub fn generate_find_all_unchecked_tokens( &[], "" ).await - .unwrap() .into_results::<#ty>() + .unwrap() } /// Performs a `SELECT * FROM table_name`, where `table_name` it's From 7d4d61c218417356d1a929f02dbc3ecdf8316206 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 21 Jan 2025 13:03:09 +0100 Subject: [PATCH 028/155] feat: removing tons of lifetime constraints over wild captured lifetimes on trait objects --- canyon_core/src/connection/conn_errors.rs | 14 +-- .../src/connection/db_clients/mssql.rs | 4 +- .../src/connection/db_clients/mysql.rs | 4 +- .../src/connection/db_clients/postgresql.rs | 4 +- canyon_core/src/connection/db_connector.rs | 16 ++-- canyon_core/src/connection/lib.rs | 2 +- canyon_core/src/connection/mod.rs | 6 +- canyon_core/src/query.rs | 4 +- canyon_macros/src/query_operations/select.rs | 95 ++++++++++++------- 9 files changed, 89 insertions(+), 60 deletions(-) diff --git a/canyon_core/src/connection/conn_errors.rs b/canyon_core/src/connection/conn_errors.rs index 9a67cc64..b25c514d 100644 --- a/canyon_core/src/connection/conn_errors.rs +++ b/canyon_core/src/connection/conn_errors.rs @@ -2,17 +2,17 @@ /// Raised when a [`crate::datasources::DatasourceConfig`] isn't found given a user input #[derive(Debug, Clone)] -pub struct DatasourceNotFound<'a> { - pub datasource_name: &'a str, +pub struct DatasourceNotFound { + pub datasource_name: String, } -impl<'a> From> for DatasourceNotFound<'a> { - fn from(value: Option<&'a str>) -> Self { +impl From> for DatasourceNotFound { + fn from(value: Option<& str>) -> Self { DatasourceNotFound { - datasource_name: value.unwrap_or_default(), // TODO: not default + datasource_name: value.map(String::from).unwrap_or_default(), // TODO: not default } } } -impl<'a> std::fmt::Display for DatasourceNotFound<'a> { +impl std::fmt::Display for DatasourceNotFound { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, @@ -21,4 +21,4 @@ impl<'a> std::fmt::Display for DatasourceNotFound<'a> { ) } } -impl<'a> std::error::Error for DatasourceNotFound<'a> {} +impl std::error::Error for DatasourceNotFound {} diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs index 2f2ef8bf..7ff94c49 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -17,7 +17,7 @@ impl DbConnection for SqlServerConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { + ) -> Result> { sqlserver_query_launcher::launch(stmt, params, self).await } } @@ -32,7 +32,7 @@ pub(crate) mod sqlserver_query_launcher { stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &SqlServerConnection, - ) -> Result> { + ) -> Result> { // Re-generate de insert statement to adequate it to the SQL SERVER syntax to retrieve the PK value(s) after insert // TODO: redo this branch into the generated queries, before the MACROS // if stmt.contains("RETURNING") { diff --git a/canyon_core/src/connection/db_clients/mysql.rs b/canyon_core/src/connection/db_clients/mysql.rs index a33a91ea..f5b3a2bb 100644 --- a/canyon_core/src/connection/db_clients/mysql.rs +++ b/canyon_core/src/connection/db_clients/mysql.rs @@ -19,7 +19,7 @@ impl DbConnection for MysqlConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { + ) -> Result> { mysql_query_launcher::launch(stmt, params, self).await } } @@ -45,7 +45,7 @@ pub(crate) mod mysql_query_launcher { stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &MysqlConnection, - ) -> Result> { + ) -> Result> { let mysql_connection = conn.client.get_conn().await?; let stmt_with_escape_characters = regex::escape(stmt); diff --git a/canyon_core/src/connection/db_clients/postgresql.rs b/canyon_core/src/connection/db_clients/postgresql.rs index ef04746d..569013ac 100644 --- a/canyon_core/src/connection/db_clients/postgresql.rs +++ b/canyon_core/src/connection/db_clients/postgresql.rs @@ -17,7 +17,7 @@ impl DbConnection for PostgreSqlConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { + ) -> Result> { postgres_query_launcher::launch(stmt, params, self).await } } @@ -31,7 +31,7 @@ pub(crate) mod postgres_query_launcher { stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &PostgreSqlConnection, - ) -> Result> { + ) -> Result> { let mut m_params = Vec::new(); for param in params { m_params.push((*param).as_postgres_param()); diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index 09ad3208..ca26129b 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -30,7 +30,7 @@ impl DbConnection for DatabaseConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { + ) -> Result> { match self { #[cfg(feature = "postgres")] DatabaseConnection::Postgres(client) => client.launch(stmt, params).await, @@ -50,7 +50,7 @@ unsafe impl Sync for DatabaseConnection {} impl DatabaseConnection { pub async fn new( datasource: &DatasourceConfig, - ) -> Result> { + ) -> Result> { match datasource.get_db_type() { #[cfg(feature = "postgres")] DatabaseType::PostgreSql => { @@ -102,7 +102,7 @@ mod connection_helpers { #[cfg(feature = "postgres")] pub async fn create_postgres_connection( datasource: &DatasourceConfig, - ) -> Result> { + ) -> Result> { let (user, password) = auth::extract_postgres_auth(&datasource.auth)?; let url = connection_string(user, password, datasource); @@ -124,7 +124,7 @@ mod connection_helpers { #[cfg(feature = "mssql")] pub async fn create_sqlserver_connection( datasource: &DatasourceConfig, - ) -> Result> { + ) -> Result> { use async_std::net::TcpStream; let mut tiberius_config = tiberius::Config::new(); @@ -150,7 +150,7 @@ mod connection_helpers { #[cfg(feature = "mysql")] pub async fn create_mysql_connection( datasource: &DatasourceConfig, - ) -> Result> { + ) -> Result> { use mysql_async::Pool; let (user, password) = auth::extract_mysql_auth(&datasource.auth)?; @@ -194,7 +194,7 @@ mod auth { #[cfg(feature = "postgres")] pub fn extract_postgres_auth<'a>( auth: &'a Auth, - ) -> Result<(&'a str, &'a str), Box<(dyn std::error::Error + Send + Sync + 'static)>> { + ) -> Result<(&'a str, &'a str), Box<(dyn std::error::Error + Send + Sync)>> { match auth { Auth::Postgres(pg_auth) => match pg_auth { PostgresAuth::Basic { username, password } => Ok((username, password)), @@ -207,7 +207,7 @@ mod auth { #[cfg(feature = "mssql")] pub fn extract_mssql_auth<'a>( auth: &'a Auth, - ) -> Result> { + ) -> Result> { match auth { Auth::SqlServer(sql_server_auth) => match sql_server_auth { SqlServerAuth::Basic { username, password } => { @@ -223,7 +223,7 @@ mod auth { #[cfg(feature = "mysql")] pub fn extract_mysql_auth<'a>( auth: &'a Auth, - ) -> Result<(&'a str, &'a str), Box<(dyn std::error::Error + Send + Sync + 'static)>> { + ) -> Result<(&'a str, &'a str), Box<(dyn std::error::Error + Send + Sync)>> { match auth { Auth::MySQL(mysql_auth) => match mysql_auth { MySQLAuth::Basic { username, password } => Ok((username, password)), diff --git a/canyon_core/src/connection/lib.rs b/canyon_core/src/connection/lib.rs index 122558f3..b6298c8c 100644 --- a/canyon_core/src/connection/lib.rs +++ b/canyon_core/src/connection/lib.rs @@ -100,7 +100,7 @@ pub async fn init_connections_cache() { // TODO: doc (main way for the user to obtain a db connection given a datasource identifier) pub async fn get_database_connection_by_ds< 'a, - T: AsRef + Copy + Debug + Default + Send + Sync + 'static, + T: AsRef + Copy + Debug + Default + Send + Sync + 'a, >( datasource_name: Option, ) -> Result> { diff --git a/canyon_core/src/connection/mod.rs b/canyon_core/src/connection/mod.rs index bd958c02..194b40b1 100644 --- a/canyon_core/src/connection/mod.rs +++ b/canyon_core/src/connection/mod.rs @@ -100,14 +100,14 @@ pub async fn init_connections_cache() { // TODO: doc (main way for the user to obtain a db connection given a datasource identifier) pub async fn get_database_connection_by_ds<'a>( datasource_name: Option<&'a str>, -) -> Result> { +) -> Result> { let ds = find_datasource_by_name_or_try_default(datasource_name)?; DatabaseConnection::new(ds).await } fn find_datasource_by_name_or_try_default<'a>( - datasource_name: Option<&'a str>, -) -> Result<&'a DatasourceConfig, DatasourceNotFound<'a>> { + datasource_name: Option<&str>, +) -> Result<&DatasourceConfig, DatasourceNotFound> { datasource_name .map_or_else( || DATASOURCES.first(), diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index 0676cf85..04d450a1 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -13,7 +13,7 @@ pub trait DbConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result>; + ) -> Result>; } #[async_trait] @@ -26,7 +26,7 @@ pub trait Transaction { stmt: S, params: Z, input: I, - ) -> Result> + ) -> Result> where S: AsRef + Display + Sync + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index 2e1c6e98..786ba801 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -1,52 +1,81 @@ use canyon_entities::field_annotation::EntityFieldAnnotation; -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use quote::quote; use crate::utils::helpers::*; use crate::utils::macro_tokens::MacroTokens; - -/// Generates the TokenStream for build the __find_all() CRUD -/// associated function -pub fn generate_find_all_unchecked_tokens( - macro_data: &MacroTokens<'_>, - table_schema_data: &String, +const SELECT_ALL_BASE_DOC_COMMENT: &str = "/// Performs a `SELECT * FROM table_name`, where `table_name` it's \ + /// the name of your entity but converted to the corresponding \ + /// database convention. P.ej. PostgreSQL prefers table names declared \ + /// with snake_case identifiers."; +fn generate_function( + name: &str, + has_datasource: bool, + ty: &syn::Ident, + stmt: &str, + with_lifetime: bool, + with_unwrap: bool, + base_doc_comment: &str ) -> TokenStream { - let ty = macro_data.ty; - let stmt = format!("SELECT * FROM {table_schema_data}"); + let fn_name = { + let fn_name_ident = syn::Ident::new(name, Span::call_site()); + quote! { #fn_name_ident } + }; + + let doc_comment: &str; + let mut datasource_param = quote! {}; + let mut datasource_arg = quote! { "" }; + + if has_datasource { + doc_comment = "/// The query is made against the database with the configured datasource \ + /// described in the configuration file, and selected with the [`&str`] passed as parameter."; + datasource_param = quote! { datasource_name: &'a str }; + datasource_arg = quote! { datasource_name }; + } else { + doc_comment = "/// The query is made against the default datasource configured in the configuration file."; + } + + let lt = if with_lifetime { quote!{ <'a> } } else { quote!{} }; + let with_unwrap = if with_unwrap { quote!{ .unwrap() } } else { quote!{} }; quote! { - /// Performs a `SELECT * FROM table_name`, where `table_name` it's - /// the name of your entity but converted to the corresponding - /// database convention. P.ej. PostgreSQL prefers table names declared - /// with snake_case identifiers. - async fn find_all_unchecked() -> Vec<#ty> { + #[doc = #base_doc_comment] + #[doc = #doc_comment] + async fn #fn_name #lt(#datasource_param) -> Vec<#ty> { <#ty as canyon_sql::core::Transaction<#ty>>::query( #stmt, &[], - "" + #datasource_arg ).await .into_results::<#ty>() - .unwrap() + #with_unwrap } + } +} - /// Performs a `SELECT * FROM table_name`, where `table_name` it's - /// the name of your entity but converted to the corresponding - /// database convention. P.ej. PostgreSQL prefers table names declared - /// with snake_case identifiers. - /// - /// The query it's made against the database with the configured datasource - /// described in the configuration file, and selected with the [`&str`] - /// passed as parameter. - async fn find_all_unchecked_datasource<'a>(datasource_name: &'a str) -> Vec<#ty> { - <#ty as canyon_sql::core::Transaction<#ty>>::query( - #stmt, - &[], - datasource_name - ).await - .unwrap() - .into_results::<#ty>() - } +/// Generates the TokenStream for build the __find_all() CRUD +/// associated function +pub fn generate_find_all_unchecked_tokens( + macro_data: &MacroTokens<'_>, + table_schema_data: &String, +) -> TokenStream { + let ty = macro_data.ty; + let stmt = format!("SELECT * FROM {table_schema_data}"); + // TODO: bring the helper and convert the SELECT * into the + // SELECT col_name, col_name2...? + // TODO: remember that this queries statements must be autogenerated by some automatic procedure + // and also, we could use the const_format crate + + let find_all_unchecked = generate_function( + "find_all_unchecked", false, ty, &stmt, false, true, SELECT_ALL_BASE_DOC_COMMENT + ); + let find_all_unchecked_ds = generate_function( + "find_all_unchecked_datasource", true, ty, &stmt, true, true, SELECT_ALL_BASE_DOC_COMMENT + ); + quote! { + #find_all_unchecked + #find_all_unchecked_ds } } From 0fd7ab630b7790746ebe923a6faf1b22e879f556 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 21 Jan 2025 15:18:43 +0100 Subject: [PATCH 029/155] feat: reworking the find_all macro(s) generation --- canyon_macros/src/lib.rs | 10 +-- canyon_macros/src/query_operations/select.rs | 78 +++++--------------- 2 files changed, 22 insertions(+), 66 deletions(-) diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index ae1f2514..cb6427e7 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -22,7 +22,6 @@ use query_operations::{ select::{ // generate_count_tokens, generate_find_all_query_tokens, generate_find_all_tokens, - generate_find_all_unchecked_tokens, // generate_find_by_foreign_key_tokens, // generate_find_by_pk_tokens, generate_find_by_reverse_foreign_key_tokens, }, @@ -248,10 +247,8 @@ fn impl_crud_operations_trait_for_struct( let ty = macro_data.ty; // Builds the find_all() query - let _find_all_unchecked_tokens = - generate_find_all_unchecked_tokens(macro_data, &table_schema_data); - // Builds the find_all_result() query - let _find_all_tokens = generate_find_all_tokens(macro_data, &table_schema_data); + let _find_all_tokens = + generate_find_all_tokens(macro_data, &table_schema_data); // Builds the find_all_query() query as a QueryBuilder // let _find_all_query_tokens = generate_find_all_query_tokens(macro_data, &table_schema_data); @@ -301,9 +298,6 @@ fn impl_crud_operations_trait_for_struct( // The find_all_result impl #_find_all_tokens - // The find_all impl - #_find_all_unchecked_tokens - // // The find_all_query impl // #_find_all_query_tokens diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index 786ba801..280a19b5 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -36,13 +36,20 @@ fn generate_function( doc_comment = "/// The query is made against the default datasource configured in the configuration file."; } - let lt = if with_lifetime { quote!{ <'a> } } else { quote!{} }; - let with_unwrap = if with_unwrap { quote!{ .unwrap() } } else { quote!{} }; + let (err_type, lt) = if with_lifetime { + (quote!{ Box<(dyn std::error::Error + Send + Sync + 'a)> }, quote!{ <'a> }) + } else { (quote!{ Box<(dyn std::error::Error + Send + Sync)> }, quote!{} )}; + + let (return_type, with_unwrap) = if with_unwrap { + (quote!{ Vec<#ty> }, quote!{ .unwrap() }) + } else { + (quote!{ Result, #err_type> }, quote!{}) + }; quote! { #[doc = #base_doc_comment] #[doc = #doc_comment] - async fn #fn_name #lt(#datasource_param) -> Vec<#ty> { + async fn #fn_name #lt(#datasource_param) -> #return_type { <#ty as canyon_sql::core::Transaction<#ty>>::query( #stmt, &[], @@ -56,7 +63,7 @@ fn generate_function( /// Generates the TokenStream for build the __find_all() CRUD /// associated function -pub fn generate_find_all_unchecked_tokens( +pub fn generate_find_all_tokens( macro_data: &MacroTokens<'_>, table_schema_data: &String, ) -> TokenStream { @@ -67,72 +74,27 @@ pub fn generate_find_all_unchecked_tokens( // TODO: remember that this queries statements must be autogenerated by some automatic procedure // and also, we could use the const_format crate + let find_all = generate_function( + "find_all", false, ty, &stmt, false, false, SELECT_ALL_BASE_DOC_COMMENT + ); + let find_all_datasource = generate_function( + "find_all_datasource", true, ty, &stmt, true, false, SELECT_ALL_BASE_DOC_COMMENT + ); let find_all_unchecked = generate_function( "find_all_unchecked", false, ty, &stmt, false, true, SELECT_ALL_BASE_DOC_COMMENT ); let find_all_unchecked_ds = generate_function( "find_all_unchecked_datasource", true, ty, &stmt, true, true, SELECT_ALL_BASE_DOC_COMMENT ); + quote! { + #find_all + #find_all_datasource #find_all_unchecked #find_all_unchecked_ds } } -/// Generates the TokenStream for build the __find_all_result() CRUD -/// associated function -pub fn generate_find_all_tokens( - macro_data: &MacroTokens<'_>, - table_schema_data: &String, -) -> TokenStream { - let ty = macro_data.ty; - let stmt = format!("SELECT * FROM {table_schema_data}"); - - quote! { - /// Performs a `SELECT * FROM table_name`, where `table_name` it's - /// the name of your entity but converted to the corresponding - /// database convention. P.ej. PostgreSQL prefers table names declared - /// with snake_case identifiers. - async fn find_all() -> - Result, Box<(dyn std::error::Error + Send + Sync)>> - { - Ok( - <#ty as canyon_sql::core::Transaction<#ty>>::query( - #stmt, - &[], - "" - ).await? - .into_results::<#ty>() - ) - } - - /// Performs a `SELECT * FROM table_name`, where `table_name` it's - /// the name of your entity but converted to the corresponding - /// database convention. P.ej. PostgreSQL prefers table names declared - /// with snake_case identifiers. - /// - /// The query it's made against the database with the configured datasource - /// described in the configuration file, and selected with the [`&str`] - /// passed as parameter. - /// - /// Also, returns a [`Vec, Error>`], wrapping a possible failure - /// querying the database, or, if no errors happens, a Vec containing - /// the data found. - async fn find_all_datasource<'a>(datasource_name: &'a str) -> - Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> - { - Ok( - <#ty as canyon_sql::core::Transaction<#ty>>::query( - #stmt, - &[], - datasource_name - ).await? - .into_results::<#ty>() - ) - } - } -} - // /// Same as above, but with a [`canyon_sql::query::QueryBuilder`] // pub fn generate_find_all_query_tokens( // macro_data: &MacroTokens<'_>, From 4e8f51c40e3452225bcbd36035cb9c7d80fbaf66 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 21 Jan 2025 17:05:52 +0100 Subject: [PATCH 030/155] feat: desugaring the async in trait on Transaction::query(...) to impl Future + Send --- canyon_core/src/query.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index 04d450a1..3f31468d 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -1,4 +1,4 @@ -use std::fmt::Display; +use std::{fmt::Display, future::Future}; use async_trait::async_trait; @@ -16,23 +16,22 @@ pub trait DbConnection { ) -> Result>; } -#[async_trait] pub trait Transaction { // provisional name /// Performs a query against the targeted database by the selected or /// the defaulted datasource, wrapping the resultant collection of entities /// in [`super::rows::CanyonRows`] - async fn query<'a, S, Z, I>( + fn query<'a, S, Z, I>( stmt: S, params: Z, input: I, - ) -> Result> + ) -> impl Future>> + Send where S: AsRef + Display + Sync + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, I: Into> + Sync + Send + 'a { - let transaction_input= input.into(); + async move {let transaction_input= input.into(); let statement = stmt.as_ref(); let query_parameters = params.as_ref(); @@ -55,7 +54,7 @@ pub trait Transaction { let conn = get_database_connection_by_ds(sane_ds_name).await?; conn.launch(statement, query_parameters).await } - } + }} } } From e970f060398a4c16dce600682223269366ebf8b7 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 21 Jan 2025 17:43:16 +0100 Subject: [PATCH 031/155] feat: removed the #[async_trait] from Transaction and DbConnection succesfully --- canyon_core/src/connection/conn_errors.rs | 4 +- canyon_core/src/connection/database_type.rs | 1 - canyon_core/src/connection/datasources.rs | 1 - .../src/connection/db_clients/mssql.rs | 10 +-- .../src/connection/db_clients/mysql.rs | 10 +-- .../src/connection/db_clients/postgresql.rs | 10 +-- canyon_core/src/connection/db_connector.rs | 29 ++++---- canyon_core/src/lib.rs | 3 +- canyon_core/src/query.rs | 70 +++++++++++-------- canyon_core/src/rows.rs | 2 +- canyon_macros/src/canyon_macro.rs | 3 +- canyon_macros/src/lib.rs | 6 +- canyon_macros/src/query_operations/select.rs | 59 ++++++++++++---- canyon_migrations/src/migrations/handler.rs | 6 +- canyon_migrations/src/migrations/memory.rs | 3 +- canyon_migrations/src/migrations/processor.rs | 6 +- tests/crud/querybuilder_operations.rs | 1 - 17 files changed, 133 insertions(+), 91 deletions(-) diff --git a/canyon_core/src/connection/conn_errors.rs b/canyon_core/src/connection/conn_errors.rs index b25c514d..18da4c80 100644 --- a/canyon_core/src/connection/conn_errors.rs +++ b/canyon_core/src/connection/conn_errors.rs @@ -5,8 +5,8 @@ pub struct DatasourceNotFound { pub datasource_name: String, } -impl From> for DatasourceNotFound { - fn from(value: Option<& str>) -> Self { +impl From> for DatasourceNotFound { + fn from(value: Option<&str>) -> Self { DatasourceNotFound { datasource_name: value.map(String::from).unwrap_or_default(), // TODO: not default } diff --git a/canyon_core/src/connection/database_type.rs b/canyon_core/src/connection/database_type.rs index 8c71d2c4..99478664 100644 --- a/canyon_core/src/connection/database_type.rs +++ b/canyon_core/src/connection/database_type.rs @@ -2,7 +2,6 @@ use serde::Deserialize; use super::datasources::Auth; - /// Holds the current supported databases by Canyon-SQL #[derive(Deserialize, Debug, Eq, PartialEq, Clone, Copy)] pub enum DatabaseType { diff --git a/canyon_core/src/connection/datasources.rs b/canyon_core/src/connection/datasources.rs index 2a988d7b..63366636 100644 --- a/canyon_core/src/connection/datasources.rs +++ b/canyon_core/src/connection/datasources.rs @@ -2,7 +2,6 @@ use serde::Deserialize; use super::database_type::DatabaseType; - /// ``` #[test] fn load_ds_config_from_array() { diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs index 7ff94c49..30012fa3 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -1,7 +1,6 @@ #[cfg(feature = "mssql")] use async_std::net::TcpStream; -use async_trait::async_trait; use crate::{query::DbConnection, query_parameters::QueryParameter, rows::CanyonRows}; use tiberius::Query; @@ -11,14 +10,15 @@ pub struct SqlServerConnection { pub client: &'static mut tiberius::Client, } -#[async_trait] impl DbConnection for SqlServerConnection { - async fn launch<'a>( + fn launch<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - sqlserver_query_launcher::launch(stmt, params, self).await + ) -> impl std::future::Future< + Output = Result>, + > + Send { + sqlserver_query_launcher::launch(stmt, params, self) } } diff --git a/canyon_core/src/connection/db_clients/mysql.rs b/canyon_core/src/connection/db_clients/mysql.rs index f5b3a2bb..eb0d5fbb 100644 --- a/canyon_core/src/connection/db_clients/mysql.rs +++ b/canyon_core/src/connection/db_clients/mysql.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; #[cfg(feature = "mysql")] use mysql_async::Pool; @@ -13,14 +12,15 @@ pub struct MysqlConnection { pub client: Pool, } -#[async_trait] impl DbConnection for MysqlConnection { - async fn launch<'a>( + fn launch<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - mysql_query_launcher::launch(stmt, params, self).await + ) -> impl std::future::Future< + Output = Result>, + > + Send { + mysql_query_launcher::launch(stmt, params, self) } } diff --git a/canyon_core/src/connection/db_clients/postgresql.rs b/canyon_core/src/connection/db_clients/postgresql.rs index 569013ac..7522afbe 100644 --- a/canyon_core/src/connection/db_clients/postgresql.rs +++ b/canyon_core/src/connection/db_clients/postgresql.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use crate::{query::DbConnection, query_parameters::QueryParameter, rows::CanyonRows}; #[cfg(feature = "postgres")] @@ -11,14 +10,15 @@ pub struct PostgreSqlConnection { // pub connection: Connection, // TODO Hold it, or not to hold it... that's the question! } -#[async_trait] impl DbConnection for PostgreSqlConnection { - async fn launch<'a>( + fn launch<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - postgres_query_launcher::launch(stmt, params, self).await + ) -> impl std::future::Future< + Output = Result>, + > + Send { + postgres_query_launcher::launch(stmt, params, self) } } diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index ca26129b..32c22555 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -8,8 +8,6 @@ use crate::query::DbConnection; use crate::query_parameters::QueryParameter; use crate::rows::CanyonRows; -use async_trait::async_trait; - /// The Canyon database connection handler. When the client's program /// starts, Canyon gets the information about the desired datasources, /// process them and generates a pool of 1 to 1 database connection for @@ -24,22 +22,25 @@ pub enum DatabaseConnection { MySQL(MysqlConnection), } -#[async_trait] impl DbConnection for DatabaseConnection { - async fn launch<'a>( + fn launch<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - match self { - #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(client) => client.launch(stmt, params).await, - - #[cfg(feature = "mssql")] - DatabaseConnection::SqlServer(client) => client.launch(stmt, params).await, - - #[cfg(feature = "mysql")] - DatabaseConnection::MySQL(client) => client.launch(stmt, params).await, + ) -> impl std::future::Future< + Output = Result>, + > + Send { + async move { + match self { + #[cfg(feature = "postgres")] + DatabaseConnection::Postgres(client) => client.launch(stmt, params).await, + + #[cfg(feature = "mssql")] + DatabaseConnection::SqlServer(client) => client.launch(stmt, params).await, + + #[cfg(feature = "mysql")] + DatabaseConnection::MySQL(client) => client.launch(stmt, params).await, + } } } } diff --git a/canyon_core/src/lib.rs b/canyon_core/src/lib.rs index aa7f0fa3..be2260c1 100644 --- a/canyon_core/src/lib.rs +++ b/canyon_core/src/lib.rs @@ -11,9 +11,8 @@ pub extern crate mysql_async; pub extern crate lazy_static; - -pub mod connection; pub mod column; +pub mod connection; pub mod mapper; pub mod query; pub mod query_parameters; diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index 3f31468d..17603055 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -1,19 +1,22 @@ +use crate::{ + connection::{ + datasources::DatasourceConfig, db_connector::DatabaseConnection, + get_database_connection_by_ds, + }, + query_parameters::{self, QueryParameter}, + rows::CanyonRows, +}; use std::{fmt::Display, future::Future}; -use async_trait::async_trait; - -use crate::{connection::{datasources::DatasourceConfig, db_connector::DatabaseConnection, get_database_connection_by_ds}, query_parameters::{self, QueryParameter}, rows::CanyonRows}; - // TODO: in order to avoid the tiberius transmute, we should define other method that takes the db_conn as a mut ref -#[async_trait] pub trait DbConnection { // TODO: guess that this is the trait that must remain sealed - async fn launch<'a>( + fn launch<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result>; + ) -> impl Future>> + Send; } pub trait Transaction { @@ -29,32 +32,39 @@ pub trait Transaction { where S: AsRef + Display + Sync + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, - I: Into> + Sync + Send + 'a + I: Into> + Sync + Send + 'a, { - async move {let transaction_input= input.into(); - let statement = stmt.as_ref(); - let query_parameters = params.as_ref(); + async move { + let transaction_input = input.into(); + let statement = stmt.as_ref(); + let query_parameters = params.as_ref(); - match transaction_input { - TransactionInput::DbConnection(conn) => { - conn.launch(statement, query_parameters).await - } - TransactionInput::DbConnectionRef(conn) => { - conn.launch(statement, query_parameters).await - } - TransactionInput::DbConnectionRefMut(/* TODO: mut*/ conn) => { - conn.launch(statement, query_parameters).await - } - TransactionInput::DatasourceConfig(ds) => { // TODO: add a new from_ds_config_mut for mssql - let conn = DatabaseConnection::new(&ds).await?; - conn.launch(statement, query_parameters).await - } - TransactionInput::DatasourceName(ds_name) => { - let sane_ds_name = if !ds_name.is_empty() { Some(ds_name) } else { None }; - let conn = get_database_connection_by_ds(sane_ds_name).await?; - conn.launch(statement, query_parameters).await + match transaction_input { + TransactionInput::DbConnection(conn) => { + conn.launch(statement, query_parameters).await + } + TransactionInput::DbConnectionRef(conn) => { + conn.launch(statement, query_parameters).await + } + TransactionInput::DbConnectionRefMut(/* TODO: mut*/ conn) => { + conn.launch(statement, query_parameters).await + } + TransactionInput::DatasourceConfig(ds) => { + // TODO: add a new from_ds_config_mut for mssql + let conn = DatabaseConnection::new(&ds).await?; + conn.launch(statement, query_parameters).await + } + TransactionInput::DatasourceName(ds_name) => { + let sane_ds_name = if !ds_name.is_empty() { + Some(ds_name) + } else { + None + }; + let conn = get_database_connection_by_ds(sane_ds_name).await?; + conn.launch(statement, query_parameters).await + } } - }} + } } } diff --git a/canyon_core/src/rows.rs b/canyon_core/src/rows.rs index 6f8aa535..0180b8bb 100644 --- a/canyon_core/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -25,7 +25,7 @@ pub enum CanyonRows { impl IntoResults for Result { fn into_results(self) -> Result, CanyonError> where - T: RowMapper + T: RowMapper, { self.map(move |rows| rows.into_results::()) } diff --git a/canyon_macros/src/canyon_macro.rs b/canyon_macros/src/canyon_macro.rs index 67cc89c6..17ace82f 100644 --- a/canyon_macros/src/canyon_macro.rs +++ b/canyon_macros/src/canyon_macro.rs @@ -7,7 +7,8 @@ use canyon_migrations::{CM_QUERIES_TO_EXECUTE, QUERIES_TO_EXECUTE}; use proc_macro2::TokenStream; use quote::quote; -pub fn main_with_queries() -> TokenStream { // TODO: migrations on main instead of main_with_queries +pub fn main_with_queries() -> TokenStream { + // TODO: migrations on main instead of main_with_queries CANYON_TOKIO_RUNTIME.block_on(async { canyon_core::connection::init_connections_cache().await; // TODO: isn't this cache always initialized anyway? try to remove it Migrations::migrate().await; diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index cb6427e7..3e688c87 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -1,7 +1,6 @@ #![allow(dead_code)] extern crate proc_macro; - mod canyon_entity_macro; #[cfg(feature = "migrations")] use canyon_macro::main_with_queries; @@ -20,7 +19,7 @@ use query_operations::{ delete::{generate_delete_query_tokens, generate_delete_tokens}, insert::{generate_insert_tokens, generate_multiple_insert_tokens}, select::{ - // generate_count_tokens, generate_find_all_query_tokens, + // generate_count_tokens, generate_find_all_query_tokens, generate_find_all_tokens, // generate_find_by_foreign_key_tokens, // generate_find_by_pk_tokens, generate_find_by_reverse_foreign_key_tokens, @@ -247,8 +246,7 @@ fn impl_crud_operations_trait_for_struct( let ty = macro_data.ty; // Builds the find_all() query - let _find_all_tokens = - generate_find_all_tokens(macro_data, &table_schema_data); + let _find_all_tokens = generate_find_all_tokens(macro_data, &table_schema_data); // Builds the find_all_query() query as a QueryBuilder // let _find_all_query_tokens = generate_find_all_query_tokens(macro_data, &table_schema_data); diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index 280a19b5..c478e5fd 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -5,7 +5,8 @@ use quote::quote; use crate::utils::helpers::*; use crate::utils::macro_tokens::MacroTokens; -const SELECT_ALL_BASE_DOC_COMMENT: &str = "/// Performs a `SELECT * FROM table_name`, where `table_name` it's \ +const SELECT_ALL_BASE_DOC_COMMENT: &str = + "/// Performs a `SELECT * FROM table_name`, where `table_name` it's \ /// the name of your entity but converted to the corresponding \ /// database convention. P.ej. PostgreSQL prefers table names declared \ /// with snake_case identifiers."; @@ -16,15 +17,15 @@ fn generate_function( stmt: &str, with_lifetime: bool, with_unwrap: bool, - base_doc_comment: &str + base_doc_comment: &str, ) -> TokenStream { let fn_name = { - let fn_name_ident = syn::Ident::new(name, Span::call_site()); + let fn_name_ident = syn::Ident::new(name, Span::call_site()); quote! { #fn_name_ident } }; let doc_comment: &str; - let mut datasource_param = quote! {}; + let mut datasource_param = quote! {}; let mut datasource_arg = quote! { "" }; if has_datasource { @@ -36,14 +37,22 @@ fn generate_function( doc_comment = "/// The query is made against the default datasource configured in the configuration file."; } - let (err_type, lt) = if with_lifetime { - (quote!{ Box<(dyn std::error::Error + Send + Sync + 'a)> }, quote!{ <'a> }) - } else { (quote!{ Box<(dyn std::error::Error + Send + Sync)> }, quote!{} )}; + let (err_type, lt) = if with_lifetime { + ( + quote! { Box<(dyn std::error::Error + Send + Sync + 'a)> }, + quote! { <'a> }, + ) + } else { + ( + quote! { Box<(dyn std::error::Error + Send + Sync)> }, + quote! {}, + ) + }; let (return_type, with_unwrap) = if with_unwrap { - (quote!{ Vec<#ty> }, quote!{ .unwrap() }) + (quote! { Vec<#ty> }, quote! { .unwrap() }) } else { - (quote!{ Result, #err_type> }, quote!{}) + (quote! { Result, #err_type> }, quote! {}) }; quote! { @@ -75,16 +84,40 @@ pub fn generate_find_all_tokens( // and also, we could use the const_format crate let find_all = generate_function( - "find_all", false, ty, &stmt, false, false, SELECT_ALL_BASE_DOC_COMMENT + "find_all", + false, + ty, + &stmt, + false, + false, + SELECT_ALL_BASE_DOC_COMMENT, ); let find_all_datasource = generate_function( - "find_all_datasource", true, ty, &stmt, true, false, SELECT_ALL_BASE_DOC_COMMENT + "find_all_datasource", + true, + ty, + &stmt, + true, + false, + SELECT_ALL_BASE_DOC_COMMENT, ); let find_all_unchecked = generate_function( - "find_all_unchecked", false, ty, &stmt, false, true, SELECT_ALL_BASE_DOC_COMMENT + "find_all_unchecked", + false, + ty, + &stmt, + false, + true, + SELECT_ALL_BASE_DOC_COMMENT, ); let find_all_unchecked_ds = generate_function( - "find_all_unchecked_datasource", true, ty, &stmt, true, true, SELECT_ALL_BASE_DOC_COMMENT + "find_all_unchecked_datasource", + true, + ty, + &stmt, + true, + true, + SELECT_ALL_BASE_DOC_COMMENT, ); quote! { diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index 266a1251..44ceee04 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -1,11 +1,11 @@ use canyon_core::{ column::Column, + connection::{ + datasources::Migrations as MigrationsStatus, db_connector::DatabaseConnection, DATASOURCES, + }, query::Transaction, row::{Row, RowOperations}, rows::CanyonRows, - connection::{ - datasources::Migrations as MigrationsStatus, db_connector::DatabaseConnection, DATASOURCES, - } }; use canyon_entities::CANYON_REGISTER_ENTITIES; use partialdebug::placeholder::PartialDebug; diff --git a/canyon_migrations/src/migrations/memory.rs b/canyon_migrations/src/migrations/memory.rs index 4c2ea3a2..351d6aa9 100644 --- a/canyon_migrations/src/migrations/memory.rs +++ b/canyon_migrations/src/migrations/memory.rs @@ -67,7 +67,8 @@ impl CanyonMemory { // TODO: can't we get the target DS while in the migrations at call site and avoid to // duplicate calls to the pool? let mut conn_cache = canyon_core::connection::CACHED_DATABASE_CONN.lock().await; - let db_conn = canyon_core::connection::get_database_connection(&datasource.name, &mut conn_cache); + let db_conn = + canyon_core::connection::get_database_connection(&datasource.name, &mut conn_cache); // Creates the memory table if not exists Self::create_memory(&datasource.name, db_conn, &datasource.get_db_type()).await; diff --git a/canyon_migrations/src/migrations/processor.rs b/canyon_migrations/src/migrations/processor.rs index 25802eab..ff0e9bc0 100644 --- a/canyon_migrations/src/migrations/processor.rs +++ b/canyon_migrations/src/migrations/processor.rs @@ -579,8 +579,10 @@ impl MigrationsProcessor { let datasource_name = datasource.0; let mut conn_cache = canyon_core::connection::CACHED_DATABASE_CONN.lock().await; - let db_conn = - canyon_core::connection::get_database_connection(datasource_name, &mut conn_cache); + let db_conn = canyon_core::connection::get_database_connection( + datasource_name, + &mut conn_cache, + ); let res = Self::query(query_to_execute, [], db_conn).await; diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index a4dcc262..7640aac6 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -240,7 +240,6 @@ // let qpr = q.clone(); // println!("PSQL: {:?}", qpr.read_sql()); // */ - // // We can now back to the original an throw the query // q.query() // .await From 135477430fc7cd08e2b9d1d2f03a774f9c8fb909 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 23 Jan 2025 12:29:57 +0100 Subject: [PATCH 032/155] feat(wip): creating a macro builder that allows to generically create the macro impl user code for the crud operations --- canyon_core/src/query.rs | 2 +- canyon_macros/src/lib.rs | 3 + .../src/query_operations/macro_template.rs | 377 ++++++++++++++++++ canyon_macros/src/query_operations/mod.rs | 2 + 4 files changed, 383 insertions(+), 1 deletion(-) create mode 100644 canyon_macros/src/query_operations/macro_template.rs diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index 17603055..7001d045 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -3,7 +3,7 @@ use crate::{ datasources::DatasourceConfig, db_connector::DatabaseConnection, get_database_connection_by_ds, }, - query_parameters::{self, QueryParameter}, + query_parameters::QueryParameter, rows::CanyonRows, }; use std::{fmt::Display, future::Future}; diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index 3e688c87..56b23f81 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -1,4 +1,7 @@ #![allow(dead_code)] +#![allow(unused_variables)] +#![allow(unused_imports)] + extern crate proc_macro; mod canyon_entity_macro; diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs new file mode 100644 index 00000000..b7e7e62d --- /dev/null +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -0,0 +1,377 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{quote, ToTokens}; +use syn::{Type, parse_quote}; + +pub struct MacroOperationBuilder { + fn_name: Option, + lifetime: bool, // bool true always will generate <'a> + datasource_param: Option, + datasource_arg: TokenStream, + return_type: Option, + base_doc_comment: Option, + doc_comment: Option, + body_tokens: Option, + query_string: Option, + input_parameters: Option, + forwarded_parameters: Option, + single_result: bool, + with_unwrap: bool, +} + +impl MacroOperationBuilder { + pub fn new() -> Self { + Self { + fn_name: None, + lifetime: false, + datasource_param: None, + datasource_arg: quote! { "" }, + return_type: None, + base_doc_comment: None, + doc_comment: None, + body_tokens: None, + query_string: None, + input_parameters: None, + forwarded_parameters: None, + single_result: false, + with_unwrap: false, + } + } + + fn get_fn_name(&self) -> TokenStream { + if let Some(fn_name) = &self.fn_name { + quote!{ #fn_name } + } else { + panic!("No function name provided") + } + } + + pub fn fn_name(mut self, name: &str) -> Self { + self.fn_name = Some(Ident::new(name, Span::call_site())); + self + } + + fn get_lifetime(&self) -> TokenStream { + if self.lifetime { quote!{ <'a> } } else { quote!{} } + } + + fn get_datasource_param(&self) -> TokenStream { + let ds_param = &self.datasource_param; + quote! { #ds_param } + } + + fn get_datasource_arg(&self) -> &TokenStream { + &self.datasource_arg + } + + pub fn with_datasource_param(mut self) -> Self { + self.datasource_param = Some(quote! { datasource_name: &'a str }); + self.datasource_arg = quote! { datasource_name }; + self.lifetime = true; + self + } + + fn get_return_type(&self) -> TokenStream { + let organic_ret_type = &self.return_type; + let container_ret_type = if self.single_result { + quote! { Option } + } else { quote! { Vec } }; + + match &self.with_unwrap { // TODO: distinguish collection from 1 results + true => quote! { #container_ret_type<#organic_ret_type> }, + false => { + let err_variant = if self.lifetime { + quote! { Box<(dyn std::error::Error + Send + Sync + 'a)> } + } else { + quote! { Box<(dyn std::error::Error + Send + Sync)>} + }; + + quote! { Result<#container_ret_type<#organic_ret_type>, #err_variant> } + } + } + } + + pub fn return_type(mut self, return_type: &Ident) -> Self { + self.return_type = Some(return_type.clone()); + self + } + + pub fn single_result(mut self) -> Self { + self.single_result = true; + self + } + + pub fn base_doc_comment(mut self, comment: &str) -> Self { + self.base_doc_comment = Some(comment.to_string()); + self + } + + pub fn doc_comment(mut self, comment: &str) -> Self { + self.doc_comment = Some(comment.to_string()); + self + } + + pub fn query_string(mut self, query: &str) -> Self { + self.query_string = Some(query.to_string()); + self + } + + pub fn input_parameters(mut self, params: TokenStream) -> Self { + self.input_parameters = Some(params); + self + } + + pub fn get_fn_parameters(&self) -> TokenStream { + let func_parameters = &self.input_parameters; + quote! { #func_parameters } + } + + pub fn forwarded_parameters(mut self, params: TokenStream) -> Self { + self.forwarded_parameters = Some(params); + self + } + + pub fn get_forwarded_parameters(&self) -> TokenStream { + let forwarded_parameters = &self.forwarded_parameters; + + if let Some(fwd_params) = &self.forwarded_parameters { + quote! { #forwarded_parameters } + } else { quote!{ &[] } } + } + + pub fn with_unwrap(mut self, value: bool) -> Self { + self.with_unwrap = value; + self + } + + + /// Generates the final `quote!` tokens for this operation + pub fn generate_tokens(&self) -> proc_macro2::TokenStream { + let base_doc_comment = &self.base_doc_comment; + let doc_comment = &self.doc_comment; + + let fn_name = self.get_fn_name(); + let lifetime = self.get_lifetime(); + + let datasource_param = self.get_datasource_param(); + let datasource_name = self.get_datasource_arg(); + let fn_parameters = self.get_fn_parameters(); + + let query_string = &self.query_string; + let forwarded_parameters = self.get_forwarded_parameters(); + let return_type = self.get_return_type(); + + let unwrap_tokens = if self.with_unwrap { + quote! { .unwrap() } + } else { + quote! {} + }; + + let body_tokens = quote!{ + >::query( + #query_string, + #forwarded_parameters, + #datasource_name + ).await + .into_results::() + }; + + let separate_params = if self.input_parameters.is_some() && self.datasource_param.is_some() { + quote! {, } + } else { quote! {} }; + + quote! { + #[doc = #base_doc_comment] + #[doc = #doc_comment] + async fn #fn_name #lifetime(#fn_parameters #separate_params #datasource_param) -> #return_type { + #body_tokens + #unwrap_tokens + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use quote::quote; + use syn::parse_quote; + + #[test] + fn test_find_operation_tokens() { + let ret_type = Ident::new("User", Span::call_site()); + + let find_operation = MacroOperationBuilder::new() + .fn_name("find_user_by_id") + .with_datasource_param() + .return_type(&ret_type) + .base_doc_comment("Finds a user by their ID.") + .doc_comment("This operation retrieves a single user record based on the provided ID.") + .query_string("SELECT * FROM users WHERE id = ?") + .input_parameters(quote! { id: &dyn QueryParameters<'_> }) + .forwarded_parameters(quote!{ &[id] }) + .single_result() + .with_unwrap(false); + + let generated_tokens = find_operation.generate_tokens(); + let expected_tokens = quote! { + #[doc = "Finds a user by their ID."] + #[doc = "This operation retrieves a single user record based on the provided ID."] + async fn find_user_by_id<'a>(id: &dyn QueryParameters<'_>, datasource_name: &'a str) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)> > { + >::query( + "SELECT * FROM users WHERE id = ?", + &[id], + datasource_name + ).await + .into_results::() + } + }; + + assert_eq!( + generated_tokens.to_string(), + expected_tokens.to_string() + ); + } + + #[test] + fn test_find_all_operation_tokens() { + let ret_type = Ident::new("User", Span::call_site()); + + let find_operation = MacroOperationBuilder::new() + .fn_name("find_all") + .return_type(&ret_type) + .base_doc_comment("Executes a 'SELECT * FROM '") + .doc_comment("This operation retrieves all the users records stored with the default datasource") + .query_string("SELECT * FROM users") + .with_unwrap(false); + + let generated_tokens = find_operation.generate_tokens(); + let expected_tokens = quote! { + #[doc = "Executes a 'SELECT * FROM '"] + #[doc = "This operation retrieves all the users records stored with the default datasource"] + async fn find_all() -> Result, Box<(dyn std::error::Error + Send + Sync)> > { + >::query( + "SELECT * FROM users", + &[], + "" + ).await + .into_results::() + } + }; + + assert_eq!( + generated_tokens.to_string(), + expected_tokens.to_string() + ); + } + + #[test] + fn test_find_all_datasource_operation_tokens() { + let ret_type = Ident::new("User", Span::call_site()); + + let find_operation = MacroOperationBuilder::new() + .fn_name("find_all_datasource") + .with_datasource_param() + .return_type(&ret_type) + .base_doc_comment("Executes a 'SELECT * FROM '") + .doc_comment("This operation retrieves all the users records stored in the provided datasource") + .query_string("SELECT * FROM users") + .with_unwrap(false); + + let generated_tokens = find_operation.generate_tokens(); + let expected_tokens = quote! { + #[doc = "Executes a 'SELECT * FROM '"] + #[doc = "This operation retrieves all the users records stored in the provided datasource"] + async fn find_all_datasource<'a>(datasource_name: &'a str) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)> > { + >::query( + "SELECT * FROM users", + &[], + datasource_name + ).await + .into_results::() + } + }; + + assert_eq!( + generated_tokens.to_string(), + expected_tokens.to_string() + ); + } + + // #[test] + // fn test_find_operation_tokens() { + // // Arrange: Build the operation + // let find_operation = MacroOperationBuilder::new() + // .fn_name("find_user_by_id") + // .datasource_param(parse_quote!(datasource)) + // .datasource_arg(quote! { datasource_arg }) + // .return_type(parse_quote!(Result)) + // .base_doc_comment("Finds a user by their ID.") + // .doc_comment("This operation retrieves a single user record based on the provided ID.") + // .query_string("SELECT * FROM users WHERE id = ?") + // .input_parameters(quote! { &[id] }) + // // .parameterized(true) + // .with_unwrap(false); + + // // Act: Generate tokens + // let generated_tokens = find_operation.generate_tokens(); + + // // Assert: Compare against expected tokens + // let expected_tokens = quote! { + // #[doc = "Finds a user by their ID."] + // #[doc = "This operation retrieves a single user record based on the provided ID."] + // async fn find_user_by_id(datasource) -> Result { + // >::query( + // "SELECT * FROM users WHERE id = ?", + // &[id], + // datasource_arg + // ).await + // .into_results::() + // } + // }; + + // assert_eq!( + // generated_tokens.to_string(), + // expected_tokens.to_string(), + // "Generated tokens do not match expected tokens!" + // ); + // } + + // #[test] + // fn test_insert_operation_tokens() { + // // Arrange: Build the operation + // let insert_operation = MacroOperationBuilder::new() + // .fn_name("insert_user") + // .datasource_param(parse_quote!(datasource)) + // .datasource_arg(quote! { datasource_arg }) + // .return_type(parse_quote!(Result<(), Error>)) + // .base_doc_comment("Inserts a new user into the database.") + // .doc_comment("This operation inserts a new user record with the provided data.") + // .query_string("INSERT INTO users (name, email) VALUES (?, ?)") + // .input_parameters(quote! { &dyn QueryParameters }) + // // .parameterized(true) + // .with_unwrap(false); + + // // Act: Generate tokens + // let generated_tokens = insert_operation.generate_tokens(); + + // // Assert: Compare against expected tokens + // let expected_tokens = quote! { + // #[doc = "Inserts a new user into the database."] + // #[doc = "This operation inserts a new user record with the provided data."] + // async fn insert_user(datasource) -> Result<(), Error> { + // >::query( + // "INSERT INTO users (name, email) VALUES (?, ?)", + // &dyn QueryParameters, + // datasource_arg + // ).await + // .into_results::() + // } + // }; + + // assert_eq!( + // generated_tokens.to_string(), + // expected_tokens.to_string(), + // "Generated tokens do not match expected tokens!" + // ); + // } +} diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index dbba723f..d59c9691 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -2,3 +2,5 @@ pub mod delete; pub mod insert; pub mod select; pub mod update; + +mod macro_template; \ No newline at end of file From 4d1b26462a235c2554fba915528b7b18310099d1 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 23 Jan 2025 13:29:19 +0100 Subject: [PATCH 033/155] feat: find_all ops variants all with the new MacroOperationBuilder --- canyon_macros/src/lib.rs | 1 + .../src/query_operations/macro_template.rs | 56 +++++++++----- canyon_macros/src/query_operations/select.rs | 74 +++++++++---------- 3 files changed, 77 insertions(+), 54 deletions(-) diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index 56b23f81..ee9753a8 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -1,3 +1,4 @@ +// TODO: remember to remove this allows #![allow(dead_code)] #![allow(unused_variables)] #![allow(unused_imports)] diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index b7e7e62d..d5d7ee5b 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -4,6 +4,7 @@ use syn::{Type, parse_quote}; pub struct MacroOperationBuilder { fn_name: Option, + user_type: Option, lifetime: bool, // bool true always will generate <'a> datasource_param: Option, datasource_arg: TokenStream, @@ -18,10 +19,17 @@ pub struct MacroOperationBuilder { with_unwrap: bool, } +impl ToTokens for MacroOperationBuilder { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.extend(self.generate_tokens()); + } +} + impl MacroOperationBuilder { pub fn new() -> Self { Self { fn_name: None, + user_type: None, lifetime: false, datasource_param: None, datasource_arg: quote! { "" }, @@ -50,6 +58,19 @@ impl MacroOperationBuilder { self } + fn get_user_type(&self) -> TokenStream { + if let Some(user_type) = &self.user_type { + quote!{ #user_type } + } else { + panic!("No T type provided for determining the operations implementor") + } + } + + pub fn user_type(mut self, ty: &Ident) -> Self { + self.user_type = Some(ty.clone()); + self + } + fn get_lifetime(&self) -> TokenStream { if self.lifetime { quote!{ <'a> } } else { quote!{} } } @@ -138,8 +159,8 @@ impl MacroOperationBuilder { } else { quote!{ &[] } } } - pub fn with_unwrap(mut self, value: bool) -> Self { - self.with_unwrap = value; + pub fn with_unwrap(mut self) -> Self { + self.with_unwrap = true; self } @@ -149,8 +170,9 @@ impl MacroOperationBuilder { let base_doc_comment = &self.base_doc_comment; let doc_comment = &self.doc_comment; + let ty = self.get_user_type(); let fn_name = self.get_fn_name(); - let lifetime = self.get_lifetime(); + let lifetime = self.get_lifetime(); // TODO: generics instead let datasource_param = self.get_datasource_param(); let datasource_name = self.get_datasource_arg(); @@ -167,12 +189,12 @@ impl MacroOperationBuilder { }; let body_tokens = quote!{ - >::query( + <#ty as canyon_sql::core::Transaction<#ty>>::query( #query_string, #forwarded_parameters, #datasource_name ).await - .into_results::() + .into_results::<#ty>() }; let separate_params = if self.input_parameters.is_some() && self.datasource_param.is_some() { @@ -198,19 +220,19 @@ mod tests { #[test] fn test_find_operation_tokens() { - let ret_type = Ident::new("User", Span::call_site()); + let user_type = Ident::new("User", Span::call_site()); let find_operation = MacroOperationBuilder::new() .fn_name("find_user_by_id") + .user_type(&user_type) .with_datasource_param() - .return_type(&ret_type) + .return_type(&user_type) .base_doc_comment("Finds a user by their ID.") .doc_comment("This operation retrieves a single user record based on the provided ID.") .query_string("SELECT * FROM users WHERE id = ?") .input_parameters(quote! { id: &dyn QueryParameters<'_> }) .forwarded_parameters(quote!{ &[id] }) - .single_result() - .with_unwrap(false); + .single_result(); let generated_tokens = find_operation.generate_tokens(); let expected_tokens = quote! { @@ -234,15 +256,15 @@ mod tests { #[test] fn test_find_all_operation_tokens() { - let ret_type = Ident::new("User", Span::call_site()); + let user_type = Ident::new("User", Span::call_site()); let find_operation = MacroOperationBuilder::new() .fn_name("find_all") - .return_type(&ret_type) + .user_type(&user_type) + .return_type(&user_type) .base_doc_comment("Executes a 'SELECT * FROM '") .doc_comment("This operation retrieves all the users records stored with the default datasource") - .query_string("SELECT * FROM users") - .with_unwrap(false); + .query_string("SELECT * FROM users"); let generated_tokens = find_operation.generate_tokens(); let expected_tokens = quote! { @@ -266,16 +288,16 @@ mod tests { #[test] fn test_find_all_datasource_operation_tokens() { - let ret_type = Ident::new("User", Span::call_site()); + let user_type = Ident::new("User", Span::call_site()); let find_operation = MacroOperationBuilder::new() .fn_name("find_all_datasource") + .user_type(&user_type) .with_datasource_param() - .return_type(&ret_type) + .return_type(&user_type) .base_doc_comment("Executes a 'SELECT * FROM '") .doc_comment("This operation retrieves all the users records stored in the provided datasource") - .query_string("SELECT * FROM users") - .with_unwrap(false); + .query_string("SELECT * FROM users"); let generated_tokens = find_operation.generate_tokens(); let expected_tokens = quote! { diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index c478e5fd..28cfaddf 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -3,6 +3,7 @@ use canyon_entities::field_annotation::EntityFieldAnnotation; use proc_macro2::{Span, TokenStream}; use quote::quote; +use crate::query_operations::macro_template::MacroOperationBuilder; use crate::utils::helpers::*; use crate::utils::macro_tokens::MacroTokens; const SELECT_ALL_BASE_DOC_COMMENT: &str = @@ -83,48 +84,47 @@ pub fn generate_find_all_tokens( // TODO: remember that this queries statements must be autogenerated by some automatic procedure // and also, we could use the const_format crate - let find_all = generate_function( - "find_all", - false, - ty, - &stmt, - false, - false, - SELECT_ALL_BASE_DOC_COMMENT, - ); - let find_all_datasource = generate_function( - "find_all_datasource", - true, - ty, - &stmt, - true, - false, - SELECT_ALL_BASE_DOC_COMMENT, - ); - let find_all_unchecked = generate_function( - "find_all_unchecked", - false, - ty, - &stmt, - false, - true, - SELECT_ALL_BASE_DOC_COMMENT, - ); - let find_all_unchecked_ds = generate_function( - "find_all_unchecked_datasource", - true, - ty, - &stmt, - true, - true, - SELECT_ALL_BASE_DOC_COMMENT, - ); + let find_all = MacroOperationBuilder::new() + .fn_name("find_all") + .user_type(ty) + .return_type(ty) + .base_doc_comment("Executes a 'SELECT * FROM '") + .doc_comment("This operation retrieves all the users records stored with the default datasource") + .query_string(&stmt); + + let find_all_datasource = MacroOperationBuilder::new() + .fn_name("find_all_datasource") + .user_type(ty) + .return_type(ty) + .with_datasource_param() + .base_doc_comment("Executes a 'SELECT * FROM '") + .doc_comment("This operation retrieves all the users records stored with the provided datasource") + .query_string(&stmt); + + let find_all_unchecked = MacroOperationBuilder::new() + .fn_name("find_all_unchecked") + .user_type(ty) + .return_type(ty) + .base_doc_comment("Executes a 'SELECT * FROM '") + .doc_comment("This operation retrieves all the users records stored with the provided datasource") + .query_string(&stmt) + .with_unwrap(); + + let find_all_unchecked_datasource = MacroOperationBuilder::new() + .fn_name("find_all_unchecked_datasource") + .user_type(ty) + .return_type(ty) + .with_datasource_param() + .base_doc_comment("Executes a 'SELECT * FROM '") + .doc_comment("This operation retrieves all the users records stored with the provided datasource") + .query_string(&stmt) + .with_unwrap(); quote! { #find_all #find_all_datasource #find_all_unchecked - #find_all_unchecked_ds + #find_all_unchecked_datasource } } From bf9505200d844a519b4810463b6b9319367b9f20 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 23 Jan 2025 16:51:29 +0100 Subject: [PATCH 034/155] feat: re-enabled the count operations --- canyon_crud/src/crud.rs | 8 +- canyon_macros/src/lib.rs | 8 +- .../src/query_operations/doc_comments.rs | 14 ++ .../src/query_operations/macro_template.rs | 86 ++++++-- canyon_macros/src/query_operations/mod.rs | 3 +- canyon_macros/src/query_operations/select.rs | 200 +++++++----------- tests/crud/select_operations.rs | 64 +++--- 7 files changed, 198 insertions(+), 185 deletions(-) create mode 100644 canyon_macros/src/query_operations/doc_comments.rs diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 99e61ff4..080bf3cb 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -40,11 +40,11 @@ where // fn select_query_datasource(datasource_name: &str) -> SelectQueryBuilder<'_, T>; - // async fn count<'a>() -> Result>; + async fn count() -> Result>; - // async fn count_datasource<'a>( - // datasource_name: &'a str, - // ) -> Result>; + async fn count_datasource<'a>( + datasource_name: &'a str, + ) -> Result>; // async fn find_by_pk<'a>( // value: &'a dyn QueryParameter<'a>, diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index ee9753a8..21b2f9a2 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -23,8 +23,8 @@ use query_operations::{ delete::{generate_delete_query_tokens, generate_delete_tokens}, insert::{generate_insert_tokens, generate_multiple_insert_tokens}, select::{ - // generate_count_tokens, generate_find_all_query_tokens, generate_find_all_tokens, + generate_count_tokens, // generate_find_by_foreign_key_tokens, // generate_find_by_pk_tokens, generate_find_by_reverse_foreign_key_tokens, }, @@ -255,7 +255,7 @@ fn impl_crud_operations_trait_for_struct( // let _find_all_query_tokens = generate_find_all_query_tokens(macro_data, &table_schema_data); // Builds a COUNT(*) query over some table - // let _count_tokens = generate_count_tokens(macro_data, &table_schema_data); + let _count_tokens = generate_count_tokens(macro_data, &table_schema_data); // Builds the find_by_pk() query // let _find_by_pk_tokens = generate_find_by_pk_tokens(macro_data, &table_schema_data); @@ -303,8 +303,8 @@ fn impl_crud_operations_trait_for_struct( // // The find_all_query impl // #_find_all_query_tokens - // // The COUNT(*) impl - // #_count_tokens + // The COUNT(*) impl + #_count_tokens // // The find_by_pk impl // #_find_by_pk_tokens diff --git a/canyon_macros/src/query_operations/doc_comments.rs b/canyon_macros/src/query_operations/doc_comments.rs new file mode 100644 index 00000000..ff1b0679 --- /dev/null +++ b/canyon_macros/src/query_operations/doc_comments.rs @@ -0,0 +1,14 @@ +pub const SELECT_ALL_BASE_DOC_COMMENT: &str = + "/// Performs a `SELECT * FROM table_name`, where `table_name` it's \ + /// the name of your entity but converted to the corresponding \ + /// database convention. P.ej. PostgreSQL prefers table names declared \ + /// with snake_case identifiers."; + +pub const SELECT_QUERYBUILDER_DOC_COMMENT: &str = + "/// Generates a [`canyon_sql::query::SelectQueryBuilder`] \ + /// that allows you to customize the query by adding parameters and constrains dynamically. \ + /// \ + /// It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your \ + /// entity but converted to the corresponding database convention, \ + /// unless concrete values are set on the available parameters of the \ + /// `canyon_macro => table_name = \"table_name\", schema = \"schema\")`"; \ No newline at end of file diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index d5d7ee5b..5edae787 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -7,7 +7,7 @@ pub struct MacroOperationBuilder { user_type: Option, lifetime: bool, // bool true always will generate <'a> datasource_param: Option, - datasource_arg: TokenStream, + datasource_arg: Option, return_type: Option, base_doc_comment: Option, doc_comment: Option, @@ -17,6 +17,11 @@ pub struct MacroOperationBuilder { forwarded_parameters: Option, single_result: bool, with_unwrap: bool, + transaction_as_variable: bool, + disable_mapping: bool, + raw_return: bool, + propagate_transaction_result: bool, + post_body: Option, } impl ToTokens for MacroOperationBuilder { @@ -26,13 +31,13 @@ impl ToTokens for MacroOperationBuilder { } impl MacroOperationBuilder { - pub fn new() -> Self { + pub const fn new() -> Self { Self { fn_name: None, user_type: None, lifetime: false, datasource_param: None, - datasource_arg: quote! { "" }, + datasource_arg: None, return_type: None, base_doc_comment: None, doc_comment: None, @@ -42,6 +47,11 @@ impl MacroOperationBuilder { forwarded_parameters: None, single_result: false, with_unwrap: false, + transaction_as_variable: false, + disable_mapping: false, + raw_return: false, + propagate_transaction_result: false, + post_body: None, } } @@ -80,13 +90,18 @@ impl MacroOperationBuilder { quote! { #ds_param } } - fn get_datasource_arg(&self) -> &TokenStream { - &self.datasource_arg + fn get_datasource_arg(&self) -> TokenStream { + if let Some(ds_arg) = &self.datasource_arg { + let ds_arg0 = ds_arg; + quote! { #ds_arg0 } + } else { + quote! { "" } + } } pub fn with_datasource_param(mut self) -> Self { self.datasource_param = Some(quote! { datasource_name: &'a str }); - self.datasource_arg = quote! { datasource_name }; + self.datasource_arg = Some(quote! { datasource_name }); self.lifetime = true; self } @@ -97,8 +112,14 @@ impl MacroOperationBuilder { quote! { Option } } else { quote! { Vec } }; + let ret_type = if self.raw_return { + quote! { #organic_ret_type } + } else { + quote! { #container_ret_type<#organic_ret_type> } + }; + match &self.with_unwrap { // TODO: distinguish collection from 1 results - true => quote! { #container_ret_type<#organic_ret_type> }, + true => quote! { #ret_type }, false => { let err_variant = if self.lifetime { quote! { Box<(dyn std::error::Error + Send + Sync + 'a)> } @@ -106,7 +127,7 @@ impl MacroOperationBuilder { quote! { Box<(dyn std::error::Error + Send + Sync)>} }; - quote! { Result<#container_ret_type<#organic_ret_type>, #err_variant> } + quote! { Result<#ret_type, #err_variant> } } } } @@ -159,11 +180,39 @@ impl MacroOperationBuilder { } else { quote!{ &[] } } } + pub fn get_unwrap(&self) -> TokenStream { + if self.with_unwrap { + quote! { .unwrap() } + } else { + quote! {} + } + } + pub fn with_unwrap(mut self) -> Self { self.with_unwrap = true; self } + pub fn transaction_as_variable(mut self, result_handling: TokenStream) -> Self { + self.transaction_as_variable = true; + self.post_body = Some(result_handling); + self + } + + pub fn disable_mapping(mut self) -> Self { + self.disable_mapping = true; + self + } + + pub fn raw_return(mut self) -> Self { + self.raw_return = true; + self + } + + pub fn propagate_transaction_result(mut self) -> Self { + self.propagate_transaction_result = true; + self + } /// Generates the final `quote!` tokens for this operation pub fn generate_tokens(&self) -> proc_macro2::TokenStream { @@ -182,20 +231,25 @@ impl MacroOperationBuilder { let forwarded_parameters = self.get_forwarded_parameters(); let return_type = self.get_return_type(); - let unwrap_tokens = if self.with_unwrap { - quote! { .unwrap() } - } else { - quote! {} - }; + let unwrap = self.get_unwrap(); - let body_tokens = quote!{ + let mut base_body_tokens = quote! { <#ty as canyon_sql::core::Transaction<#ty>>::query( #query_string, #forwarded_parameters, #datasource_name ).await - .into_results::<#ty>() }; + if self.propagate_transaction_result { base_body_tokens.extend(quote! { ? }) }; + if !self.disable_mapping { base_body_tokens.extend(quote! { .into_results::<#ty>() }) }; + + let body_tokens = if self.transaction_as_variable { + let result_handling = &self.post_body; + quote! { + let transaction_result = #base_body_tokens; + #result_handling + } + } else { base_body_tokens }; let separate_params = if self.input_parameters.is_some() && self.datasource_param.is_some() { quote! {, } @@ -206,7 +260,7 @@ impl MacroOperationBuilder { #[doc = #doc_comment] async fn #fn_name #lifetime(#fn_parameters #separate_params #datasource_param) -> #return_type { #body_tokens - #unwrap_tokens + #unwrap } } } diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index d59c9691..5eb1c6c5 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -3,4 +3,5 @@ pub mod insert; pub mod select; pub mod update; -mod macro_template; \ No newline at end of file +mod macro_template; +mod doc_comments; \ No newline at end of file diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index 28cfaddf..b8fb7748 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -6,70 +6,6 @@ use quote::quote; use crate::query_operations::macro_template::MacroOperationBuilder; use crate::utils::helpers::*; use crate::utils::macro_tokens::MacroTokens; -const SELECT_ALL_BASE_DOC_COMMENT: &str = - "/// Performs a `SELECT * FROM table_name`, where `table_name` it's \ - /// the name of your entity but converted to the corresponding \ - /// database convention. P.ej. PostgreSQL prefers table names declared \ - /// with snake_case identifiers."; -fn generate_function( - name: &str, - has_datasource: bool, - ty: &syn::Ident, - stmt: &str, - with_lifetime: bool, - with_unwrap: bool, - base_doc_comment: &str, -) -> TokenStream { - let fn_name = { - let fn_name_ident = syn::Ident::new(name, Span::call_site()); - quote! { #fn_name_ident } - }; - - let doc_comment: &str; - let mut datasource_param = quote! {}; - let mut datasource_arg = quote! { "" }; - - if has_datasource { - doc_comment = "/// The query is made against the database with the configured datasource \ - /// described in the configuration file, and selected with the [`&str`] passed as parameter."; - datasource_param = quote! { datasource_name: &'a str }; - datasource_arg = quote! { datasource_name }; - } else { - doc_comment = "/// The query is made against the default datasource configured in the configuration file."; - } - - let (err_type, lt) = if with_lifetime { - ( - quote! { Box<(dyn std::error::Error + Send + Sync + 'a)> }, - quote! { <'a> }, - ) - } else { - ( - quote! { Box<(dyn std::error::Error + Send + Sync)> }, - quote! {}, - ) - }; - - let (return_type, with_unwrap) = if with_unwrap { - (quote! { Vec<#ty> }, quote! { .unwrap() }) - } else { - (quote! { Result, #err_type> }, quote! {}) - }; - - quote! { - #[doc = #base_doc_comment] - #[doc = #doc_comment] - async fn #fn_name #lt(#datasource_param) -> #return_type { - <#ty as canyon_sql::core::Transaction<#ty>>::query( - #stmt, - &[], - #datasource_arg - ).await - .into_results::<#ty>() - #with_unwrap - } - } -} /// Generates the TokenStream for build the __find_all() CRUD /// associated function @@ -136,13 +72,13 @@ pub fn generate_find_all_tokens( // let ty = macro_data.ty; // quote! { -// /// Generates a [`canyon_sql::query::SelectQueryBuilder`] -// /// that allows you to customize the query by adding parameters and constrains dynamically. -// /// -// /// It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your -// /// entity but converted to the corresponding database convention, -// /// unless concrete values are set on the available parameters of the -// /// `canyon_macro(table_name = "table_name", schema = "schema")` + // / Generates a [`canyon_sql::query::SelectQueryBuilder`] + // / that allows you to customize the query by adding parameters and constrains dynamically. + // / + // / It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your + // / entity but converted to the corresponding database convention, + // / unless concrete values are set on the available parameters of the + // / `canyon_macro(table_name = "table_name", schema = "schema")` // fn select_query<'a>() -> canyon_sql::query::SelectQueryBuilder<'a, #ty> { // canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, "") // } @@ -164,65 +100,73 @@ pub fn generate_find_all_tokens( // } // } -// /// Performs a COUNT(*) query over some table, returning a [`Result`] wrapping -// /// a possible success or error coming from the database -// pub fn generate_count_tokens( -// macro_data: &MacroTokens<'_>, -// table_schema_data: &String, -// ) -> TokenStream { -// let ty = macro_data.ty; -// let ty_str = &ty.to_string(); -// let stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); - -// let result_handling = quote! { -// #[cfg(feature="postgres")] -// canyon_sql::core::CanyonRows::Postgres(mut v) => Ok( -// v.remove(0).get::<&str, i64>("count") -// ), -// #[cfg(feature="mssql")] -// canyon_sql::core::CanyonRows::Tiberius(mut v) => -// v.remove(0) -// .get::(0) -// .map(|c| c as i64) -// .ok_or(format!("Failure in the COUNT query for MSSQL for: {}", #ty_str).into()) -// .into(), -// #[cfg(feature="mysql")] -// canyon_sql::core::CanyonRows::MySQL(mut v) => v.remove(0) -// .get::(0) -// .ok_or(format!("Failure in the COUNT query for MYSQL for: {}", #ty_str).into()), -// _ => panic!() // TODO remove when the generics will be refactored -// }; - -// quote! { -// /// Performs a COUNT(*) query over some table, returning a [`Result`] rather than panicking, -// /// wrapping a possible success or error coming from the database -// async fn count() -> Result> { -// let count = <#ty as canyon_sql::core::Transaction<#ty>>::query( -// #stmt, -// &[], -// "" -// ).await?; - -// match count { -// #result_handling -// } -// } +/// Performs a COUNT(*) query over some table, returning a [`Result`] wrapping +/// a possible success or error coming from the database +pub fn generate_count_tokens( + macro_data: &MacroTokens<'_>, + table_schema_data: &String, +) -> TokenStream { + let ty = macro_data.ty; + let ty_str = &ty.to_string(); + let stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); + + let result_handling = quote! { + #[cfg(feature="postgres")] + canyon_sql::core::CanyonRows::Postgres(mut v) => Ok( + v.remove(0).get::<&str, i64>("count") + ), + #[cfg(feature="mssql")] + canyon_sql::core::CanyonRows::Tiberius(mut v) => + v.remove(0) + .get::(0) + .map(|c| c as i64) + .ok_or(format!("Failure in the COUNT query for MSSQL for: {}", #ty_str).into()) + .into(), + #[cfg(feature="mysql")] + canyon_sql::core::CanyonRows::MySQL(mut v) => v.remove(0) + .get::(0) + .ok_or(format!("Failure in the COUNT query for MYSQL for: {}", #ty_str).into()), + _ => panic!() // TODO remove when the generics will be refactored + }; -// /// Performs a COUNT(*) query over some table, returning a [`Result`] rather than panicking, -// /// wrapping a possible success or error coming from the database with the specified datasource -// async fn count_datasource<'a>(datasource_name: &'a str) -> Result> { -// let count = <#ty as canyon_sql::core::Transaction<#ty>>::query( -// #stmt, -// &[], -// datasource_name -// ).await?; + let count = MacroOperationBuilder::new() + .fn_name("count") + .user_type(ty) + .return_type(&syn::Ident::new("i64", Span::call_site())) // TODO: into ident or take by value + .base_doc_comment("Performs a COUNT(*) query over the table related to the entity T'") + .doc_comment("Executed with the default datasource") + .query_string(&stmt) + .transaction_as_variable(quote!{ + match transaction_result { // NOTE: dark magic. Should be refactored + #result_handling + } + }) + .propagate_transaction_result() + .disable_mapping() + .raw_return(); + + let count_with = MacroOperationBuilder::new() + .fn_name("count_datasource") + .user_type(ty) + .with_datasource_param() + .return_type(&syn::Ident::new("i64", Span::call_site())) // TODO: into ident or take by value + .base_doc_comment("Performs a COUNT(*) query over the table related to the entity T'") + .doc_comment("It will be executed with the specified datasource") + .query_string(&stmt) + .transaction_as_variable(quote!{ + match transaction_result { + #result_handling + } + }) + .propagate_transaction_result() + .disable_mapping() + .raw_return(); -// match count { -// #result_handling -// } -// } -// } -// } + quote! { + #count + #count_with + } +} // /// Generates the TokenStream for build the __find_by_pk() CRUD operation // pub fn generate_find_by_pk_tokens( diff --git a/tests/crud/select_operations.rs b/tests/crud/select_operations.rs index 878936e7..6176b6ad 100644 --- a/tests/crud/select_operations.rs +++ b/tests/crud/select_operations.rs @@ -142,37 +142,37 @@ fn test_crud_find_all_unchecked_datasource() { // ); // } -// /// Counts how many rows contains an entity on the target database. -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_count_operation() { -// assert_eq!( -// League::find_all().await.unwrap().len() as i64, -// League::count().await.unwrap() -// ); -// } +/// Counts how many rows contains an entity on the target database. +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_count_operation() { + assert_eq!( + League::find_all().await.unwrap().len() as i64, + League::count().await.unwrap() + ); +} -// /// Counts how many rows contains an entity on the target database using -// /// the specified datasource mssql -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_count_datasource_operation_mssql() { -// assert_eq!( -// League::find_all_datasource(SQL_SERVER_DS) -// .await -// .unwrap() -// .len() as i64, -// League::count_datasource(SQL_SERVER_DS).await.unwrap() -// ); -// } +/// Counts how many rows contains an entity on the target database using +/// the specified datasource mssql +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_count_datasource_operation_mssql() { + assert_eq!( + League::find_all_datasource(SQL_SERVER_DS) + .await + .unwrap() + .len() as i64, + League::count_datasource(SQL_SERVER_DS).await.unwrap() + ); +} -// /// Counts how many rows contains an entity on the target database using -// /// the specified datasource mysql -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_count_datasource_operation_mysql() { -// assert_eq!( -// League::find_all_datasource(MYSQL_DS).await.unwrap().len() as i64, -// League::count_datasource(MYSQL_DS).await.unwrap() -// ); -// } +/// Counts how many rows contains an entity on the target database using +/// the specified datasource mysql +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_count_datasource_operation_mysql() { + assert_eq!( + League::find_all_datasource(MYSQL_DS).await.unwrap().len() as i64, + League::count_datasource(MYSQL_DS).await.unwrap() + ); +} From f383977b4382b812023d80215123db3c97075d06 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 23 Jan 2025 18:00:29 +0100 Subject: [PATCH 035/155] feat: doc comments added as a collection --- .../src/query_operations/macro_template.rs | 36 ++++++++----------- canyon_macros/src/query_operations/select.rs | 27 +++++++------- 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index 5edae787..449926ba 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -9,8 +9,7 @@ pub struct MacroOperationBuilder { datasource_param: Option, datasource_arg: Option, return_type: Option, - base_doc_comment: Option, - doc_comment: Option, + doc_comments: Vec, body_tokens: Option, query_string: Option, input_parameters: Option, @@ -39,8 +38,7 @@ impl MacroOperationBuilder { datasource_param: None, datasource_arg: None, return_type: None, - base_doc_comment: None, - doc_comment: None, + doc_comments: Vec::new(), body_tokens: None, query_string: None, input_parameters: None, @@ -142,13 +140,8 @@ impl MacroOperationBuilder { self } - pub fn base_doc_comment(mut self, comment: &str) -> Self { - self.base_doc_comment = Some(comment.to_string()); - self - } - - pub fn doc_comment(mut self, comment: &str) -> Self { - self.doc_comment = Some(comment.to_string()); + pub fn add_doc_comment(mut self, comment: &str) -> Self { + self.doc_comments.push(comment.to_string()); self } @@ -216,8 +209,10 @@ impl MacroOperationBuilder { /// Generates the final `quote!` tokens for this operation pub fn generate_tokens(&self) -> proc_macro2::TokenStream { - let base_doc_comment = &self.base_doc_comment; - let doc_comment = &self.doc_comment; + let doc_comments = &self.doc_comments + .iter() + .map(|doc_comment| quote! { #[doc = #doc_comment] }) + .collect::>(); let ty = self.get_user_type(); let fn_name = self.get_fn_name(); @@ -256,8 +251,7 @@ impl MacroOperationBuilder { } else { quote! {} }; quote! { - #[doc = #base_doc_comment] - #[doc = #doc_comment] + #(#doc_comments)* async fn #fn_name #lifetime(#fn_parameters #separate_params #datasource_param) -> #return_type { #body_tokens #unwrap @@ -281,8 +275,8 @@ mod tests { .user_type(&user_type) .with_datasource_param() .return_type(&user_type) - .base_doc_comment("Finds a user by their ID.") - .doc_comment("This operation retrieves a single user record based on the provided ID.") + .add_doc_comment("Finds a user by their ID.") + .add_doc_comment("This operation retrieves a single user record based on the provided ID.") .query_string("SELECT * FROM users WHERE id = ?") .input_parameters(quote! { id: &dyn QueryParameters<'_> }) .forwarded_parameters(quote!{ &[id] }) @@ -316,8 +310,8 @@ mod tests { .fn_name("find_all") .user_type(&user_type) .return_type(&user_type) - .base_doc_comment("Executes a 'SELECT * FROM '") - .doc_comment("This operation retrieves all the users records stored with the default datasource") + .add_doc_comment("Executes a 'SELECT * FROM '") + .add_doc_comment("This operation retrieves all the users records stored with the default datasource") .query_string("SELECT * FROM users"); let generated_tokens = find_operation.generate_tokens(); @@ -349,8 +343,8 @@ mod tests { .user_type(&user_type) .with_datasource_param() .return_type(&user_type) - .base_doc_comment("Executes a 'SELECT * FROM '") - .doc_comment("This operation retrieves all the users records stored in the provided datasource") + .add_doc_comment("Executes a 'SELECT * FROM '") + .add_doc_comment("This operation retrieves all the users records stored in the provided datasource") .query_string("SELECT * FROM users"); let generated_tokens = find_operation.generate_tokens(); diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index b8fb7748..467ece9b 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -24,8 +24,8 @@ pub fn generate_find_all_tokens( .fn_name("find_all") .user_type(ty) .return_type(ty) - .base_doc_comment("Executes a 'SELECT * FROM '") - .doc_comment("This operation retrieves all the users records stored with the default datasource") + .add_doc_comment("Executes a 'SELECT * FROM '") + .add_doc_comment("This operation retrieves all the users records stored with the default datasource") .query_string(&stmt); let find_all_datasource = MacroOperationBuilder::new() @@ -33,16 +33,16 @@ pub fn generate_find_all_tokens( .user_type(ty) .return_type(ty) .with_datasource_param() - .base_doc_comment("Executes a 'SELECT * FROM '") - .doc_comment("This operation retrieves all the users records stored with the provided datasource") + .add_doc_comment("Executes a 'SELECT * FROM '") + .add_doc_comment("This operation retrieves all the users records stored with the provided datasource") .query_string(&stmt); let find_all_unchecked = MacroOperationBuilder::new() .fn_name("find_all_unchecked") .user_type(ty) .return_type(ty) - .base_doc_comment("Executes a 'SELECT * FROM '") - .doc_comment("This operation retrieves all the users records stored with the provided datasource") + .add_doc_comment("Executes a 'SELECT * FROM '") + .add_doc_comment("This operation retrieves all the users records stored with the provided datasource") .query_string(&stmt) .with_unwrap(); @@ -51,8 +51,8 @@ pub fn generate_find_all_tokens( .user_type(ty) .return_type(ty) .with_datasource_param() - .base_doc_comment("Executes a 'SELECT * FROM '") - .doc_comment("This operation retrieves all the users records stored with the provided datasource") + .add_doc_comment("Executes a 'SELECT * FROM '") + .add_doc_comment("This operation retrieves all the users records stored with the provided datasource") .query_string(&stmt) .with_unwrap(); @@ -126,15 +126,16 @@ pub fn generate_count_tokens( canyon_sql::core::CanyonRows::MySQL(mut v) => v.remove(0) .get::(0) .ok_or(format!("Failure in the COUNT query for MYSQL for: {}", #ty_str).into()), - _ => panic!() // TODO remove when the generics will be refactored + + _ => panic!() // TODO remove when the generics will be refactored }; let count = MacroOperationBuilder::new() .fn_name("count") .user_type(ty) .return_type(&syn::Ident::new("i64", Span::call_site())) // TODO: into ident or take by value - .base_doc_comment("Performs a COUNT(*) query over the table related to the entity T'") - .doc_comment("Executed with the default datasource") + .add_doc_comment("Performs a COUNT(*) query over the table related to the entity T'") + .add_doc_comment("Executed with the default datasource") .query_string(&stmt) .transaction_as_variable(quote!{ match transaction_result { // NOTE: dark magic. Should be refactored @@ -150,8 +151,8 @@ pub fn generate_count_tokens( .user_type(ty) .with_datasource_param() .return_type(&syn::Ident::new("i64", Span::call_site())) // TODO: into ident or take by value - .base_doc_comment("Performs a COUNT(*) query over the table related to the entity T'") - .doc_comment("It will be executed with the specified datasource") + .add_doc_comment("Performs a COUNT(*) query over the table related to the entity T'") + .add_doc_comment("It will be executed with the specified datasource") // TODO: doc_comment as collection of comments as tokens on the format of #[doc = "..."] n times, as much as doc_comments added .query_string(&stmt) .transaction_as_variable(quote!{ match transaction_result { From 205ff9904afb071f78f390bfa908450d7fd69d89 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 24 Jan 2025 09:05:34 +0100 Subject: [PATCH 036/155] feat: re-enabled the querybuilder ops --- canyon_crud/src/crud.rs | 4 +- .../src/query_elements/query_builder.rs | 16 +- canyon_macros/src/lib.rs | 9 +- canyon_macros/src/query_operations/select.rs | 68 +-- tests/crud/querybuilder_operations.rs | 445 +++++++++--------- 5 files changed, 271 insertions(+), 271 deletions(-) diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 080bf3cb..f82a803d 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -36,9 +36,9 @@ where async fn find_all_unchecked_datasource<'a>(datasource_name: &'a str) -> Vec; - // fn select_query<'a>() -> SelectQueryBuilder<'a, T>; + fn select_query<'a>() -> SelectQueryBuilder<'a, T>; - // fn select_query_datasource(datasource_name: &str) -> SelectQueryBuilder<'_, T>; + fn select_query_datasource(datasource_name: &str) -> SelectQueryBuilder<'_, T>; async fn count() -> Result>; diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index 9eacf89e..ce45e5e3 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -176,15 +176,13 @@ where ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'static)>> { self.query.sql.push(';'); - // Ok(T::query( - // self, - // self.query.sql.clone(), - // self.query.params.to_vec(), - // // self.datasource_name, - // ) - // .await? - // .into_results::()) - todo!() + Ok(T::query( + self.query.sql.clone(), + self.query.params.to_vec(), + self.datasource_name, + ) + .await? + .into_results::()) } pub fn r#where>(&mut self, r#where: Z, op: impl Operator) { diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index 21b2f9a2..adeac3da 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -25,6 +25,7 @@ use query_operations::{ select::{ generate_find_all_tokens, generate_count_tokens, + generate_find_all_query_tokens, // generate_find_by_foreign_key_tokens, // generate_find_by_pk_tokens, generate_find_by_reverse_foreign_key_tokens, }, @@ -252,7 +253,7 @@ fn impl_crud_operations_trait_for_struct( // Builds the find_all() query let _find_all_tokens = generate_find_all_tokens(macro_data, &table_schema_data); // Builds the find_all_query() query as a QueryBuilder - // let _find_all_query_tokens = generate_find_all_query_tokens(macro_data, &table_schema_data); + let _find_all_query_tokens = generate_find_all_query_tokens(macro_data, &table_schema_data); // Builds a COUNT(*) query over some table let _count_tokens = generate_count_tokens(macro_data, &table_schema_data); @@ -297,11 +298,11 @@ fn impl_crud_operations_trait_for_struct( ); let crud_operations_tokens = quote! { - // The find_all_result impl + // The find_all_result impl // TODO: they must be wrapped into only four, C-R-U-D #_find_all_tokens - // // The find_all_query impl - // #_find_all_query_tokens + // The SELECT_QUERYBUILDER + #_find_all_query_tokens // The COUNT(*) impl #_count_tokens diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index 467ece9b..fd548484 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -64,41 +64,41 @@ pub fn generate_find_all_tokens( } } -// /// Same as above, but with a [`canyon_sql::query::QueryBuilder`] -// pub fn generate_find_all_query_tokens( -// macro_data: &MacroTokens<'_>, -// table_schema_data: &String, -// ) -> TokenStream { -// let ty = macro_data.ty; - -// quote! { - // / Generates a [`canyon_sql::query::SelectQueryBuilder`] - // / that allows you to customize the query by adding parameters and constrains dynamically. - // / - // / It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your - // / entity but converted to the corresponding database convention, - // / unless concrete values are set on the available parameters of the - // / `canyon_macro(table_name = "table_name", schema = "schema")` -// fn select_query<'a>() -> canyon_sql::query::SelectQueryBuilder<'a, #ty> { -// canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, "") -// } +/// Same as above, but with a [`canyon_sql::query::QueryBuilder`] +pub fn generate_find_all_query_tokens( + macro_data: &MacroTokens<'_>, + table_schema_data: &String, +) -> TokenStream { + let ty = macro_data.ty; -// /// Generates a [`canyon_sql::query::SelectQueryBuilder`] -// /// that allows you to customize the query by adding parameters and constrains dynamically. -// /// -// /// It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your -// /// entity but converted to the corresponding database convention, -// /// unless concrete values are set on the available parameters of the -// /// `canyon_macro(table_name = "table_name", schema = "schema")` -// /// -// /// The query it's made against the database with the configured datasource -// /// described in the configuration file, and selected with the [`&str`] -// /// passed as parameter. -// fn select_query_datasource<'a>(datasource_name: &'a str) -> canyon_sql::query::SelectQueryBuilder<'a, #ty> { -// canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, datasource_name) -// } -// } -// } + quote! { + /// Generates a [`canyon_sql::query::SelectQueryBuilder`] + /// that allows you to customize the query by adding parameters and constrains dynamically. + /// + /// It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your + /// entity but converted to the corresponding database convention, + /// unless concrete values are set on the available parameters of the + /// `canyon_macro(table_name = "table_name", schema = "schema")` + fn select_query<'a>() -> canyon_sql::query::SelectQueryBuilder<'a, #ty> { + canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, "") + } + + /// Generates a [`canyon_sql::query::SelectQueryBuilder`] + /// that allows you to customize the query by adding parameters and constrains dynamically. + /// + /// It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your + /// entity but converted to the corresponding database convention, + /// unless concrete values are set on the available parameters of the + /// `canyon_macro(table_name = "table_name", schema = "schema")` + /// + /// The query it's made against the database with the configured datasource + /// described in the configuration file, and selected with the [`&str`] + /// passed as parameter. + fn select_query_datasource<'a>(datasource_name: &'a str) -> canyon_sql::query::SelectQueryBuilder<'a, #ty> { + canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, datasource_name) + } + } +} /// Performs a COUNT(*) query over some table, returning a [`Result`] wrapping /// a possible success or error coming from the database diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index 7640aac6..f1874d72 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -1,225 +1,226 @@ -// #[cfg(feature = "mysql")] -// use crate::constants::MYSQL_DS; -// #[cfg(feature = "mssql")] -// use crate::constants::SQL_SERVER_DS; - -// /// Tests for the QueryBuilder available operations within Canyon. -// /// -// /// QueryBuilder are the way of obtain more flexibility that with -// /// the default generated queries, essentially for build the queries -// /// with the SQL filters -// /// -// use canyon_sql::{ -// crud::CrudOperations, -// query::{operators::Comp, operators::Like, ops::QueryBuilder}, -// }; - -// use crate::tests_models::league::*; -// use crate::tests_models::player::*; -// use crate::tests_models::tournament::*; - -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_generated_sql_by_the_select_querybuilder() { -// let mut select_with_joins = League::select_query(); -// select_with_joins -// .inner_join("tournament", "league.id", "tournament.league_id") -// .left_join("team", "tournament.id", "player.tournament_id") -// .r#where(LeagueFieldValue::id(&7), Comp::Gt) -// .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) -// .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); -// // .query() -// // .await; -// // NOTE: We don't have in the docker the generated relationships -// // with the joins, so for now, we are just going to check that the -// // generated SQL by the SelectQueryBuilder is the spected -// assert_eq!( -// select_with_joins.read_sql(), -// "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" -// ) -// } - -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder() { -// // Find all the leagues with ID less or equals that 7 -// // and where it's region column value is equals to 'Korea' -// let filtered_leagues_result: Result, _> = League::select_query() -// .r#where(LeagueFieldValue::id(&50), Comp::LtEq) -// .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) -// .query() -// .await; - -// let filtered_leagues: Vec = filtered_leagues_result.unwrap(); -// assert!(!filtered_leagues.is_empty()); - -// let league_idx_0 = filtered_leagues.first().unwrap(); -// assert_eq!(league_idx_0.id, 34); -// assert_eq!(league_idx_0.region, "KOREA"); -// } - -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_fulllike() { -// // Find all the leagues with "LC" in their name -// let mut filtered_leagues_result = League::select_query(); -// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); - -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" -// ) -// } - -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_fulllike_datasource_mssql() { -// // Find all the leagues with "LC" in their name -// let mut filtered_leagues_result = League::select_query_datasource(SQL_SERVER_DS); -// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); - -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" -// ) -// } - -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_fulllike_datasource_mysql() { -// // Find all the leagues with "LC" in their name -// let mut filtered_leagues_result = League::select_query_datasource(MYSQL_DS); -// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); - -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR) ,'%')" -// ) -// } - -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_leftlike() { -// // Find all the leagues whose name ends with "CK" -// let mut filtered_leagues_result = League::select_query(); -// filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); - -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" -// ) -// } - -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_leftlike_datasource_mssql() { -// // Find all the leagues whose name ends with "CK" -// let mut filtered_leagues_result = League::select_query(); -// filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); - -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" -// ) -// } - -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_leftlike_datasource_mysql() { -// // Find all the leagues whose name ends with "CK" -// let mut filtered_leagues_result = League::select_query_datasource(MYSQL_DS); -// filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); - -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR))" -// ) -// } - -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_rightlike() { -// // Find all the leagues whose name starts with "LC" -// let mut filtered_leagues_result = League::select_query(); -// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); - -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" -// ) -// } - -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_rightlike_datasource_mssql() { -// // Find all the leagues whose name starts with "LC" -// let mut filtered_leagues_result = League::select_query_datasource(SQL_SERVER_DS); -// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); - -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" -// ) -// } -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_rightlike_datasource_mysql() { -// // Find all the leagues whose name starts with "LC" -// let mut filtered_leagues_result = League::select_query_datasource(MYSQL_DS); -// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); - -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS CHAR) ,'%')" -// ) -// } - -// /// Same than the above but with the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_datasource_mssql() { -// // Find all the players where its ID column value is greater that 50 -// let filtered_find_players = Player::select_query_datasource(SQL_SERVER_DS) -// .r#where(PlayerFieldValue::id(&50), Comp::Gt) -// .query() -// .await; - -// assert!(!filtered_find_players.unwrap().is_empty()); -// } - -// /// Same than the above but with the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_datasource_mysql() { -// // Find all the players where its ID column value is greater that 50 -// let filtered_find_players = Player::select_query_datasource(MYSQL_DS) -// .r#where(PlayerFieldValue::id(&50), Comp::Gt) -// .query() -// .await; - -// assert!(!filtered_find_players.unwrap().is_empty()); -// } +#[cfg(feature = "mysql")] +use crate::constants::MYSQL_DS; +#[cfg(feature = "mssql")] +use crate::constants::SQL_SERVER_DS; + +/// Tests for the QueryBuilder available operations within Canyon. +/// +/// QueryBuilder are the way of obtain more flexibility that with +/// the default generated queries, essentially for build the queries +/// with the SQL filters +/// +use canyon_sql::{ + crud::CrudOperations, + query::{operators::Comp, operators::Like, ops::QueryBuilder}, +}; + +use crate::tests_models::league::*; +use crate::tests_models::player::*; +use crate::tests_models::tournament::*; + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[canyon_sql::macros::canyon_tokio_test] +fn test_generated_sql_by_the_select_querybuilder() { + let mut select_with_joins = League::select_query(); + select_with_joins + .inner_join("tournament", "league.id", "tournament.league_id") + .left_join("team", "tournament.id", "player.tournament_id") + .r#where(LeagueFieldValue::id(&7), Comp::Gt) + .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) + .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); + // .query() + // .await; + // NOTE: We don't have in the docker the generated relationships + // with the joins, so for now, we are just going to check that the + // generated SQL by the SelectQueryBuilder is the spected + assert_eq!( + select_with_joins.read_sql(), + "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder() { + // Find all the leagues with ID less or equals that 7 + // and where it's region column value is equals to 'Korea' + let filtered_leagues_result: Result, _> = League::select_query() + .r#where(LeagueFieldValue::id(&50), Comp::LtEq) + .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) + .query() + .await; + + let filtered_leagues: Vec = filtered_leagues_result.unwrap(); + assert!(!filtered_leagues.is_empty()); + + let league_idx_0 = filtered_leagues.first().unwrap(); + assert_eq!(league_idx_0.id, 34); + assert_eq!(league_idx_0.region, "KOREA"); +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_fulllike() { + // Find all the leagues with "LC" in their name + let mut filtered_leagues_result = League::select_query(); + filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_fulllike_datasource_mssql() { + // Find all the leagues with "LC" in their name + let mut filtered_leagues_result = League::select_query_datasource(SQL_SERVER_DS); + filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_fulllike_datasource_mysql() { + // Find all the leagues with "LC" in their name + let mut filtered_leagues_result = League::select_query_datasource(MYSQL_DS); + filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_leftlike() { + // Find all the leagues whose name ends with "CK" + let mut filtered_leagues_result = League::select_query(); + filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_leftlike_datasource_mssql() { + // Find all the leagues whose name ends with "CK" + let mut filtered_leagues_result = League::select_query(); + filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_leftlike_datasource_mysql() { + // Find all the leagues whose name ends with "CK" + let mut filtered_leagues_result = League::select_query_datasource(MYSQL_DS); + filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR))" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_rightlike() { + // Find all the leagues whose name starts with "LC" + let mut filtered_leagues_result = League::select_query(); + filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_rightlike_datasource_mssql() { + // Find all the leagues whose name starts with "LC" + let mut filtered_leagues_result = League::select_query_datasource(SQL_SERVER_DS); + filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_rightlike_datasource_mysql() { + // Find all the leagues whose name starts with "LC" + let mut filtered_leagues_result = League::select_query_datasource(MYSQL_DS); + filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS CHAR) ,'%')" + ) +} + +/// Same than the above but with the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_datasource_mssql() { + // Find all the players where its ID column value is greater that 50 + let filtered_find_players = Player::select_query_datasource(SQL_SERVER_DS) + .r#where(PlayerFieldValue::id(&50), Comp::Gt) + .query() + .await; + + assert!(!filtered_find_players.unwrap().is_empty()); +} + +/// Same than the above but with the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_datasource_mysql() { + // Find all the players where its ID column value is greater that 50 + let filtered_find_players = Player::select_query_datasource(MYSQL_DS) + .r#where(PlayerFieldValue::id(&50), Comp::Gt) + .query() + .await; + + assert!(!filtered_find_players.unwrap().is_empty()); +} // /// Updates the values of the range on entries defined by the constraint parameters // /// in the database entity From c72dcac474f9a788f25fcfa306d6d1147b29b636 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 24 Jan 2025 11:20:37 +0100 Subject: [PATCH 037/155] test: unit testing the generated tokens for the find_all operations macros --- canyon_macros/src/query_operations/select.rs | 159 +++++++++++++++---- 1 file changed, 127 insertions(+), 32 deletions(-) diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index fd548484..4bcf4d32 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -13,6 +13,8 @@ pub fn generate_find_all_tokens( macro_data: &MacroTokens<'_>, table_schema_data: &String, ) -> TokenStream { + use __details::find_all_generators::*; + let ty = macro_data.ty; let stmt = format!("SELECT * FROM {table_schema_data}"); // TODO: bring the helper and convert the SELECT * into the @@ -20,41 +22,13 @@ pub fn generate_find_all_tokens( // TODO: remember that this queries statements must be autogenerated by some automatic procedure // and also, we could use the const_format crate - let find_all = MacroOperationBuilder::new() - .fn_name("find_all") - .user_type(ty) - .return_type(ty) - .add_doc_comment("Executes a 'SELECT * FROM '") - .add_doc_comment("This operation retrieves all the users records stored with the default datasource") - .query_string(&stmt); + let find_all = create_find_all_macro(ty, &stmt); - let find_all_datasource = MacroOperationBuilder::new() - .fn_name("find_all_datasource") - .user_type(ty) - .return_type(ty) - .with_datasource_param() - .add_doc_comment("Executes a 'SELECT * FROM '") - .add_doc_comment("This operation retrieves all the users records stored with the provided datasource") - .query_string(&stmt); + let find_all_datasource = create_find_all_ds_macro(ty, &stmt); - let find_all_unchecked = MacroOperationBuilder::new() - .fn_name("find_all_unchecked") - .user_type(ty) - .return_type(ty) - .add_doc_comment("Executes a 'SELECT * FROM '") - .add_doc_comment("This operation retrieves all the users records stored with the provided datasource") - .query_string(&stmt) - .with_unwrap(); + let find_all_unchecked = create_find_all_unchecked_macro(ty, &stmt); - let find_all_unchecked_datasource = MacroOperationBuilder::new() - .fn_name("find_all_unchecked_datasource") - .user_type(ty) - .return_type(ty) - .with_datasource_param() - .add_doc_comment("Executes a 'SELECT * FROM '") - .add_doc_comment("This operation retrieves all the users records stored with the provided datasource") - .query_string(&stmt) - .with_unwrap(); + let find_all_unchecked_datasource = create_find_all_unchecked_ds_macro(ty, &stmt); quote! { #find_all @@ -455,3 +429,124 @@ pub fn generate_count_tokens( // rev_fk_quotes // } + +mod __details { + use crate::query_operations::macro_template::MacroOperationBuilder; + + pub mod find_all_generators { + use super::*; + + pub fn create_find_all_macro(ty: &syn::Ident, stmt: &str) -> MacroOperationBuilder { + MacroOperationBuilder::new() + .fn_name("find_all") + .user_type(ty) + .return_type(ty) + .add_doc_comment("Executes a 'SELECT * FROM '") + .add_doc_comment("This operation retrieves all the users records stored with the default datasource") + .query_string(&stmt) + } + + pub fn create_find_all_ds_macro(ty: &syn::Ident, stmt: &str) -> MacroOperationBuilder { + MacroOperationBuilder::new() + .fn_name("find_all_datasource") + .user_type(ty) + .return_type(ty) + .with_datasource_param() + .add_doc_comment("Executes a 'SELECT * FROM '") + .add_doc_comment("This operation retrieves all the users records stored with the provided datasource") + .query_string(&stmt) + } + + pub fn create_find_all_unchecked_macro(ty: &syn::Ident, stmt: &str) -> MacroOperationBuilder { + MacroOperationBuilder::new() + .fn_name("find_all_unchecked") + .user_type(ty) + .return_type(ty) + .add_doc_comment("Executes a 'SELECT * FROM '") + .add_doc_comment("This operation retrieves all the users records stored with the provided datasource") + .query_string(&stmt) + .with_unwrap() + } + + pub fn create_find_all_unchecked_ds_macro(ty: &syn::Ident, stmt: &str) -> MacroOperationBuilder { + MacroOperationBuilder::new() + .fn_name("find_all_unchecked_datasource") + .user_type(ty) + .return_type(ty) + .with_datasource_param() + .add_doc_comment("Executes a 'SELECT * FROM '") + .add_doc_comment("This operation retrieves all the users records stored with the provided datasource") + .query_string(&stmt) + .with_unwrap() + } + } +} + +#[cfg(test)] +mod macro_builder_find_all_tests { + use super::__details::find_all_generators::*; + use proc_macro2::Span; + use syn::Ident; + + const SELECT_ALL_STMT: &str = "SELECT * FROM public.user"; + const RAW_RET_TY: &str = "Vec < User >"; + const RES_RET_TY: &str = "Result < Vec < User > , Box < (dyn std :: error :: Error + Send + Sync) > >"; + const RES_RET_TY_LT: &str = "Result < Vec < User > , Box < (dyn std :: error :: Error + Send + Sync + 'a) > >"; + const MAPS_TO: &str = "into_results :: < User > ()"; + const LT_CONSTRAINT: &str = "< 'a >"; + const DS_PARAM: &str = "(datasource_name : & 'a str)"; + + #[test] + fn test_macro_builder_find_all() { + let ty: Ident = Ident::new("User", Span::call_site()); + let find_all_builder = create_find_all_macro(&ty, SELECT_ALL_STMT); + let find_all = find_all_builder + .generate_tokens() + .to_string(); + + assert!(find_all.contains("async fn find_all")); + assert!(find_all.contains(RES_RET_TY)); + } + + #[test] + fn test_macro_builder_find_all_datasource() { + let ty: Ident = Ident::new("User", Span::call_site()); + let find_all_builder = create_find_all_ds_macro(&ty, SELECT_ALL_STMT); + let find_all_ds = find_all_builder + .generate_tokens() + .to_string(); + + println!("{:?}", find_all_ds.split("\n").collect::>()); + + assert!(find_all_ds.contains("async fn find_all_datasource")); + assert!(find_all_ds.contains(RES_RET_TY_LT)); + assert!(find_all_ds.contains(LT_CONSTRAINT)); + assert!(find_all_ds.contains(DS_PARAM)); + } + + #[test] + fn test_macro_builder_find_all_unchecked() { + let ty: Ident = Ident::new("User", Span::call_site()); + let find_all_builder = create_find_all_unchecked_macro(&ty, SELECT_ALL_STMT); + let find_all_ds = find_all_builder + .generate_tokens() + .to_string(); + + assert!(find_all_ds.contains("async fn find_all_unchecked")); + assert!(find_all_ds.contains(RAW_RET_TY)); + } + + #[test] + fn test_macro_builder_find_all_unchecked_datasource() { + let ty: Ident = Ident::new("User", Span::call_site()); + let find_all_builder = create_find_all_unchecked_ds_macro(&ty, SELECT_ALL_STMT); + let find_all_ds = find_all_builder + .generate_tokens() + .to_string(); + + assert!(find_all_ds.contains("async fn find_all_unchecked_datasource")); + assert!(find_all_ds.contains(RAW_RET_TY)); + assert!(find_all_ds.contains(LT_CONSTRAINT)); + assert!(find_all_ds.contains(DS_PARAM)); + } +} \ No newline at end of file From 314546eb0669b7be0aa7f6651230bee814bea436 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 24 Jan 2025 12:18:29 +0100 Subject: [PATCH 038/155] feat: final refactor for the count operations and unit tests for the fn macro generation --- canyon_macros/src/lib.rs | 21 +- canyon_macros/src/query_operations/select.rs | 225 +++++++++++-------- 2 files changed, 135 insertions(+), 111 deletions(-) diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index adeac3da..b7c85470 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -23,8 +23,7 @@ use query_operations::{ delete::{generate_delete_query_tokens, generate_delete_tokens}, insert::{generate_insert_tokens, generate_multiple_insert_tokens}, select::{ - generate_find_all_tokens, - generate_count_tokens, + generate_read_operations_tokens, generate_find_all_query_tokens, // generate_find_by_foreign_key_tokens, // generate_find_by_pk_tokens, generate_find_by_reverse_foreign_key_tokens, @@ -250,13 +249,8 @@ fn impl_crud_operations_trait_for_struct( ) -> proc_macro::TokenStream { let ty = macro_data.ty; - // Builds the find_all() query - let _find_all_tokens = generate_find_all_tokens(macro_data, &table_schema_data); - // Builds the find_all_query() query as a QueryBuilder - let _find_all_query_tokens = generate_find_all_query_tokens(macro_data, &table_schema_data); - - // Builds a COUNT(*) query over some table - let _count_tokens = generate_count_tokens(macro_data, &table_schema_data); + let read_operations_tokens = generate_read_operations_tokens(macro_data, &table_schema_data); + let find_all_query_tokens = generate_find_all_query_tokens(macro_data, &table_schema_data); // Builds the find_by_pk() query // let _find_by_pk_tokens = generate_find_by_pk_tokens(macro_data, &table_schema_data); @@ -299,13 +293,10 @@ fn impl_crud_operations_trait_for_struct( let crud_operations_tokens = quote! { // The find_all_result impl // TODO: they must be wrapped into only four, C-R-U-D - #_find_all_tokens - - // The SELECT_QUERYBUILDER - #_find_all_query_tokens + #read_operations_tokens - // The COUNT(*) impl - #_count_tokens + // The SELECT_QUERYBUILDER impl + #find_all_query_tokens // // The find_by_pk impl // #_find_by_pk_tokens diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index 4bcf4d32..9a26688a 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -7,38 +7,39 @@ use crate::query_operations::macro_template::MacroOperationBuilder; use crate::utils::helpers::*; use crate::utils::macro_tokens::MacroTokens; -/// Generates the TokenStream for build the __find_all() CRUD -/// associated function -pub fn generate_find_all_tokens( +pub fn generate_read_operations_tokens( macro_data: &MacroTokens<'_>, table_schema_data: &String, ) -> TokenStream { - use __details::find_all_generators::*; + use __details::{find_all_generators::*, count_generators::*}; let ty = macro_data.ty; - let stmt = format!("SELECT * FROM {table_schema_data}"); + let fa_stmt = format!("SELECT * FROM {table_schema_data}"); // TODO: bring the helper and convert the SELECT * into the // SELECT col_name, col_name2...? // TODO: remember that this queries statements must be autogenerated by some automatic procedure // and also, we could use the const_format crate - let find_all = create_find_all_macro(ty, &stmt); - - let find_all_datasource = create_find_all_ds_macro(ty, &stmt); - - let find_all_unchecked = create_find_all_unchecked_macro(ty, &stmt); - - let find_all_unchecked_datasource = create_find_all_unchecked_ds_macro(ty, &stmt); + let find_all = create_find_all_macro(ty, &fa_stmt); + let find_all_datasource = create_find_all_ds_macro(ty, &fa_stmt); + let find_all_unchecked = create_find_all_unchecked_macro(ty, &fa_stmt); + let find_all_unchecked_datasource = create_find_all_unchecked_ds_macro(ty, &fa_stmt); + + let count_stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); + let count = create_count_macro(ty, &count_stmt); + let count_datasource = create_count_ds_macro(ty, &count_stmt); quote! { #find_all #find_all_datasource #find_all_unchecked #find_all_unchecked_datasource + + #count + #count_datasource } } -/// Same as above, but with a [`canyon_sql::query::QueryBuilder`] pub fn generate_find_all_query_tokens( macro_data: &MacroTokens<'_>, table_schema_data: &String, @@ -74,75 +75,6 @@ pub fn generate_find_all_query_tokens( } } -/// Performs a COUNT(*) query over some table, returning a [`Result`] wrapping -/// a possible success or error coming from the database -pub fn generate_count_tokens( - macro_data: &MacroTokens<'_>, - table_schema_data: &String, -) -> TokenStream { - let ty = macro_data.ty; - let ty_str = &ty.to_string(); - let stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); - - let result_handling = quote! { - #[cfg(feature="postgres")] - canyon_sql::core::CanyonRows::Postgres(mut v) => Ok( - v.remove(0).get::<&str, i64>("count") - ), - #[cfg(feature="mssql")] - canyon_sql::core::CanyonRows::Tiberius(mut v) => - v.remove(0) - .get::(0) - .map(|c| c as i64) - .ok_or(format!("Failure in the COUNT query for MSSQL for: {}", #ty_str).into()) - .into(), - #[cfg(feature="mysql")] - canyon_sql::core::CanyonRows::MySQL(mut v) => v.remove(0) - .get::(0) - .ok_or(format!("Failure in the COUNT query for MYSQL for: {}", #ty_str).into()), - - _ => panic!() // TODO remove when the generics will be refactored - }; - - let count = MacroOperationBuilder::new() - .fn_name("count") - .user_type(ty) - .return_type(&syn::Ident::new("i64", Span::call_site())) // TODO: into ident or take by value - .add_doc_comment("Performs a COUNT(*) query over the table related to the entity T'") - .add_doc_comment("Executed with the default datasource") - .query_string(&stmt) - .transaction_as_variable(quote!{ - match transaction_result { // NOTE: dark magic. Should be refactored - #result_handling - } - }) - .propagate_transaction_result() - .disable_mapping() - .raw_return(); - - let count_with = MacroOperationBuilder::new() - .fn_name("count_datasource") - .user_type(ty) - .with_datasource_param() - .return_type(&syn::Ident::new("i64", Span::call_site())) // TODO: into ident or take by value - .add_doc_comment("Performs a COUNT(*) query over the table related to the entity T'") - .add_doc_comment("It will be executed with the specified datasource") // TODO: doc_comment as collection of comments as tokens on the format of #[doc = "..."] n times, as much as doc_comments added - .query_string(&stmt) - .transaction_as_variable(quote!{ - match transaction_result { - #result_handling - } - }) - .propagate_transaction_result() - .disable_mapping() - .raw_return(); - - quote! { - #count - #count_with - } -} - // /// Generates the TokenStream for build the __find_by_pk() CRUD operation // pub fn generate_find_by_pk_tokens( // macro_data: &MacroTokens<'_>, @@ -432,6 +364,9 @@ pub fn generate_count_tokens( mod __details { use crate::query_operations::macro_template::MacroOperationBuilder; + use quote::quote; + use proc_macro2::Span; + use syn::Ident; pub mod find_all_generators { use super::*; @@ -480,15 +415,88 @@ mod __details { .with_unwrap() } } + + pub mod count_generators { + use proc_macro2::TokenStream; + + use super::*; + + fn generate_count_manual_result_handling(ty: &Ident) -> TokenStream { + let ty_str = ty.to_string(); + + quote! { + #[cfg(feature="postgres")] + canyon_sql::core::CanyonRows::Postgres(mut v) => Ok( + v.remove(0).get::<&str, i64>("count") + ), + #[cfg(feature="mssql")] + canyon_sql::core::CanyonRows::Tiberius(mut v) => + v.remove(0) + .get::(0) + .map(|c| c as i64) + .ok_or(format!("Failure in the COUNT query for MSSQL for: {}", #ty_str).into()) + .into(), + #[cfg(feature="mysql")] + canyon_sql::core::CanyonRows::MySQL(mut v) => v.remove(0) + .get::(0) + .ok_or(format!("Failure in the COUNT query for MYSQL for: {}", #ty_str).into()), + + _ => panic!() // TODO remove when the generics will be refactored + } + } + + pub fn create_count_macro(ty: &syn::Ident, stmt: &str) -> MacroOperationBuilder { + let result_handling = generate_count_manual_result_handling(ty); + + MacroOperationBuilder::new() + .fn_name("count") + .user_type(ty) + .return_type(&syn::Ident::new("i64", Span::call_site())) // TODO: into ident or take by value + .add_doc_comment("Performs a COUNT(*) query over the table related to the entity T'") + .add_doc_comment("Executed with the default datasource") + .query_string(&stmt) + .transaction_as_variable(quote!{ + match transaction_result { // NOTE: dark magic. Should be refactored + #result_handling + } + }) + .propagate_transaction_result() + .disable_mapping() + .raw_return() + } + + pub fn create_count_ds_macro(ty: &syn::Ident, stmt: &str) -> MacroOperationBuilder { + let result_handling = generate_count_manual_result_handling(ty); + + MacroOperationBuilder::new() + .fn_name("count_datasource") + .user_type(ty) + .with_datasource_param() + .return_type(&syn::Ident::new("i64", Span::call_site())) // TODO: into ident or take by value + .add_doc_comment("Performs a COUNT(*) query over the table related to the entity T'") + .add_doc_comment("It will be executed with the specified datasource") + .query_string(&stmt) + .transaction_as_variable(quote!{ + match transaction_result { + #result_handling + } + }) + .propagate_transaction_result() + .disable_mapping() + .raw_return() + } + } } #[cfg(test)] -mod macro_builder_find_all_tests { - use super::__details::find_all_generators::*; +mod macro_builder_read_ops_tests { + use super::__details::{find_all_generators::*, count_generators::*}; use proc_macro2::Span; use syn::Ident; const SELECT_ALL_STMT: &str = "SELECT * FROM public.user"; + const COUNT_STMT: &str = "SELECT COUNT(*) FROM public.user"; + const RAW_RET_TY: &str = "Vec < User >"; const RES_RET_TY: &str = "Result < Vec < User > , Box < (dyn std :: error :: Error + Send + Sync) > >"; const RES_RET_TY_LT: &str = "Result < Vec < User > , Box < (dyn std :: error :: Error + Send + Sync + 'a) > >"; @@ -516,8 +524,6 @@ mod macro_builder_find_all_tests { .generate_tokens() .to_string(); - println!("{:?}", find_all_ds.split("\n").collect::>()); - assert!(find_all_ds.contains("async fn find_all_datasource")); assert!(find_all_ds.contains(RES_RET_TY_LT)); assert!(find_all_ds.contains(LT_CONSTRAINT)); @@ -527,26 +533,53 @@ mod macro_builder_find_all_tests { #[test] fn test_macro_builder_find_all_unchecked() { let ty: Ident = Ident::new("User", Span::call_site()); - let find_all_builder = create_find_all_unchecked_macro(&ty, SELECT_ALL_STMT); - let find_all_ds = find_all_builder + let find_all_unc_builder = create_find_all_unchecked_macro(&ty, SELECT_ALL_STMT); + let find_all_unc = find_all_unc_builder .generate_tokens() .to_string(); - assert!(find_all_ds.contains("async fn find_all_unchecked")); - assert!(find_all_ds.contains(RAW_RET_TY)); + assert!(find_all_unc.contains("async fn find_all_unchecked")); + assert!(find_all_unc.contains(RAW_RET_TY)); } #[test] fn test_macro_builder_find_all_unchecked_datasource() { let ty: Ident = Ident::new("User", Span::call_site()); - let find_all_builder = create_find_all_unchecked_ds_macro(&ty, SELECT_ALL_STMT); - let find_all_ds = find_all_builder + let find_all_unc_ds_builder = create_find_all_unchecked_ds_macro(&ty, SELECT_ALL_STMT); + let find_all_unc_ds = find_all_unc_ds_builder .generate_tokens() .to_string(); - assert!(find_all_ds.contains("async fn find_all_unchecked_datasource")); - assert!(find_all_ds.contains(RAW_RET_TY)); - assert!(find_all_ds.contains(LT_CONSTRAINT)); - assert!(find_all_ds.contains(DS_PARAM)); + assert!(find_all_unc_ds.contains("async fn find_all_unchecked_datasource")); + assert!(find_all_unc_ds.contains(RAW_RET_TY)); + assert!(find_all_unc_ds.contains(LT_CONSTRAINT)); + assert!(find_all_unc_ds.contains(DS_PARAM)); + } + + #[test] + fn test_macro_builder_count() { + let ty: Ident = Ident::new("User", Span::call_site()); + let count_builder = create_count_macro(&ty, COUNT_STMT); + let count = count_builder + .generate_tokens() + .to_string(); + println!("{:?}", count.split("\n").collect::>()); + + assert!(count.contains("async fn count")); + assert!(count.contains("Result < i64")); + } + + #[test] + fn test_macro_builder_count_datasource() { + let ty: Ident = Ident::new("User", Span::call_site()); + let count_ds_builder = create_count_ds_macro(&ty, COUNT_STMT); + let count_ds = count_ds_builder + .generate_tokens() + .to_string(); + + assert!(count_ds.contains("async fn count_datasource")); + assert!(count_ds.contains("Result < i64")); + assert!(count_ds.contains(LT_CONSTRAINT)); + assert!(count_ds.contains(DS_PARAM)); } } \ No newline at end of file From df3519367edfbf971ee1b252e99c39351ad87ee9 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 24 Jan 2025 14:03:07 +0100 Subject: [PATCH 039/155] feat: re-enabled the find by primary key operations --- canyon_crud/src/crud.rs | 14 +- canyon_macros/src/lib.rs | 5 +- .../src/query_operations/doc_comments.rs | 21 +- .../src/query_operations/macro_template.rs | 5 + canyon_macros/src/query_operations/select.rs | 259 ++++++++++-------- tests/crud/select_operations.rs | 136 ++++----- 6 files changed, 250 insertions(+), 190 deletions(-) diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index f82a803d..1324b470 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -46,14 +46,14 @@ where datasource_name: &'a str, ) -> Result>; - // async fn find_by_pk<'a>( - // value: &'a dyn QueryParameter<'a>, - // ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>>; + async fn find_by_pk<'a>( + value: &'a dyn QueryParameter<'a>, + ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>>; - // async fn find_by_pk_datasource<'a>( - // value: &'a dyn QueryParameter<'a>, - // datasource_name: &'a str, - // ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>>; + async fn find_by_pk_datasource<'a>( + value: &'a dyn QueryParameter<'a>, + datasource_name: &'a str, + ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>>; // async fn insert<'a>(&mut self) -> Result<(), Box>; diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index b7c85470..d4eb1329 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -26,7 +26,7 @@ use query_operations::{ generate_read_operations_tokens, generate_find_all_query_tokens, // generate_find_by_foreign_key_tokens, - // generate_find_by_pk_tokens, generate_find_by_reverse_foreign_key_tokens, + // generate_find_by_reverse_foreign_key_tokens, }, update::{generate_update_query_tokens, generate_update_tokens}, }; @@ -252,9 +252,6 @@ fn impl_crud_operations_trait_for_struct( let read_operations_tokens = generate_read_operations_tokens(macro_data, &table_schema_data); let find_all_query_tokens = generate_find_all_query_tokens(macro_data, &table_schema_data); - // Builds the find_by_pk() query - // let _find_by_pk_tokens = generate_find_by_pk_tokens(macro_data, &table_schema_data); - // Builds the insert() query let _insert_tokens = generate_insert_tokens(macro_data, &table_schema_data); // Builds the insert_multi() query diff --git a/canyon_macros/src/query_operations/doc_comments.rs b/canyon_macros/src/query_operations/doc_comments.rs index ff1b0679..fa88791b 100644 --- a/canyon_macros/src/query_operations/doc_comments.rs +++ b/canyon_macros/src/query_operations/doc_comments.rs @@ -11,4 +11,23 @@ pub const SELECT_QUERYBUILDER_DOC_COMMENT: &str = /// It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your \ /// entity but converted to the corresponding database convention, \ /// unless concrete values are set on the available parameters of the \ - /// `canyon_macro => table_name = \"table_name\", schema = \"schema\")`"; \ No newline at end of file + /// `canyon_macro => table_name = \"table_name\", schema = \"schema\")`"; + +pub const FIND_BY_PK: &str = + "/// Finds an element on the queried table that matches the \ + /// value of the field annotated with the `primary_key` attribute, \ + /// filtering by the column that it's declared as the primary \ + /// key on the database. \ + /// \ + /// *NOTE:* This operation it's only available if the [`CanyonEntity`] contains \ + /// some field declared as primary key. \ + /// \ + /// *returns:* a [`Result, Error>`], wrapping a possible failure \ + /// querying the database, or, if no errors happens, a success containing \ + /// and Option with the data found wrapped in the Some(T) variant, \ + /// or None if the value isn't found on the table."; + +pub const DS_ADVERTISING: &str = + "/// The query it's made against the database with the configured datasource \ + /// described in the configuration file, and selected with the [`&str`] \ + /// passed as parameter."; \ No newline at end of file diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index 449926ba..3db01ab0 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -97,6 +97,11 @@ impl MacroOperationBuilder { } } + pub fn with_lifetime(mut self) -> Self { + self.lifetime = true; + self + } + pub fn with_datasource_param(mut self) -> Self { self.datasource_param = Some(quote! { datasource_name: &'a str }); self.datasource_arg = Some(quote! { datasource_name }); diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index 9a26688a..acd75e2a 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -7,6 +7,7 @@ use crate::query_operations::macro_template::MacroOperationBuilder; use crate::utils::helpers::*; use crate::utils::macro_tokens::MacroTokens; +// The API for export to the real macro implementation the generated macros for the READ operations pub fn generate_read_operations_tokens( macro_data: &MacroTokens<'_>, table_schema_data: &String, @@ -18,7 +19,6 @@ pub fn generate_read_operations_tokens( // TODO: bring the helper and convert the SELECT * into the // SELECT col_name, col_name2...? // TODO: remember that this queries statements must be autogenerated by some automatic procedure - // and also, we could use the const_format crate let find_all = create_find_all_macro(ty, &fa_stmt); let find_all_datasource = create_find_all_ds_macro(ty, &fa_stmt); @@ -29,6 +29,8 @@ pub fn generate_read_operations_tokens( let count = create_count_macro(ty, &count_stmt); let count_datasource = create_count_ds_macro(ty, &count_stmt); + let find_by_pk_complex_tokens = generate_find_by_pk_tokens(macro_data, table_schema_data); + quote! { #find_all #find_all_datasource @@ -37,6 +39,8 @@ pub fn generate_read_operations_tokens( #count #count_datasource + + #find_by_pk_complex_tokens } } @@ -75,112 +79,65 @@ pub fn generate_find_all_query_tokens( } } -// /// Generates the TokenStream for build the __find_by_pk() CRUD operation -// pub fn generate_find_by_pk_tokens( -// macro_data: &MacroTokens<'_>, -// table_schema_data: &String, -// ) -> TokenStream { -// let ty = macro_data.ty; -// let pk = macro_data.get_primary_key_annotation().unwrap_or_default(); -// let stmt = format!("SELECT * FROM {table_schema_data} WHERE {pk} = $1"); - -// // Disabled if there's no `primary_key` annotation -// if pk.is_empty() { -// return quote! { -// async fn find_by_pk<'a>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) -// -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> -// { -// Err( -// std::io::Error::new( -// std::io::ErrorKind::Unsupported, -// "You can't use the 'find_by_pk' associated function on a \ -// CanyonEntity that does not have a #[primary_key] annotation. \ -// If you need to perform an specific search, use the Querybuilder instead." -// ).into_inner().unwrap() -// ) -// } - -// async fn find_by_pk_datasource<'a>( -// value: &'a dyn canyon_sql::core::QueryParameter<'a>, -// datasource_name: &'a str -// ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { -// Err( -// std::io::Error::new( -// std::io::ErrorKind::Unsupported, -// "You can't use the 'find_by_pk_datasource' associated function on a \ -// CanyonEntity that does not have a #[primary_key] annotation. \ -// If you need to perform an specific search, use the Querybuilder instead." -// ).into_inner().unwrap() -// ) -// } -// }; -// } +/// Generates the TokenStream for build the __find_by_pk() CRUD operation +fn generate_find_by_pk_tokens( + macro_data: &MacroTokens<'_>, + table_schema_data: &String, +) -> TokenStream { + use __details::pk_generators::*; -// let result_handling = quote! { -// match result { -// n if n.len() == 0 => Ok(None), -// _ => Ok( -// Some(result.into_results::<#ty>().remove(0)) -// ) -// } -// }; - -// quote! { -// /// Finds an element on the queried table that matches the -// /// value of the field annotated with the `primary_key` attribute, -// /// filtering by the column that it's declared as the primary -// /// key on the database. -// /// -// /// This operation it's only available if the [`CanyonEntity`] contains -// /// some field declared as primary key. -// /// -// /// Also, returns a [`Result, Error>`], wrapping a possible failure -// /// querying the database, or, if no errors happens, a success containing -// /// and Option with the data found wrapped in the Some(T) variant, -// /// or None if the value isn't found on the table. -// async fn find_by_pk<'a>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) -> -// Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> -// { -// let result = <#ty as canyon_sql::core::Transaction<#ty>>::query( -// #stmt, -// vec![value], -// "" -// ).await?; - -// #result_handling -// } + let ty = macro_data.ty; + let pk = macro_data.get_primary_key_annotation().unwrap_or_default(); + let stmt = format!("SELECT * FROM {table_schema_data} WHERE {pk} = $1"); + + // Disabled if there's no `primary_key` annotation + if pk.is_empty() { + return quote! { + async fn find_by_pk<'a>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) + -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + { + Err( + std::io::Error::new( + std::io::ErrorKind::Unsupported, + "You can't use the 'find_by_pk' associated function on a \ + CanyonEntity that does not have a #[primary_key] annotation. \ + If you need to perform an specific search, use the Querybuilder instead." + ).into_inner().unwrap() + ) + } -// /// Finds an element on the queried table that matches the -// /// value of the field annotated with the `primary_key` attribute, -// /// filtering by the column that it's declared as the primary -// /// key on the database. -// /// -// /// The query it's made against the database with the configured datasource -// /// described in the configuration file, and selected with the [`&str`] -// /// passed as parameter. -// /// -// /// This operation it's only available if the [`CanyonEntity`] contains -// /// some field declared as primary key. -// /// -// /// Also, returns a [`Result, Error>`], wrapping a possible failure -// /// querying the database, or, if no errors happens, a success containing -// /// and Option with the data found wrapped in the Some(T) variant, -// /// or None if the value isn't found on the table. -// async fn find_by_pk_datasource<'a>( -// value: &'a dyn canyon_sql::core::QueryParameter<'a>, -// datasource_name: &'a str -// ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> { - -// let result = <#ty as canyon_sql::core::Transaction<#ty>>::query( -// #stmt, -// vec![value], -// datasource_name -// ).await?; - -// #result_handling -// } -// } -// } + async fn find_by_pk_datasource<'a>( + value: &'a dyn canyon_sql::core::QueryParameter<'a>, + datasource_name: &'a str + ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> { + Err( + std::io::Error::new( + std::io::ErrorKind::Unsupported, + "You can't use the 'find_by_pk_datasource' associated function on a \ + CanyonEntity that does not have a #[primary_key] annotation. \ + If you need to perform an specific search, use the Querybuilder instead." + ).into_inner().unwrap() + ) + } + }; + } + + // TODO: this can be functionally handled, instead of this impl + let result_handling = quote! { + n if n.len() == 0 => Ok(None), + _ => Ok( + Some(transaction_result.into_results::<#ty>().remove(0)) + ) + }; + + let find_by_pk = create_find_by_pk_macro(ty, &stmt, &result_handling); + let find_by_pk_ds = create_find_by_pk_datasource(ty, &stmt, &result_handling); + + quote! { + #find_by_pk + #find_by_pk_ds + } +} // /// Generates the TokenStream for build the search by foreign key feature, also as a method instance // /// of a T type of as an associated function of same T type, but wrapped as a Result, representing @@ -363,7 +320,10 @@ pub fn generate_find_all_query_tokens( // } mod __details { - use crate::query_operations::macro_template::MacroOperationBuilder; + use crate::query_operations::{ + macro_template::MacroOperationBuilder, + doc_comments + }; use quote::quote; use proc_macro2::Span; use syn::Ident; @@ -486,23 +446,75 @@ mod __details { .raw_return() } } + + pub mod pk_generators { + use proc_macro2::TokenStream; + + use super::*; + + pub fn create_find_by_pk_macro(ty: &Ident, stmt: &str, result_handling: &TokenStream) -> MacroOperationBuilder { + MacroOperationBuilder::new() + .fn_name("find_by_pk") + .with_lifetime() + .user_type(ty) + .return_type(ty) + .add_doc_comment(doc_comments::FIND_BY_PK) + .add_doc_comment(doc_comments::DS_ADVERTISING) + .query_string(&stmt) + .input_parameters(quote!{ value: &'a dyn canyon_sql::core::QueryParameter<'a> }) + .forwarded_parameters(quote!{ vec![value] }) + .propagate_transaction_result() + .disable_mapping() + .single_result() + .transaction_as_variable(quote!{ + match transaction_result { // NOTE: dark magic. Should be refactored + #result_handling + } + }) + } + + pub fn create_find_by_pk_datasource(ty: &Ident, stmt: &str, result_handling: &TokenStream) -> MacroOperationBuilder { + MacroOperationBuilder::new() + .fn_name("find_by_pk_datasource") + .with_datasource_param() + .user_type(ty) + .return_type(ty) + .add_doc_comment(doc_comments::FIND_BY_PK) + .add_doc_comment(doc_comments::DS_ADVERTISING) + .query_string(&stmt) + .input_parameters(quote!{ value: &'a dyn canyon_sql::core::QueryParameter<'a> }) + .forwarded_parameters(quote!{ vec![value] }) + .propagate_transaction_result() + .disable_mapping() + .single_result() + .transaction_as_variable(quote!{ + match transaction_result { // NOTE: dark magic. Should be refactored + #result_handling + } + }) + } + } } #[cfg(test)] mod macro_builder_read_ops_tests { - use super::__details::{find_all_generators::*, count_generators::*}; + use super::__details::{find_all_generators::*, count_generators::*, pk_generators::*}; use proc_macro2::Span; use syn::Ident; + use quote::quote; - const SELECT_ALL_STMT: &str = "SELECT * FROM public.user"; + const SELECT_ALL_STMT: &str = "SELECT * FROM public.user"; // TODO: introduce the const_format crate const COUNT_STMT: &str = "SELECT COUNT(*) FROM public.user"; + const FIND_BY_PK_STMT: &str = "SELECT * FROM public.user WHERE id = $1"; const RAW_RET_TY: &str = "Vec < User >"; const RES_RET_TY: &str = "Result < Vec < User > , Box < (dyn std :: error :: Error + Send + Sync) > >"; const RES_RET_TY_LT: &str = "Result < Vec < User > , Box < (dyn std :: error :: Error + Send + Sync + 'a) > >"; + const OPT_RET_TY_LT: &str = "Result < Option < User > , Box < (dyn std :: error :: Error + Send + Sync + 'a) > >"; + const MAPS_TO: &str = "into_results :: < User > ()"; const LT_CONSTRAINT: &str = "< 'a >"; - const DS_PARAM: &str = "(datasource_name : & 'a str)"; + const DS_PARAM: &str = "datasource_name : & 'a str"; #[test] fn test_macro_builder_find_all() { @@ -563,7 +575,6 @@ mod macro_builder_read_ops_tests { let count = count_builder .generate_tokens() .to_string(); - println!("{:?}", count.split("\n").collect::>()); assert!(count.contains("async fn count")); assert!(count.contains("Result < i64")); @@ -582,4 +593,32 @@ mod macro_builder_read_ops_tests { assert!(count_ds.contains(LT_CONSTRAINT)); assert!(count_ds.contains(DS_PARAM)); } + + #[test] + fn test_macro_builder_find_by_pk() { + let ty: Ident = Ident::new("User", Span::call_site()); + let find_by_pk_builder = create_find_by_pk_macro(&ty, FIND_BY_PK_STMT, "e!{}); + let find_by_pk = find_by_pk_builder + .generate_tokens() + .to_string(); + + assert!(find_by_pk.contains("async fn find_by_pk")); + assert!(find_by_pk.contains(LT_CONSTRAINT)); + assert!(find_by_pk.contains(OPT_RET_TY_LT)); + } + + #[test] + fn test_macro_builder_find_by_pk_datasource() { + let ty: Ident = Ident::new("User", Span::call_site()); + let find_by_pk_ds_builder = create_find_by_pk_datasource(&ty, FIND_BY_PK_STMT, "e!{}); + let find_by_pk_ds = find_by_pk_ds_builder + .generate_tokens() + .to_string(); + println!("{:?}", find_by_pk_ds.split("\n").collect::>()); + + assert!(find_by_pk_ds.contains("async fn find_by_pk_datasource")); + assert!(find_by_pk_ds.contains(LT_CONSTRAINT)); + assert!(find_by_pk_ds.contains(DS_PARAM)); + assert!(find_by_pk_ds.contains(OPT_RET_TY_LT)); + } } \ No newline at end of file diff --git a/tests/crud/select_operations.rs b/tests/crud/select_operations.rs index 6176b6ad..154b0445 100644 --- a/tests/crud/select_operations.rs +++ b/tests/crud/select_operations.rs @@ -73,74 +73,74 @@ fn test_crud_find_all_unchecked_datasource() { assert!(!find_all_result.is_empty()); } -// /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -// /// defined with the #[primary_key] attribute over some field of the type. -// /// -// /// Uses the *default datasource*. -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_by_pk() { -// let find_by_pk_result: Result, Box> = -// League::find_by_pk(&1).await; -// assert!(find_by_pk_result.as_ref().unwrap().is_some()); - -// let some_league = find_by_pk_result.unwrap().unwrap(); -// assert_eq!(some_league.id, 1); -// assert_eq!(some_league.ext_id, 100695891328981122_i64); -// assert_eq!(some_league.slug, "european-masters"); -// assert_eq!(some_league.name, "European Masters"); -// assert_eq!(some_league.region, "EUROPE"); -// assert_eq!( -// some_league.image_url, -// "http://static.lolesports.com/leagues/EM_Bug_Outline1.png" -// ); -// } - -// /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -// /// defined with the #[primary_key] attribute over some field of the type. -// /// -// /// Uses the *specified datasource mssql* in the second parameter of the function call. -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_by_pk_datasource_mssql() { -// let find_by_pk_result: Result, Box> = -// League::find_by_pk_datasource(&27, SQL_SERVER_DS).await; -// assert!(find_by_pk_result.as_ref().unwrap().is_some()); - -// let some_league = find_by_pk_result.unwrap().unwrap(); -// assert_eq!(some_league.id, 27); -// assert_eq!(some_league.ext_id, 107898214974993351_i64); -// assert_eq!(some_league.slug, "college_championship"); -// assert_eq!(some_league.name, "College Championship"); -// assert_eq!(some_league.region, "NORTH AMERICA"); -// assert_eq!( -// some_league.image_url, -// "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" -// ); -// } - -// /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -// /// defined with the #[primary_key] attribute over some field of the type. -// /// -// /// Uses the *specified datasource mysql* in the second parameter of the function call. -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_by_pk_datasource_mysql() { -// let find_by_pk_result: Result, Box> = -// League::find_by_pk_datasource(&27, MYSQL_DS).await; -// assert!(find_by_pk_result.as_ref().unwrap().is_some()); - -// let some_league = find_by_pk_result.unwrap().unwrap(); -// assert_eq!(some_league.id, 27); -// assert_eq!(some_league.ext_id, 107898214974993351_i64); -// assert_eq!(some_league.slug, "college_championship"); -// assert_eq!(some_league.name, "College Championship"); -// assert_eq!(some_league.region, "NORTH AMERICA"); -// assert_eq!( -// some_league.image_url, -// "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" -// ); -// } +/// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +/// defined with the #[primary_key] attribute over some field of the type. +/// +/// Uses the *default datasource*. +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_by_pk() { + let find_by_pk_result: Result, Box> = + League::find_by_pk(&1).await; + assert!(find_by_pk_result.as_ref().unwrap().is_some()); + + let some_league = find_by_pk_result.unwrap().unwrap(); + assert_eq!(some_league.id, 1); + assert_eq!(some_league.ext_id, 100695891328981122_i64); + assert_eq!(some_league.slug, "european-masters"); + assert_eq!(some_league.name, "European Masters"); + assert_eq!(some_league.region, "EUROPE"); + assert_eq!( + some_league.image_url, + "http://static.lolesports.com/leagues/EM_Bug_Outline1.png" + ); +} + +/// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +/// defined with the #[primary_key] attribute over some field of the type. +/// +/// Uses the *specified datasource mssql* in the second parameter of the function call. +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_by_pk_datasource_mssql() { + let find_by_pk_result: Result, Box> = + League::find_by_pk_datasource(&27, SQL_SERVER_DS).await; + assert!(find_by_pk_result.as_ref().unwrap().is_some()); + + let some_league = find_by_pk_result.unwrap().unwrap(); + assert_eq!(some_league.id, 27); + assert_eq!(some_league.ext_id, 107898214974993351_i64); + assert_eq!(some_league.slug, "college_championship"); + assert_eq!(some_league.name, "College Championship"); + assert_eq!(some_league.region, "NORTH AMERICA"); + assert_eq!( + some_league.image_url, + "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" + ); +} + +/// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +/// defined with the #[primary_key] attribute over some field of the type. +/// +/// Uses the *specified datasource mysql* in the second parameter of the function call. +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_by_pk_datasource_mysql() { + let find_by_pk_result: Result, Box> = + League::find_by_pk_datasource(&27, MYSQL_DS).await; + assert!(find_by_pk_result.as_ref().unwrap().is_some()); + + let some_league = find_by_pk_result.unwrap().unwrap(); + assert_eq!(some_league.id, 27); + assert_eq!(some_league.ext_id, 107898214974993351_i64); + assert_eq!(some_league.slug, "college_championship"); + assert_eq!(some_league.name, "College Championship"); + assert_eq!(some_league.region, "NORTH AMERICA"); + assert_eq!( + some_league.image_url, + "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" + ); +} /// Counts how many rows contains an entity on the target database. #[cfg(feature = "postgres")] From 90a2e32ef0491a8d12cb19a5dceb0869137ec181 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 24 Jan 2025 17:23:34 +0100 Subject: [PATCH 040/155] feat(wip): replacing datasource as str for macro inputs for the more flexible input approach --- canyon_core/src/query.rs | 4 +- .../src/query_operations/macro_builder.rs | 230 ++++++++++++++++++ .../src/query_operations/macro_template.rs | 143 +++++++---- canyon_macros/src/query_operations/select.rs | 169 ++++++------- 4 files changed, 408 insertions(+), 138 deletions(-) create mode 100644 canyon_macros/src/query_operations/macro_builder.rs diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index 7001d045..91b11b04 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -51,7 +51,7 @@ pub trait Transaction { } TransactionInput::DatasourceConfig(ds) => { // TODO: add a new from_ds_config_mut for mssql - let conn = DatabaseConnection::new(&ds).await?; + let conn = DatabaseConnection::new(ds).await?; conn.launch(statement, query_parameters).await } TransactionInput::DatasourceName(ds_name) => { @@ -76,7 +76,7 @@ pub enum TransactionInput<'a> { DatasourceName(&'a str), } -impl<'a> From for TransactionInput<'a> { +impl From for TransactionInput<'_> { fn from(conn: DatabaseConnection) -> Self { TransactionInput::DbConnection(conn) } diff --git a/canyon_macros/src/query_operations/macro_builder.rs b/canyon_macros/src/query_operations/macro_builder.rs new file mode 100644 index 00000000..eb5e3fe2 --- /dev/null +++ b/canyon_macros/src/query_operations/macro_builder.rs @@ -0,0 +1,230 @@ +use quote::quote; +use syn::{Ident, Type}; + +/// Builder for constructing CRUD operation metadata +pub struct OperationBuilder { + operation_name: Option, + fn_name: Option, + datasource_param: Option, + datasource_arg: Option, + return_type: Option, + base_doc_comment: Option, + doc_comment: Option, + body_tokens: Option, + with_unwrap: bool, +} + +impl OperationBuilder { + /// Creates a new builder instance + pub fn new() -> Self { + Self { + operation_name: None, + fn_name: None, + datasource_param: None, + datasource_arg: None, + return_type: None, + base_doc_comment: None, + doc_comment: None, + body_tokens: None, + with_unwrap: false, + } + } + + /// Sets the name of the operation + pub fn operation_name(mut self, name: &str) -> Self { + self.operation_name = Some(syn::Ident::new(name, proc_macro2::Span::call_site())); + self + } + + /// Sets the function name + pub fn fn_name(mut self, name: &str) -> Self { + self.fn_name = Some(syn::Ident::new(name, proc_macro2::Span::call_site())); + self + } + + /// Sets the datasource parameter + pub fn datasource_param(mut self, param: &str) -> Self { + self.datasource_param = Some(syn::Ident::new(param, proc_macro2::Span::call_site())); + self + } + + /// Sets the datasource argument + pub fn datasource_arg(mut self, arg: &str) -> Self { + self.datasource_arg = Some(syn::Ident::new(arg, proc_macro2::Span::call_site())); + self + } + + /// Sets the return type + pub fn return_type(mut self, ty: Type) -> Self { + self.return_type = Some(ty); + self + } + + /// Adds a base doc comment + pub fn base_doc_comment(mut self, comment: &str) -> Self { + self.base_doc_comment = Some(comment.to_string()); + self + } + + /// Adds an additional doc comment + pub fn doc_comment(mut self, comment: &str) -> Self { + self.doc_comment = Some(comment.to_string()); + self + } + + /// Sets the body of the function + pub fn body_tokens(mut self, tokens: proc_macro2::TokenStream) -> Self { + self.body_tokens = Some(tokens); + self + } + + /// Configures whether to use `.unwrap()` + pub fn with_unwrap(mut self, unwrap: bool) -> Self { + self.with_unwrap = unwrap; + self + } + + /// Finalizes the builder and returns the operation + pub fn build(self) -> Operation { + Operation { + operation_name: self.operation_name.unwrap(), + fn_name: self.fn_name.unwrap(), + datasource_param: self.datasource_param.unwrap(), + datasource_arg: self.datasource_arg.unwrap(), + return_type: self.return_type.unwrap(), + base_doc_comment: self.base_doc_comment.unwrap(), + doc_comment: self.doc_comment.unwrap(), + body_tokens: self.body_tokens.unwrap(), + with_unwrap: self.with_unwrap, + } + } +} + +/// Represents a fully constructed CRUD operation +pub struct Operation { + pub operation_name: Ident, + pub fn_name: Ident, + pub datasource_param: Ident, + pub datasource_arg: Ident, + pub return_type: Type, + pub base_doc_comment: String, + pub doc_comment: String, + pub body_tokens: proc_macro2::TokenStream, + pub with_unwrap: bool, +} + +impl Operation { + /// Generates the final `quote!` tokens for this operation + pub fn generate_tokens(&self) -> proc_macro2::TokenStream { + let base_doc_comment = &self.base_doc_comment; + let doc_comment = &self.doc_comment; + let fn_name = &self.fn_name; + let datasource_param = &self.datasource_param; + let return_type = &self.return_type; + let body_tokens = &self.body_tokens; + let unwrap_tokens = if self.with_unwrap { + quote! { .unwrap() } + } else { + quote! {} + }; + + quote! { + #[doc = #base_doc_comment] + #[doc = #doc_comment] + async fn #fn_name(#datasource_param) -> #return_type { + #body_tokens + #unwrap_tokens + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; // Import your structs and builder + use quote::quote; + use syn::parse_quote; + + #[test] + fn test_find_operation_tokens() { + // Arrange: Build the operation + let find_operation = OperationBuilder::new() + .operation_name("find") + .fn_name("find_user_by_id") + .datasource_param(parse_quote!(datasource)) + .datasource_arg(quote! { datasource_arg }) + .return_type((Result)) + .base_doc_comment("Finds a user by their ID.") + .doc_comment("This operation retrieves a single user record based on the provided ID.") + .query_string("SELECT * FROM users WHERE id = ?") + .input_parameters(quote! { &[id] }) + .parameterized(true) + .with_unwrap(false) + .build(); + + // Act: Generate tokens + let generated_tokens = find_operation.generate_tokens(); + + // Assert: Compare against expected tokens + let expected_tokens = quote! { + #[doc = "Finds a user by their ID."] + #[doc = "This operation retrieves a single user record based on the provided ID."] + async fn find_user_by_id(datasource) -> Result { + >::query( + "SELECT * FROM users WHERE id = ?", + &[id], + datasource_arg + ).await + .into_results::() + } + }; + + assert_eq!( + generated_tokens.to_string(), + expected_tokens.to_string(), + "Generated tokens do not match expected tokens!" + ); + } + + #[test] + fn test_insert_operation_tokens() { + // Arrange: Build the operation + let insert_operation = OperationBuilder::new() + .operation_name("insert") + .fn_name("insert_user") + .datasource_param(parse_quote!(datasource)) + .datasource_arg(quote! { datasource_arg }) + .return_type(parse_quote!(Result<(), Error>)) + .base_doc_comment("Inserts a new user into the database.") + .doc_comment("This operation inserts a new user record with the provided data.") + .query_string("INSERT INTO users (name, email) VALUES (?, ?)") + .input_parameters(quote! { &dyn QueryParameters }) + .parameterized(true) + .with_unwrap(false) + .build(); + + // Act: Generate tokens + let generated_tokens = insert_operation.generate_tokens(); + + // Assert: Compare against expected tokens + let expected_tokens = quote! { + #[doc = "Inserts a new user into the database."] + #[doc = "This operation inserts a new user record with the provided data."] + async fn insert_user(datasource) -> Result<(), Error> { + >::query( + "INSERT INTO users (name, email) VALUES (?, ?)", + &dyn QueryParameters, + datasource_arg + ).await + .into_results::() + } + }; + + assert_eq!( + generated_tokens.to_string(), + expected_tokens.to_string(), + "Generated tokens do not match expected tokens!" + ); + } +} + diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index 3db01ab0..0c24ea4c 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -1,14 +1,15 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; -use syn::{Type, parse_quote}; +use syn::{parse_quote, Type}; pub struct MacroOperationBuilder { fn_name: Option, user_type: Option, lifetime: bool, // bool true always will generate <'a> - datasource_param: Option, - datasource_arg: Option, + input_param: Option, + input_fwd_arg: Option, return_type: Option, + where_clause_bounds: Vec, doc_comments: Vec, body_tokens: Option, query_string: Option, @@ -35,9 +36,10 @@ impl MacroOperationBuilder { fn_name: None, user_type: None, lifetime: false, - datasource_param: None, - datasource_arg: None, + input_param: None, + input_fwd_arg: None, return_type: None, + where_clause_bounds: Vec::new(), doc_comments: Vec::new(), body_tokens: None, query_string: None, @@ -55,7 +57,7 @@ impl MacroOperationBuilder { fn get_fn_name(&self) -> TokenStream { if let Some(fn_name) = &self.fn_name { - quote!{ #fn_name } + quote! { #fn_name } } else { panic!("No function name provided") } @@ -68,7 +70,7 @@ impl MacroOperationBuilder { fn get_user_type(&self) -> TokenStream { if let Some(user_type) = &self.user_type { - quote!{ #user_type } + quote! { #user_type } } else { panic!("No T type provided for determining the operations implementor") } @@ -80,17 +82,21 @@ impl MacroOperationBuilder { } fn get_lifetime(&self) -> TokenStream { - if self.lifetime { quote!{ <'a> } } else { quote!{} } + if self.lifetime { + quote! { <'a> } + } else { + quote! {} + } } - fn get_datasource_param(&self) -> TokenStream { - let ds_param = &self.datasource_param; - quote! { #ds_param } + fn get_input_param(&self) -> TokenStream { + let input_param = &self.input_param; + quote! { #input_param } } - fn get_datasource_arg(&self) -> TokenStream { - if let Some(ds_arg) = &self.datasource_arg { - let ds_arg0 = ds_arg; + fn get_input_arg(&self) -> TokenStream { + if let Some(input_arg) = &self.input_fwd_arg { + let ds_arg0 = input_arg; quote! { #ds_arg0 } } else { quote! { "" } @@ -102,10 +108,12 @@ impl MacroOperationBuilder { self } - pub fn with_datasource_param(mut self) -> Self { - self.datasource_param = Some(quote! { datasource_name: &'a str }); - self.datasource_arg = Some(quote! { datasource_name }); + pub fn with_input_param(mut self) -> Self { + self.input_param = Some(quote! { input: I }); + self.input_fwd_arg = Some(quote! { input }); self.lifetime = true; + self.where_clause_bounds + .push(quote! { I: Into> + Sync + Send + 'a }); self } @@ -113,7 +121,9 @@ impl MacroOperationBuilder { let organic_ret_type = &self.return_type; let container_ret_type = if self.single_result { quote! { Option } - } else { quote! { Vec } }; + } else { + quote! { Vec } + }; let ret_type = if self.raw_return { quote! { #organic_ret_type } @@ -121,7 +131,8 @@ impl MacroOperationBuilder { quote! { #container_ret_type<#organic_ret_type> } }; - match &self.with_unwrap { // TODO: distinguish collection from 1 results + match &self.with_unwrap { + // TODO: distinguish collection from 1 results true => quote! { #ret_type }, false => { let err_variant = if self.lifetime { @@ -135,6 +146,17 @@ impl MacroOperationBuilder { } } + fn get_where_clause_bounds(&self) -> TokenStream { + if self.where_clause_bounds.is_empty() { + quote! {} + } else { + let where_bounds = &self.where_clause_bounds; + quote! { + where #(#where_bounds),* + } + } + } + pub fn return_type(mut self, return_type: &Ident) -> Self { self.return_type = Some(return_type.clone()); self @@ -170,15 +192,17 @@ impl MacroOperationBuilder { self } - pub fn get_forwarded_parameters(&self) -> TokenStream { + fn get_forwarded_parameters(&self) -> TokenStream { let forwarded_parameters = &self.forwarded_parameters; if let Some(fwd_params) = &self.forwarded_parameters { quote! { #forwarded_parameters } - } else { quote!{ &[] } } + } else { + quote! { &[] } + } } - pub fn get_unwrap(&self) -> TokenStream { + fn get_unwrap(&self) -> TokenStream { if self.with_unwrap { quote! { .unwrap() } } else { @@ -214,34 +238,40 @@ impl MacroOperationBuilder { /// Generates the final `quote!` tokens for this operation pub fn generate_tokens(&self) -> proc_macro2::TokenStream { - let doc_comments = &self.doc_comments + let doc_comments = &self + .doc_comments .iter() .map(|doc_comment| quote! { #[doc = #doc_comment] }) .collect::>(); - + let ty = self.get_user_type(); let fn_name = self.get_fn_name(); let lifetime = self.get_lifetime(); // TODO: generics instead - let datasource_param = self.get_datasource_param(); - let datasource_name = self.get_datasource_arg(); + let input_param = self.get_input_param(); + let input_fwd_arg = self.get_input_arg(); // TODO: replace let fn_parameters = self.get_fn_parameters(); let query_string = &self.query_string; let forwarded_parameters = self.get_forwarded_parameters(); let return_type = self.get_return_type(); - + let where_clause = self.get_where_clause_bounds(); let unwrap = self.get_unwrap(); let mut base_body_tokens = quote! { <#ty as canyon_sql::core::Transaction<#ty>>::query( #query_string, #forwarded_parameters, - #datasource_name + #input_fwd_arg ).await }; - if self.propagate_transaction_result { base_body_tokens.extend(quote! { ? }) }; - if !self.disable_mapping { base_body_tokens.extend(quote! { .into_results::<#ty>() }) }; + + if self.propagate_transaction_result { + base_body_tokens.extend(quote! { ? }) + }; + if !self.disable_mapping { + base_body_tokens.extend(quote! { .into_results::<#ty>() }) + }; let body_tokens = if self.transaction_as_variable { let result_handling = &self.post_body; @@ -249,15 +279,25 @@ impl MacroOperationBuilder { let transaction_result = #base_body_tokens; #result_handling } - } else { base_body_tokens }; + } else { + base_body_tokens + }; - let separate_params = if self.input_parameters.is_some() && self.datasource_param.is_some() { + let separate_params = if self.input_parameters.is_some() && self.input_param.is_some() // TODO: + // change + // for + // getter? + { quote! {, } - } else { quote! {} }; + } else { + quote! {} + }; quote! { #(#doc_comments)* - async fn #fn_name #lifetime(#fn_parameters #separate_params #datasource_param) -> #return_type { + async fn #fn_name #lifetime(#fn_parameters #separate_params #input_param) -> #return_type + #where_clause + { #body_tokens #unwrap } @@ -270,7 +310,7 @@ mod tests { use super::*; use quote::quote; use syn::parse_quote; - + #[test] fn test_find_operation_tokens() { let user_type = Ident::new("User", Span::call_site()); @@ -278,13 +318,15 @@ mod tests { let find_operation = MacroOperationBuilder::new() .fn_name("find_user_by_id") .user_type(&user_type) - .with_datasource_param() + .with_input_param() .return_type(&user_type) .add_doc_comment("Finds a user by their ID.") - .add_doc_comment("This operation retrieves a single user record based on the provided ID.") + .add_doc_comment( + "This operation retrieves a single user record based on the provided ID.", + ) .query_string("SELECT * FROM users WHERE id = ?") .input_parameters(quote! { id: &dyn QueryParameters<'_> }) - .forwarded_parameters(quote!{ &[id] }) + .forwarded_parameters(quote! { &[id] }) .single_result(); let generated_tokens = find_operation.generate_tokens(); @@ -301,10 +343,7 @@ mod tests { } }; - assert_eq!( - generated_tokens.to_string(), - expected_tokens.to_string() - ); + assert_eq!(generated_tokens.to_string(), expected_tokens.to_string()); } #[test] @@ -316,7 +355,9 @@ mod tests { .user_type(&user_type) .return_type(&user_type) .add_doc_comment("Executes a 'SELECT * FROM '") - .add_doc_comment("This operation retrieves all the users records stored with the default datasource") + .add_doc_comment( + "This operation retrieves all the users records stored with the default datasource", + ) .query_string("SELECT * FROM users"); let generated_tokens = find_operation.generate_tokens(); @@ -333,10 +374,7 @@ mod tests { } }; - assert_eq!( - generated_tokens.to_string(), - expected_tokens.to_string() - ); + assert_eq!(generated_tokens.to_string(), expected_tokens.to_string()); } #[test] @@ -346,10 +384,12 @@ mod tests { let find_operation = MacroOperationBuilder::new() .fn_name("find_all_datasource") .user_type(&user_type) - .with_datasource_param() + .with_input_param() .return_type(&user_type) .add_doc_comment("Executes a 'SELECT * FROM '") - .add_doc_comment("This operation retrieves all the users records stored in the provided datasource") + .add_doc_comment( + "This operation retrieves all the users records stored in the provided datasource", + ) .query_string("SELECT * FROM users"); let generated_tokens = find_operation.generate_tokens(); @@ -366,10 +406,7 @@ mod tests { } }; - assert_eq!( - generated_tokens.to_string(), - expected_tokens.to_string() - ); + assert_eq!(generated_tokens.to_string(), expected_tokens.to_string()); } // #[test] diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index acd75e2a..2fc8e6bf 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -12,7 +12,7 @@ pub fn generate_read_operations_tokens( macro_data: &MacroTokens<'_>, table_schema_data: &String, ) -> TokenStream { - use __details::{find_all_generators::*, count_generators::*}; + use __details::{count_generators::*, find_all_generators::*}; let ty = macro_data.ty; let fa_stmt = format!("SELECT * FROM {table_schema_data}"); @@ -21,10 +21,10 @@ pub fn generate_read_operations_tokens( // TODO: remember that this queries statements must be autogenerated by some automatic procedure let find_all = create_find_all_macro(ty, &fa_stmt); - let find_all_datasource = create_find_all_ds_macro(ty, &fa_stmt); + let find_all_datasource = create_find_all_with_macro(ty, &fa_stmt); let find_all_unchecked = create_find_all_unchecked_macro(ty, &fa_stmt); let find_all_unchecked_datasource = create_find_all_unchecked_ds_macro(ty, &fa_stmt); - + let count_stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); let count = create_count_macro(ty, &count_stmt); let count_datasource = create_count_ds_macro(ty, &count_stmt); @@ -122,7 +122,7 @@ fn generate_find_by_pk_tokens( }; } - // TODO: this can be functionally handled, instead of this impl + // TODO: this can be functionally handled, instead of this impl let result_handling = quote! { n if n.len() == 0 => Ok(None), _ => Ok( @@ -320,12 +320,9 @@ fn generate_find_by_pk_tokens( // } mod __details { - use crate::query_operations::{ - macro_template::MacroOperationBuilder, - doc_comments - }; - use quote::quote; + use crate::query_operations::{doc_comments, macro_template::MacroOperationBuilder}; use proc_macro2::Span; + use quote::quote; use syn::Ident; pub mod find_all_generators { @@ -338,32 +335,38 @@ mod __details { .return_type(ty) .add_doc_comment("Executes a 'SELECT * FROM '") .add_doc_comment("This operation retrieves all the users records stored with the default datasource") - .query_string(&stmt) + .query_string(stmt) } - - pub fn create_find_all_ds_macro(ty: &syn::Ident, stmt: &str) -> MacroOperationBuilder { + + pub fn create_find_all_with_macro(ty: &syn::Ident, stmt: &str) -> MacroOperationBuilder { MacroOperationBuilder::new() - .fn_name("find_all_datasource") + .fn_name("find_all_with") + .with_input_param() .user_type(ty) .return_type(ty) - .with_datasource_param() .add_doc_comment("Executes a 'SELECT * FROM '") .add_doc_comment("This operation retrieves all the users records stored with the provided datasource") - .query_string(&stmt) + .query_string(stmt) } - - pub fn create_find_all_unchecked_macro(ty: &syn::Ident, stmt: &str) -> MacroOperationBuilder { + + pub fn create_find_all_unchecked_macro( + ty: &syn::Ident, + stmt: &str, + ) -> MacroOperationBuilder { MacroOperationBuilder::new() .fn_name("find_all_unchecked") .user_type(ty) .return_type(ty) .add_doc_comment("Executes a 'SELECT * FROM '") .add_doc_comment("This operation retrieves all the users records stored with the provided datasource") - .query_string(&stmt) + .query_string(stmt) .with_unwrap() } - - pub fn create_find_all_unchecked_ds_macro(ty: &syn::Ident, stmt: &str) -> MacroOperationBuilder { + + pub fn create_find_all_unchecked_ds_macro( + ty: &syn::Ident, + stmt: &str, + ) -> MacroOperationBuilder { MacroOperationBuilder::new() .fn_name("find_all_unchecked_datasource") .user_type(ty) @@ -371,7 +374,7 @@ mod __details { .with_datasource_param() .add_doc_comment("Executes a 'SELECT * FROM '") .add_doc_comment("This operation retrieves all the users records stored with the provided datasource") - .query_string(&stmt) + .query_string(stmt) .with_unwrap() } } @@ -387,20 +390,20 @@ mod __details { quote! { #[cfg(feature="postgres")] canyon_sql::core::CanyonRows::Postgres(mut v) => Ok( - v.remove(0).get::<&str, i64>("count") - ), + v.remove(0).get::<&str, i64>("count") + ), #[cfg(feature="mssql")] canyon_sql::core::CanyonRows::Tiberius(mut v) => - v.remove(0) - .get::(0) - .map(|c| c as i64) - .ok_or(format!("Failure in the COUNT query for MSSQL for: {}", #ty_str).into()) - .into(), + v.remove(0) + .get::(0) + .map(|c| c as i64) + .ok_or(format!("Failure in the COUNT query for MSSQL for: {}", #ty_str).into()) + .into(), #[cfg(feature="mysql")] canyon_sql::core::CanyonRows::MySQL(mut v) => v.remove(0) - .get::(0) - .ok_or(format!("Failure in the COUNT query for MYSQL for: {}", #ty_str).into()), - + .get::(0) + .ok_or(format!("Failure in the COUNT query for MYSQL for: {}", #ty_str).into()), + _ => panic!() // TODO remove when the generics will be refactored } } @@ -412,10 +415,12 @@ mod __details { .fn_name("count") .user_type(ty) .return_type(&syn::Ident::new("i64", Span::call_site())) // TODO: into ident or take by value - .add_doc_comment("Performs a COUNT(*) query over the table related to the entity T'") + .add_doc_comment( + "Performs a COUNT(*) query over the table related to the entity T'", + ) .add_doc_comment("Executed with the default datasource") - .query_string(&stmt) - .transaction_as_variable(quote!{ + .query_string(stmt) + .transaction_as_variable(quote! { match transaction_result { // NOTE: dark magic. Should be refactored #result_handling } @@ -433,10 +438,12 @@ mod __details { .user_type(ty) .with_datasource_param() .return_type(&syn::Ident::new("i64", Span::call_site())) // TODO: into ident or take by value - .add_doc_comment("Performs a COUNT(*) query over the table related to the entity T'") + .add_doc_comment( + "Performs a COUNT(*) query over the table related to the entity T'", + ) .add_doc_comment("It will be executed with the specified datasource") - .query_string(&stmt) - .transaction_as_variable(quote!{ + .query_string(stmt) + .transaction_as_variable(quote! { match transaction_result { #result_handling } @@ -452,7 +459,11 @@ mod __details { use super::*; - pub fn create_find_by_pk_macro(ty: &Ident, stmt: &str, result_handling: &TokenStream) -> MacroOperationBuilder { + pub fn create_find_by_pk_macro( + ty: &Ident, + stmt: &str, + result_handling: &TokenStream, + ) -> MacroOperationBuilder { MacroOperationBuilder::new() .fn_name("find_by_pk") .with_lifetime() @@ -460,20 +471,24 @@ mod __details { .return_type(ty) .add_doc_comment(doc_comments::FIND_BY_PK) .add_doc_comment(doc_comments::DS_ADVERTISING) - .query_string(&stmt) - .input_parameters(quote!{ value: &'a dyn canyon_sql::core::QueryParameter<'a> }) - .forwarded_parameters(quote!{ vec![value] }) + .query_string(stmt) + .input_parameters(quote! { value: &'a dyn canyon_sql::core::QueryParameter<'a> }) + .forwarded_parameters(quote! { vec![value] }) .propagate_transaction_result() .disable_mapping() .single_result() - .transaction_as_variable(quote!{ + .transaction_as_variable(quote! { match transaction_result { // NOTE: dark magic. Should be refactored #result_handling } }) } - pub fn create_find_by_pk_datasource(ty: &Ident, stmt: &str, result_handling: &TokenStream) -> MacroOperationBuilder { + pub fn create_find_by_pk_datasource( + ty: &Ident, + stmt: &str, + result_handling: &TokenStream, + ) -> MacroOperationBuilder { MacroOperationBuilder::new() .fn_name("find_by_pk_datasource") .with_datasource_param() @@ -481,13 +496,13 @@ mod __details { .return_type(ty) .add_doc_comment(doc_comments::FIND_BY_PK) .add_doc_comment(doc_comments::DS_ADVERTISING) - .query_string(&stmt) - .input_parameters(quote!{ value: &'a dyn canyon_sql::core::QueryParameter<'a> }) - .forwarded_parameters(quote!{ vec![value] }) + .query_string(stmt) + .input_parameters(quote! { value: &'a dyn canyon_sql::core::QueryParameter<'a> }) + .forwarded_parameters(quote! { vec![value] }) .propagate_transaction_result() .disable_mapping() .single_result() - .transaction_as_variable(quote!{ + .transaction_as_variable(quote! { match transaction_result { // NOTE: dark magic. Should be refactored #result_handling } @@ -498,20 +513,23 @@ mod __details { #[cfg(test)] mod macro_builder_read_ops_tests { - use super::__details::{find_all_generators::*, count_generators::*, pk_generators::*}; + use super::__details::{count_generators::*, find_all_generators::*, pk_generators::*}; use proc_macro2::Span; - use syn::Ident; use quote::quote; + use syn::Ident; const SELECT_ALL_STMT: &str = "SELECT * FROM public.user"; // TODO: introduce the const_format crate const COUNT_STMT: &str = "SELECT COUNT(*) FROM public.user"; const FIND_BY_PK_STMT: &str = "SELECT * FROM public.user WHERE id = $1"; const RAW_RET_TY: &str = "Vec < User >"; - const RES_RET_TY: &str = "Result < Vec < User > , Box < (dyn std :: error :: Error + Send + Sync) > >"; - const RES_RET_TY_LT: &str = "Result < Vec < User > , Box < (dyn std :: error :: Error + Send + Sync + 'a) > >"; - const OPT_RET_TY_LT: &str = "Result < Option < User > , Box < (dyn std :: error :: Error + Send + Sync + 'a) > >"; - + const RES_RET_TY: &str = + "Result < Vec < User > , Box < (dyn std :: error :: Error + Send + Sync) > >"; + const RES_RET_TY_LT: &str = + "Result < Vec < User > , Box < (dyn std :: error :: Error + Send + Sync + 'a) > >"; + const OPT_RET_TY_LT: &str = + "Result < Option < User > , Box < (dyn std :: error :: Error + Send + Sync + 'a) > >"; + const MAPS_TO: &str = "into_results :: < User > ()"; const LT_CONSTRAINT: &str = "< 'a >"; const DS_PARAM: &str = "datasource_name : & 'a str"; @@ -520,23 +538,19 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_find_all() { let ty: Ident = Ident::new("User", Span::call_site()); let find_all_builder = create_find_all_macro(&ty, SELECT_ALL_STMT); - let find_all = find_all_builder - .generate_tokens() - .to_string(); + let find_all = find_all_builder.generate_tokens().to_string(); assert!(find_all.contains("async fn find_all")); assert!(find_all.contains(RES_RET_TY)); } #[test] - fn test_macro_builder_find_all_datasource() { + fn test_macro_builder_find_all_with() { let ty: Ident = Ident::new("User", Span::call_site()); - let find_all_builder = create_find_all_ds_macro(&ty, SELECT_ALL_STMT); - let find_all_ds = find_all_builder - .generate_tokens() - .to_string(); + let find_all_builder = create_find_all_with_macro(&ty, SELECT_ALL_STMT); + let find_all_ds = find_all_builder.generate_tokens().to_string(); - assert!(find_all_ds.contains("async fn find_all_datasource")); + assert!(find_all_ds.contains("async fn find_all_with")); assert!(find_all_ds.contains(RES_RET_TY_LT)); assert!(find_all_ds.contains(LT_CONSTRAINT)); assert!(find_all_ds.contains(DS_PARAM)); @@ -546,9 +560,7 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_find_all_unchecked() { let ty: Ident = Ident::new("User", Span::call_site()); let find_all_unc_builder = create_find_all_unchecked_macro(&ty, SELECT_ALL_STMT); - let find_all_unc = find_all_unc_builder - .generate_tokens() - .to_string(); + let find_all_unc = find_all_unc_builder.generate_tokens().to_string(); assert!(find_all_unc.contains("async fn find_all_unchecked")); assert!(find_all_unc.contains(RAW_RET_TY)); @@ -558,9 +570,7 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_find_all_unchecked_datasource() { let ty: Ident = Ident::new("User", Span::call_site()); let find_all_unc_ds_builder = create_find_all_unchecked_ds_macro(&ty, SELECT_ALL_STMT); - let find_all_unc_ds = find_all_unc_ds_builder - .generate_tokens() - .to_string(); + let find_all_unc_ds = find_all_unc_ds_builder.generate_tokens().to_string(); assert!(find_all_unc_ds.contains("async fn find_all_unchecked_datasource")); assert!(find_all_unc_ds.contains(RAW_RET_TY)); @@ -572,9 +582,7 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_count() { let ty: Ident = Ident::new("User", Span::call_site()); let count_builder = create_count_macro(&ty, COUNT_STMT); - let count = count_builder - .generate_tokens() - .to_string(); + let count = count_builder.generate_tokens().to_string(); assert!(count.contains("async fn count")); assert!(count.contains("Result < i64")); @@ -584,9 +592,7 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_count_datasource() { let ty: Ident = Ident::new("User", Span::call_site()); let count_ds_builder = create_count_ds_macro(&ty, COUNT_STMT); - let count_ds = count_ds_builder - .generate_tokens() - .to_string(); + let count_ds = count_ds_builder.generate_tokens().to_string(); assert!(count_ds.contains("async fn count_datasource")); assert!(count_ds.contains("Result < i64")); @@ -597,10 +603,8 @@ mod macro_builder_read_ops_tests { #[test] fn test_macro_builder_find_by_pk() { let ty: Ident = Ident::new("User", Span::call_site()); - let find_by_pk_builder = create_find_by_pk_macro(&ty, FIND_BY_PK_STMT, "e!{}); - let find_by_pk = find_by_pk_builder - .generate_tokens() - .to_string(); + let find_by_pk_builder = create_find_by_pk_macro(&ty, FIND_BY_PK_STMT, "e! {}); + let find_by_pk = find_by_pk_builder.generate_tokens().to_string(); assert!(find_by_pk.contains("async fn find_by_pk")); assert!(find_by_pk.contains(LT_CONSTRAINT)); @@ -610,10 +614,8 @@ mod macro_builder_read_ops_tests { #[test] fn test_macro_builder_find_by_pk_datasource() { let ty: Ident = Ident::new("User", Span::call_site()); - let find_by_pk_ds_builder = create_find_by_pk_datasource(&ty, FIND_BY_PK_STMT, "e!{}); - let find_by_pk_ds = find_by_pk_ds_builder - .generate_tokens() - .to_string(); + let find_by_pk_ds_builder = create_find_by_pk_datasource(&ty, FIND_BY_PK_STMT, "e! {}); + let find_by_pk_ds = find_by_pk_ds_builder.generate_tokens().to_string(); println!("{:?}", find_by_pk_ds.split("\n").collect::>()); assert!(find_by_pk_ds.contains("async fn find_by_pk_datasource")); @@ -621,4 +623,5 @@ mod macro_builder_read_ops_tests { assert!(find_by_pk_ds.contains(DS_PARAM)); assert!(find_by_pk_ds.contains(OPT_RET_TY_LT)); } -} \ No newline at end of file +} + From af44ab1ba0625f43106c188a278e7f46b56932b4 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sat, 25 Jan 2025 11:23:06 +0100 Subject: [PATCH 041/155] feat(wip): allowing Transaction to receive the I: Into> as &'_I --- canyon_core/src/query.rs | 5 +- canyon_crud/src/crud.rs | 39 +++--- canyon_crud/src/query_elements/operators.rs | 2 +- canyon_crud/src/query_elements/query.rs | 19 ++- .../src/query_elements/query_builder.rs | 118 +++++++++++------- canyon_macros/src/query_operations/delete.rs | 6 +- canyon_macros/src/query_operations/insert.rs | 2 +- .../src/query_operations/macro_template.rs | 12 +- canyon_macros/src/query_operations/select.rs | 93 +++++++------- canyon_macros/src/query_operations/update.rs | 6 +- tests/crud/delete_operations.rs | 22 ++-- tests/crud/foreign_key_operations.rs | 24 ++-- tests/crud/init_mssql.rs | 2 +- tests/crud/insert_operations.rs | 40 +++--- tests/crud/querybuilder_operations.rs | 54 ++++---- tests/crud/select_operations.rs | 34 ++--- tests/crud/update_operations.rs | 20 +-- 17 files changed, 264 insertions(+), 234 deletions(-) diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index 91b11b04..9ecda910 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -27,15 +27,16 @@ pub trait Transaction { fn query<'a, S, Z, I>( stmt: S, params: Z, - input: I, + input: &'a I, ) -> impl Future>> + Send where S: AsRef + Display + Sync + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, I: Into> + Sync + Send + 'a, + TransactionInput<'a>: From<&'a I>, { async move { - let transaction_input = input.into(); + let transaction_input: TransactionInput<'a> = TransactionInput::from(input); let statement = stmt.as_ref(); let query_parameters = params.as_ref(); diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 1324b470..a10a29a6 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; use canyon_core::query_parameters::QueryParameter; use canyon_core::{mapper::RowMapper, query::Transaction}; - +use canyon_core::query::TransactionInput; use crate::query_elements::query_builder::{ DeleteQueryBuilder, SelectQueryBuilder, UpdateQueryBuilder, }; @@ -28,36 +28,39 @@ where { async fn find_all() -> Result, Box<(dyn std::error::Error + Send + Sync)>>; - async fn find_all_datasource<'a>( - datasource_name: &'a str, - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>>; + async fn find_all_with<'a, I>(input: I) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + where I: Into> + Sync + Send + 'a; async fn find_all_unchecked() -> Vec; - async fn find_all_unchecked_datasource<'a>(datasource_name: &'a str) -> Vec; + async fn find_all_unchecked_with<'a, I>(input: I) -> Vec + where I: Into> + Sync + Send + 'a; - fn select_query<'a>() -> SelectQueryBuilder<'a, T>; + fn select_query<'a, I>() -> SelectQueryBuilder<'a, T, I> where I: Into> + Sync + Send + 'a, TransactionInput<'a>: From<&'a I>,; - fn select_query_datasource(datasource_name: &str) -> SelectQueryBuilder<'_, T>; + fn select_query_with<'a, I>(input: I) -> SelectQueryBuilder<'a, T, I> + where I: Into> + Sync + Send + 'a, + TransactionInput<'a>: From<&'a I>,; async fn count() -> Result>; - async fn count_datasource<'a>( - datasource_name: &'a str, - ) -> Result>; + async fn count_with<'a, I>( + input: I, + ) -> Result> + where I: Into> + Sync + Send + 'a; async fn find_by_pk<'a>( value: &'a dyn QueryParameter<'a>, ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>>; - async fn find_by_pk_datasource<'a>( + async fn find_by_pk_with<'a, I>( value: &'a dyn QueryParameter<'a>, - datasource_name: &'a str, + input: I, ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>>; // async fn insert<'a>(&mut self) -> Result<(), Box>; - // async fn insert_datasource<'a>( + // async fn insert_with<'a>( // &mut self, // datasource_name: &'a str, // ) -> Result<(), Box>; @@ -66,30 +69,30 @@ where // instances: &'a mut [&'a mut T], // ) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'a)>>; - // async fn multi_insert_datasource<'a>( + // async fn multi_insert_with<'a>( // instances: &'a mut [&'a mut T], // datasource_name: &'a str, // ) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'a)>>; // async fn update(&self) -> Result<(), Box>; - // async fn update_datasource<'a>( + // async fn update_with<'a>( // &self, // datasource_name: &'a str, // ) -> Result<(), Box>; // fn update_query<'a>() -> UpdateQueryBuilder<'a, T>; - // fn update_query_datasource(datasource_name: &str) -> UpdateQueryBuilder<'_, T>; + // fn update_query_with(datasource_name: &str) -> UpdateQueryBuilder<'_, T>; // async fn delete(&self) -> Result<(), Box>; - // async fn delete_datasource<'a>( + // async fn delete_with<'a>( // &self, // datasource_name: &'a str, // ) -> Result<(), Box>; // fn delete_query<'a>() -> DeleteQueryBuilder<'a, T>; - // fn delete_query_datasource(datasource_name: &str) -> DeleteQueryBuilder<'_, T>; + // fn delete_query_with(datasource_name: &str) -> DeleteQueryBuilder<'_, T>; } diff --git a/canyon_crud/src/query_elements/operators.rs b/canyon_crud/src/query_elements/operators.rs index 7a613630..17b5c58d 100644 --- a/canyon_crud/src/query_elements/operators.rs +++ b/canyon_crud/src/query_elements/operators.rs @@ -22,7 +22,7 @@ pub enum Comp { } impl Operator for Comp { - fn as_str(&self, placeholder_counter: usize, _datasource_type: &DatabaseType) -> String { + fn as_str(&self, placeholder_counter: usize, _with_type: &DatabaseType) -> String { match *self { Self::Eq => format!(" = ${placeholder_counter}"), Self::Neq => format!(" <> ${placeholder_counter}"), diff --git a/canyon_crud/src/query_elements/query.rs b/canyon_crud/src/query_elements/query.rs index dc66e178..e27cb9b2 100644 --- a/canyon_crud/src/query_elements/query.rs +++ b/canyon_crud/src/query_elements/query.rs @@ -1,25 +1,20 @@ -use std::{fmt::Debug, marker::PhantomData}; +use std::fmt::Debug; -use crate::crud::CrudOperations; -use canyon_core::{mapper::RowMapper, query::Transaction, query_parameters::QueryParameter}; +use canyon_core::query_parameters::QueryParameter; /// Holds a sql sentence details #[derive(Debug, Clone)] -pub struct Query<'a, T: CrudOperations + Transaction + RowMapper> { +pub struct Query<'a> +{ pub sql: String, pub params: Vec<&'a dyn QueryParameter<'a>>, - marker: PhantomData, } -impl<'a, T> Query<'a, T> -where - T: CrudOperations + Transaction + RowMapper, -{ - pub fn new(sql: String) -> Query<'a, T> { +impl<'a> Query<'a> { + pub fn new(sql: String) -> Query<'a> { Self { sql, - params: vec![], - marker: PhantomData, + params: vec![] } } } diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index ce45e5e3..e2f0c1ac 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -1,8 +1,8 @@ use std::fmt::Debug; - +use std::marker::PhantomData; use canyon_core::connection::{database_type::DatabaseType, get_database_config, DATASOURCES}; use canyon_core::{mapper::RowMapper, query::Transaction, query_parameters::QueryParameter}; - +use canyon_core::query::TransactionInput; use crate::{ bounds::{FieldIdentifier, FieldValueIdentifier}, crud::CrudOperations, @@ -135,37 +135,46 @@ pub mod ops { } /// Type for construct more complex queries than the classical CRUD ones. -#[derive(Debug, Clone)] -pub struct QueryBuilder<'a, T> +pub struct QueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, + I: Into> + Send + Sync + 'a, + TransactionInput<'a>: From<&'a I>, { - query: Query<'a, T>, - datasource_name: &'a str, + query: Query<'a>, + input: &'a I, datasource_type: DatabaseType, + pd: PhantomData // TODO: provisional while reworking the bounds } -unsafe impl<'a, T> Send for QueryBuilder<'a, T> where - T: CrudOperations + Transaction + RowMapper -{ -} -unsafe impl<'a, T> Sync for QueryBuilder<'a, T> where - T: CrudOperations + Transaction + RowMapper +unsafe impl<'a, T, I> Send for QueryBuilder<'a, T, I> + where T: CrudOperations + Transaction + RowMapper, + I: Into> + Send + Sync + 'a, + TransactionInput<'a>: From<&'a I>, { } +unsafe impl<'a, T, I> Sync for QueryBuilder<'a, T, I> + where T: CrudOperations + Transaction + RowMapper, + I: Into> + Send + Sync + 'a, + TransactionInput<'a>: From<&'a I> +{} -impl<'a, T> QueryBuilder<'a, T> +impl<'a, T, I> QueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, + I: Into> + Send + Sync + 'a, + TransactionInput<'a>: From<&'a I>, { /// Returns a new instance of the [`QueryBuilder`] - pub fn new(query: Query<'a, T>, datasource_name: &'a str) -> Self { + pub fn new(query: Query<'a>, input: &I) -> Self { Self { query, - datasource_name, - datasource_type: DatabaseType::from( - &get_database_config(datasource_name, &DATASOURCES).auth, - ), + input, + datasource_type: todo!("The from type on the querybuilder"), + // DatabaseType::from( + // &get_database_config(input, &DATASOURCES).auth, + // ), + pd: Default::default(), } } @@ -173,13 +182,13 @@ where /// by the selected datasource pub async fn query( &'a mut self, - ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'static)>> { + ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> { self.query.sql.push(';'); Ok(T::query( self.query.sql.clone(), self.query.params.to_vec(), - self.datasource_name, + self.input, ) .await? .into_results::()) @@ -292,24 +301,27 @@ where } } -#[derive(Debug, Clone)] -pub struct SelectQueryBuilder<'a, T> +pub struct SelectQueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, + I: Into> + Send + Sync + 'a, + TransactionInput<'a>: From<&'a I>, { - _inner: QueryBuilder<'a, T>, + _inner: QueryBuilder<'a, T, I>, } -impl<'a, T> SelectQueryBuilder<'a, T> +impl<'a, T, I> SelectQueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, + I: Into> + Send + Sync + 'a, + TransactionInput<'a>: From<&'a I>, { /// Generates a new public instance of the [`SelectQueryBuilder`] - pub fn new(table_schema_data: &str, datasource_name: &'a str) -> Self { + pub fn new(table_schema_data: &str, input: &I) -> Self { Self { - _inner: QueryBuilder::::new( + _inner: QueryBuilder::::new( Query::new(format!("SELECT * FROM {table_schema_data}")), - datasource_name, + input, ), } } @@ -319,7 +331,7 @@ where #[inline] pub async fn query( &'a mut self, - ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'static)>> { + ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> { self._inner.query().await } @@ -388,9 +400,11 @@ where } } -impl<'a, T> ops::QueryBuilder<'a, T> for SelectQueryBuilder<'a, T> +impl<'a, T, I> ops::QueryBuilder<'a, T> for SelectQueryBuilder<'a, T, I> where T: Debug + CrudOperations + Transaction + RowMapper + Send, + I: Into> + Send + Sync + 'a, + TransactionInput<'a>: From<&'a I>, { #[inline] fn read_sql(&'a self) -> &'a str { @@ -455,24 +469,27 @@ where /// /// * `set` - To construct a new `SET` clause to determine the columns to /// update with the provided values -#[derive(Debug, Clone)] -pub struct UpdateQueryBuilder<'a, T> +pub struct UpdateQueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, + I: Into> + Send + Sync + 'a, + TransactionInput<'a>: From<&'a I>, { - _inner: QueryBuilder<'a, T>, + _inner: QueryBuilder<'a, T, I>, } -impl<'a, T> UpdateQueryBuilder<'a, T> +impl<'a, T, I> UpdateQueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, + I: Into> + Send + Sync + 'a, + TransactionInput<'a>: From<&'a I>, { /// Generates a new public instance of the [`UpdateQueryBuilder`] - pub fn new(table_schema_data: &str, datasource_name: &'a str) -> Self { + pub fn new(table_schema_data: &str, input: &I) -> Self { Self { - _inner: QueryBuilder::::new( + _inner: QueryBuilder::::new( Query::new(format!("UPDATE {table_schema_data}")), - datasource_name, + input, ), } } @@ -482,7 +499,7 @@ where #[inline] pub async fn query( &'a mut self, - ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'static)>> { + ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> { self._inner.query().await } @@ -526,9 +543,11 @@ where } } -impl<'a, T> ops::QueryBuilder<'a, T> for UpdateQueryBuilder<'a, T> +impl<'a, T, I> ops::QueryBuilder<'a, T> for UpdateQueryBuilder<'a, T, I> where T: Debug + CrudOperations + Transaction + RowMapper + Send, + I: Into> + Send + Sync + 'a, + TransactionInput<'a>: From<&'a I>, { #[inline] fn read_sql(&'a self) -> &'a str { @@ -594,24 +613,27 @@ where /// /// * `set` - To construct a new `SET` clause to determine the columns to /// update with the provided values -#[derive(Debug, Clone)] -pub struct DeleteQueryBuilder<'a, T> +pub struct DeleteQueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, + I: Into> + Send + Sync + 'a, + TransactionInput<'a>: From<&'a I>, { - _inner: QueryBuilder<'a, T>, + _inner: QueryBuilder<'a, T, I>, } -impl<'a, T> DeleteQueryBuilder<'a, T> +impl<'a, T, I> DeleteQueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, + I: Into> + Send + Sync + 'a, + TransactionInput<'a>: From<&'a I>, { /// Generates a new public instance of the [`DeleteQueryBuilder`] - pub fn new(table_schema_data: &str, datasource_name: &'a str) -> Self { + pub fn new(table_schema_data: &str, input: I) -> Self { Self { - _inner: QueryBuilder::::new( + _inner: QueryBuilder::::new( Query::new(format!("DELETE FROM {table_schema_data}")), - datasource_name, + &input, ), } } @@ -621,14 +643,16 @@ where #[inline] pub async fn query( &'a mut self, - ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'static)>> { + ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> { self._inner.query().await } } -impl<'a, T> ops::QueryBuilder<'a, T> for DeleteQueryBuilder<'a, T> +impl<'a, T, I> ops::QueryBuilder<'a, T> for DeleteQueryBuilder<'a, T, I> where T: Debug + CrudOperations + Transaction + RowMapper + Send, + I: Into> + Send + Sync + 'a, + TransactionInput<'a>: From<&'a I>, { #[inline] fn read_sql(&'a self) -> &'a str { diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index 2594b3ea..509a4d9e 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -38,7 +38,7 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri // /// Deletes from a database entity the row that matches // /// the current instance of a T type, returning a result // /// indicating a possible failure querying the database with the specified datasource. - // async fn delete_datasource<'a>(&self, datasource_name: &'a str) + // async fn delete_with<'a, I>(&self, input: I) // -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'static)>> // { // <#ty as canyon_sql::core::Transaction<#ty>>::query( @@ -65,12 +65,12 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri // ).into_inner().unwrap()) // } - // async fn delete_datasource<'a>(&self, datasource_name: &'a str) + // async fn delete_with<'a, I>(&self, input: I) // -> Result<(), Box> // { // Err(std::io::Error::new( // std::io::ErrorKind::Unsupported, - // "You can't use the 'delete_datasource' method on a \ + // "You can't use the 'delete_with' method on a \ // CanyonEntity that does not have a #[primary_key] annotation. \ // If you need to perform an specific search, use the Querybuilder instead." // ).into_inner().unwrap()) diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index bface463..9fe3ee13 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -190,7 +190,7 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri // / } // / ``` // / - // async fn insert_datasource<'a>(&mut self, datasource_name: &'a str) + // async fn insert_with<'a, I>(&mut self, input: I) // -> Result<(), Box> // { // let mut values: Vec<&dyn canyon_sql::core::QueryParameter<'_>> = vec![#(#insert_values_cloned),*]; diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index 0c24ea4c..a45785cc 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -333,7 +333,8 @@ mod tests { let expected_tokens = quote! { #[doc = "Finds a user by their ID."] #[doc = "This operation retrieves a single user record based on the provided ID."] - async fn find_user_by_id<'a>(id: &dyn QueryParameters<'_>, datasource_name: &'a str) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)> > { + async fn find_user_by_id<'a>(id: &dyn QueryParameters<'_>, input: I) + where I: Into> + Sync + Send + 'aResult, Box<(dyn std::error::Error + Send + Sync + 'a)> > { >::query( "SELECT * FROM users WHERE id = ?", &[id], @@ -378,13 +379,13 @@ mod tests { } #[test] - fn test_find_all_datasource_operation_tokens() { + fn test_find_all_with_operation_tokens() { let user_type = Ident::new("User", Span::call_site()); let find_operation = MacroOperationBuilder::new() - .fn_name("find_all_datasource") - .user_type(&user_type) + .fn_name("find_all_with") .with_input_param() + .user_type(&user_type) .return_type(&user_type) .add_doc_comment("Executes a 'SELECT * FROM '") .add_doc_comment( @@ -396,7 +397,8 @@ mod tests { let expected_tokens = quote! { #[doc = "Executes a 'SELECT * FROM '"] #[doc = "This operation retrieves all the users records stored in the provided datasource"] - async fn find_all_datasource<'a>(datasource_name: &'a str) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)> > { + async fn find_all_with<'a, I>(input: I) + where I: Into> + Sync + Send + 'aResult, Box<(dyn std::error::Error + Send + Sync + 'a)> > { >::query( "SELECT * FROM users", &[], diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index 2fc8e6bf..431cd92d 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -21,24 +21,24 @@ pub fn generate_read_operations_tokens( // TODO: remember that this queries statements must be autogenerated by some automatic procedure let find_all = create_find_all_macro(ty, &fa_stmt); - let find_all_datasource = create_find_all_with_macro(ty, &fa_stmt); + let find_all_with = create_find_all_with_macro(ty, &fa_stmt); let find_all_unchecked = create_find_all_unchecked_macro(ty, &fa_stmt); - let find_all_unchecked_datasource = create_find_all_unchecked_ds_macro(ty, &fa_stmt); + let find_all_unchecked_with = create_find_all_unchecked_with_macro(ty, &fa_stmt); let count_stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); let count = create_count_macro(ty, &count_stmt); - let count_datasource = create_count_ds_macro(ty, &count_stmt); + let count_with = create_count_with_macro(ty, &count_stmt); let find_by_pk_complex_tokens = generate_find_by_pk_tokens(macro_data, table_schema_data); quote! { #find_all - #find_all_datasource + #find_all_with #find_all_unchecked - #find_all_unchecked_datasource + #find_all_unchecked_with #count - #count_datasource + #count_with #find_by_pk_complex_tokens } @@ -58,7 +58,7 @@ pub fn generate_find_all_query_tokens( /// entity but converted to the corresponding database convention, /// unless concrete values are set on the available parameters of the /// `canyon_macro(table_name = "table_name", schema = "schema")` - fn select_query<'a>() -> canyon_sql::query::SelectQueryBuilder<'a, #ty> { + fn select_query<'a>() -> canyon_sql::query::SelectQueryBuilder<'a, #ty, &str> { canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, "") } @@ -73,7 +73,9 @@ pub fn generate_find_all_query_tokens( /// The query it's made against the database with the configured datasource /// described in the configuration file, and selected with the [`&str`] /// passed as parameter. - fn select_query_datasource<'a>(datasource_name: &'a str) -> canyon_sql::query::SelectQueryBuilder<'a, #ty> { + fn select_query_datasource<'a, I>(input: I) -> canyon_sql::query::SelectQueryBuilder<'a, #ty, I> + where I: Into> + Sync + Send + 'a + { canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, datasource_name) } } @@ -95,6 +97,7 @@ fn generate_find_by_pk_tokens( return quote! { async fn find_by_pk<'a>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + where I: Into> + Sync + Send + 'a { Err( std::io::Error::new( @@ -106,14 +109,16 @@ fn generate_find_by_pk_tokens( ) } - async fn find_by_pk_datasource<'a>( + async fn find_by_pk_with<'a, I>( value: &'a dyn canyon_sql::core::QueryParameter<'a>, - datasource_name: &'a str - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> { + input: I + ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + where I: Into> + Sync + Send + 'a + { Err( std::io::Error::new( std::io::ErrorKind::Unsupported, - "You can't use the 'find_by_pk_datasource' associated function on a \ + "You can't use the 'find_by_pk_with' associated function on a \ CanyonEntity that does not have a #[primary_key] annotation. \ If you need to perform an specific search, use the Querybuilder instead." ).into_inner().unwrap() @@ -131,7 +136,7 @@ fn generate_find_by_pk_tokens( }; let find_by_pk = create_find_by_pk_macro(ty, &stmt, &result_handling); - let find_by_pk_ds = create_find_by_pk_datasource(ty, &stmt, &result_handling); + let find_by_pk_ds = create_find_by_pk_with(ty, &stmt, &result_handling); quote! { #find_by_pk @@ -161,15 +166,15 @@ fn generate_find_by_pk_tokens( // let method_name_ident = // proc_macro2::Ident::new(&method_name, proc_macro2::Span::call_site()); // let method_name_ident_ds = proc_macro2::Ident::new( -// &format!("{}_datasource", &method_name), +// &format!("{}_with", &method_name), // proc_macro2::Span::call_site(), // ); // let quoted_method_signature: TokenStream = quote! { // async fn #method_name_ident(&self) -> // Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> // }; -// let quoted_datasource_method_signature: TokenStream = quote! { -// async fn #method_name_ident_ds<'a>(&self, datasource_name: &'a str) -> +// let quoted_with_method_signature: TokenStream = quote! { +// async fn #method_name_ident_ds<'a>(&self, input: I) -> // Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> // }; @@ -204,10 +209,10 @@ fn generate_find_by_pk_tokens( // )); // fk_quotes.push(( -// quote! { #quoted_datasource_method_signature; }, +// quote! { #quoted_with_method_signature; }, // quote! { // /// Searches the parent entity (if exists) for this type with the specified datasource -// #quoted_datasource_method_signature { +// #quoted_with_method_signature { // let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( // #stmt, // &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], @@ -244,16 +249,16 @@ fn generate_find_by_pk_tokens( // let method_name_ident = // proc_macro2::Ident::new(&method_name, proc_macro2::Span::call_site()); // let method_name_ident_ds = proc_macro2::Ident::new( -// &format!("{}_datasource", &method_name), +// &format!("{}_with", &method_name), // proc_macro2::Span::call_site(), // ); // let quoted_method_signature: TokenStream = quote! { // async fn #method_name_ident<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send>(value: &F) -> // Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> // }; -// let quoted_datasource_method_signature: TokenStream = quote! { +// let quoted_with_method_signature: TokenStream = quote! { // async fn #method_name_ident_ds<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send> -// (value: &F, datasource_name: &'a str) -> +// (value: &F, input: I) -> // Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> // }; @@ -287,12 +292,12 @@ fn generate_find_by_pk_tokens( // )); // rev_fk_quotes.push(( -// quote! { #quoted_datasource_method_signature; }, +// quote! { #quoted_with_method_signature; }, // quote! { // /// Given a parent entity T annotated with the derive proc macro `ForeignKeyable`, // /// performns a search to find the children that belong to that concrete parent // /// with the specified datasource. -// #quoted_datasource_method_signature +// #quoted_with_method_signature // { // let lookage_value = value.get_fk_column(#column) // .expect(format!( @@ -363,15 +368,15 @@ mod __details { .with_unwrap() } - pub fn create_find_all_unchecked_ds_macro( + pub fn create_find_all_unchecked_with_macro( ty: &syn::Ident, stmt: &str, ) -> MacroOperationBuilder { MacroOperationBuilder::new() - .fn_name("find_all_unchecked_datasource") + .fn_name("find_all_unchecked_with") .user_type(ty) .return_type(ty) - .with_datasource_param() + .with_input_param() .add_doc_comment("Executes a 'SELECT * FROM '") .add_doc_comment("This operation retrieves all the users records stored with the provided datasource") .query_string(stmt) @@ -430,13 +435,13 @@ mod __details { .raw_return() } - pub fn create_count_ds_macro(ty: &syn::Ident, stmt: &str) -> MacroOperationBuilder { + pub fn create_count_with_macro(ty: &syn::Ident, stmt: &str) -> MacroOperationBuilder { let result_handling = generate_count_manual_result_handling(ty); MacroOperationBuilder::new() - .fn_name("count_datasource") + .fn_name("count_with") .user_type(ty) - .with_datasource_param() + .with_input_param() .return_type(&syn::Ident::new("i64", Span::call_site())) // TODO: into ident or take by value .add_doc_comment( "Performs a COUNT(*) query over the table related to the entity T'", @@ -484,14 +489,14 @@ mod __details { }) } - pub fn create_find_by_pk_datasource( + pub fn create_find_by_pk_with( ty: &Ident, stmt: &str, result_handling: &TokenStream, ) -> MacroOperationBuilder { MacroOperationBuilder::new() - .fn_name("find_by_pk_datasource") - .with_datasource_param() + .fn_name("find_by_pk_with") + .with_input_param() .user_type(ty) .return_type(ty) .add_doc_comment(doc_comments::FIND_BY_PK) @@ -567,12 +572,12 @@ mod macro_builder_read_ops_tests { } #[test] - fn test_macro_builder_find_all_unchecked_datasource() { + fn test_macro_builder_find_all_unchecked_with() { let ty: Ident = Ident::new("User", Span::call_site()); - let find_all_unc_ds_builder = create_find_all_unchecked_ds_macro(&ty, SELECT_ALL_STMT); - let find_all_unc_ds = find_all_unc_ds_builder.generate_tokens().to_string(); + let find_all_unc_with_builder = create_find_all_unchecked_with_macro(&ty, SELECT_ALL_STMT); + let find_all_unc_ds = find_all_unc_with_builder.generate_tokens().to_string(); - assert!(find_all_unc_ds.contains("async fn find_all_unchecked_datasource")); + assert!(find_all_unc_ds.contains("async fn find_all_unchecked_with")); assert!(find_all_unc_ds.contains(RAW_RET_TY)); assert!(find_all_unc_ds.contains(LT_CONSTRAINT)); assert!(find_all_unc_ds.contains(DS_PARAM)); @@ -589,12 +594,12 @@ mod macro_builder_read_ops_tests { } #[test] - fn test_macro_builder_count_datasource() { + fn test_macro_builder_count_with() { let ty: Ident = Ident::new("User", Span::call_site()); - let count_ds_builder = create_count_ds_macro(&ty, COUNT_STMT); - let count_ds = count_ds_builder.generate_tokens().to_string(); + let count_with_builder = create_count_with_macro(&ty, COUNT_STMT); + let count_ds = count_with_builder.generate_tokens().to_string(); - assert!(count_ds.contains("async fn count_datasource")); + assert!(count_ds.contains("async fn count_with")); assert!(count_ds.contains("Result < i64")); assert!(count_ds.contains(LT_CONSTRAINT)); assert!(count_ds.contains(DS_PARAM)); @@ -612,13 +617,13 @@ mod macro_builder_read_ops_tests { } #[test] - fn test_macro_builder_find_by_pk_datasource() { + fn test_macro_builder_find_by_pk_with() { let ty: Ident = Ident::new("User", Span::call_site()); - let find_by_pk_ds_builder = create_find_by_pk_datasource(&ty, FIND_BY_PK_STMT, "e! {}); - let find_by_pk_ds = find_by_pk_ds_builder.generate_tokens().to_string(); + let find_by_pk_with_builder = create_find_by_pk_with(&ty, FIND_BY_PK_STMT, "e! {}); + let find_by_pk_ds = find_by_pk_with_builder.generate_tokens().to_string(); println!("{:?}", find_by_pk_ds.split("\n").collect::>()); - assert!(find_by_pk_ds.contains("async fn find_by_pk_datasource")); + assert!(find_by_pk_ds.contains("async fn find_by_pk_with")); assert!(find_by_pk_ds.contains(LT_CONSTRAINT)); assert!(find_by_pk_ds.contains(DS_PARAM)); assert!(find_by_pk_ds.contains(OPT_RET_TY_LT)); diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index b1db7c4e..3edca853 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -53,7 +53,7 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri // /// the current instance of a T type, returning a result // /// indicating a possible failure querying the database with the // /// specified datasource - // async fn update_datasource<'a>(&self, datasource_name: &'a str) + // async fn update_with<'a, I>(&self, input: I) // -> Result<(), Box> // { // let stmt = format!( @@ -88,13 +88,13 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri // ) // } - // async fn update_datasource<'a>(&self, datasource_name: &'a str) + // async fn update_with<'a, I>(&self, input: I) // -> Result<(), Box> // { // Err( // std::io::Error::new( // std::io::ErrorKind::Unsupported, - // "You can't use the 'update_datasource' method on a \ + // "You can't use the 'update_with' method on a \ // CanyonEntity that does not have a #[primary_key] annotation. \ // If you need to perform an specific search, use the Querybuilder instead." // ).into_inner().unwrap() diff --git a/tests/crud/delete_operations.rs b/tests/crud/delete_operations.rs index 584895ba..8f1c7eec 100644 --- a/tests/crud/delete_operations.rs +++ b/tests/crud/delete_operations.rs @@ -39,7 +39,7 @@ // assert_eq!( // new_league.id, -// League::find_by_pk_datasource(&new_league.id, PSQL_DS) +// League::find_by_pk_with(&new_league.id, PSQL_DS) // .await // .expect("Request error") // .expect("None value") @@ -67,7 +67,7 @@ // /// Same as the delete test, but performing the operations with the specified datasource // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_datasource_mssql_method_operation() { +// fn test_crud_delete_with_mssql_method_operation() { // // For test the delete, we will insert a new instance of the database, and then, // // after inspect it, we will proceed to delete it // let mut new_league: League = League { @@ -81,12 +81,12 @@ // // We insert the instance on the database, on the `League` entity // new_league -// .insert_datasource(SQL_SERVER_DS) +// .insert_with(SQL_SERVER_DS) // .await // .expect("Failed insert operation"); // assert_eq!( // new_league.id, -// League::find_by_pk_datasource(&new_league.id, SQL_SERVER_DS) +// League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) // .await // .expect("Request error") // .expect("None value") @@ -96,7 +96,7 @@ // // Now that we have an instance mapped to some entity by a primary key, we can now // // remove that entry from the database with the delete operation // new_league -// .delete_datasource(SQL_SERVER_DS) +// .delete_with(SQL_SERVER_DS) // .await // .expect("Failed to delete the operation"); @@ -104,7 +104,7 @@ // // the result of the operation, the find by primary key contains Some(v) or None // // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> // assert_eq!( -// League::find_by_pk_datasource(&new_league.id, SQL_SERVER_DS) +// League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) // .await // .expect("Unwrapping the result, letting the Option"), // None @@ -114,7 +114,7 @@ // /// Same as the delete test, but performing the operations with the specified datasource // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_datasource_mysql_method_operation() { +// fn test_crud_delete_with_mysql_method_operation() { // // For test the delete, we will insert a new instance of the database, and then, // // after inspect it, we will proceed to delete it // let mut new_league: League = League { @@ -128,12 +128,12 @@ // // We insert the instance on the database, on the `League` entity // new_league -// .insert_datasource(MYSQL_DS) +// .insert_with(MYSQL_DS) // .await // .expect("Failed insert operation"); // assert_eq!( // new_league.id, -// League::find_by_pk_datasource(&new_league.id, MYSQL_DS) +// League::find_by_pk_with(&new_league.id, MYSQL_DS) // .await // .expect("Request error") // .expect("None value") @@ -143,7 +143,7 @@ // // Now that we have an instance mapped to some entity by a primary key, we can now // // remove that entry from the database with the delete operation // new_league -// .delete_datasource(MYSQL_DS) +// .delete_with(MYSQL_DS) // .await // .expect("Failed to delete the operation"); @@ -151,7 +151,7 @@ // // the result of the operation, the find by primary key contains Some(v) or None // // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> // assert_eq!( -// League::find_by_pk_datasource(&new_league.id, MYSQL_DS) +// League::find_by_pk_with(&new_league.id, MYSQL_DS) // .await // .expect("Unwrapping the result, letting the Option"), // None diff --git a/tests/crud/foreign_key_operations.rs b/tests/crud/foreign_key_operations.rs index 8f286fb0..52f81288 100644 --- a/tests/crud/foreign_key_operations.rs +++ b/tests/crud/foreign_key_operations.rs @@ -45,15 +45,15 @@ // /// Same as the search by foreign key, but with the specified datasource // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_by_foreign_key_datasource_mssql() { -// let some_tournament: Tournament = Tournament::find_by_pk_datasource(&10, SQL_SERVER_DS) +// fn test_crud_search_by_foreign_key_with_mssql() { +// let some_tournament: Tournament = Tournament::find_by_pk_with(&10, SQL_SERVER_DS) // .await // .expect("Result variant of the query is err") // .expect("No result found for the given parameter"); // // We can get the parent entity for the retrieved child instance // let parent_entity: Option = some_tournament -// .search_league_datasource(SQL_SERVER_DS) +// .search_league_with(SQL_SERVER_DS) // .await // .expect("Result variant of the query is err"); @@ -71,15 +71,15 @@ // /// Same as the search by foreign key, but with the specified datasource // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_by_foreign_key_datasource_mysql() { -// let some_tournament: Tournament = Tournament::find_by_pk_datasource(&10, MYSQL_DS) +// fn test_crud_search_by_foreign_key_with_mysql() { +// let some_tournament: Tournament = Tournament::find_by_pk_with(&10, MYSQL_DS) // .await // .expect("Result variant of the query is err") // .expect("No result found for the given parameter"); // // We can get the parent entity for the retrieved child instance // let parent_entity: Option = some_tournament -// .search_league_datasource(MYSQL_DS) +// .search_league_with(MYSQL_DS) // .await // .expect("Result variant of the query is err"); @@ -122,15 +122,15 @@ // /// but with the specified datasource // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_reverse_side_foreign_key_datasource_mssql() { -// let some_league: League = League::find_by_pk_datasource(&1, SQL_SERVER_DS) +// fn test_crud_search_reverse_side_foreign_key_with_mssql() { +// let some_league: League = League::find_by_pk_with(&1, SQL_SERVER_DS) // .await // .expect("Result variant of the query is err") // .expect("No result found for the given parameter"); // // Computes how many tournaments are pointing to the retrieved league // let child_tournaments: Vec = -// Tournament::search_league_childrens_datasource(&some_league, SQL_SERVER_DS) +// Tournament::search_league_childrens_with(&some_league, SQL_SERVER_DS) // .await // .expect("Result variant of the query is err"); @@ -144,15 +144,15 @@ // /// but with the specified datasource // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_reverse_side_foreign_key_datasource_mysql() { -// let some_league: League = League::find_by_pk_datasource(&1, MYSQL_DS) +// fn test_crud_search_reverse_side_foreign_key_with_mysql() { +// let some_league: League = League::find_by_pk_with(&1, MYSQL_DS) // .await // .expect("Result variant of the query is err") // .expect("No result found for the given parameter"); // // Computes how many tournaments are pointing to the retrieved league // let child_tournaments: Vec = -// Tournament::search_league_childrens_datasource(&some_league, MYSQL_DS) +// Tournament::search_league_childrens_with(&some_league, MYSQL_DS) // .await // .expect("Result variant of the query is err"); diff --git a/tests/crud/init_mssql.rs b/tests/crud/init_mssql.rs index b2d7e8fc..2f4f43a9 100644 --- a/tests/crud/init_mssql.rs +++ b/tests/crud/init_mssql.rs @@ -40,7 +40,7 @@ fn initialize_sql_server_docker_instance() { let query_result = client.query(SQL_SERVER_CREATE_TABLES, &[]).await; assert!(query_result.is_ok()); - let leagues_sql = League::find_all_datasource(SQL_SERVER_DS).await; + let leagues_sql = League::find_all_with(SQL_SERVER_DS).await; println!("LSQL ERR: {leagues_sql:?}"); assert!(leagues_sql.is_ok()); diff --git a/tests/crud/insert_operations.rs b/tests/crud/insert_operations.rs index 510e3b7a..00758f0c 100644 --- a/tests/crud/insert_operations.rs +++ b/tests/crud/insert_operations.rs @@ -61,7 +61,7 @@ // /// the specified datasource // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_insert_datasource_mssql_operation() { +// fn test_crud_insert_with_mssql_operation() { // let mut new_league: League = League { // id: Default::default(), // ext_id: 7892635306594_i64, @@ -73,7 +73,7 @@ // // We insert the instance on the database, on the `League` entity // new_league -// .insert_datasource(SQL_SERVER_DS) +// .insert_with(SQL_SERVER_DS) // .await // .expect("Failed insert datasource operation"); @@ -81,7 +81,7 @@ // // value for the primary key field, which is id. So, we can query the // // database again with the find by primary key operation to check if // // the value was really inserted -// let inserted_league = League::find_by_pk_datasource(&new_league.id, SQL_SERVER_DS) +// let inserted_league = League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) // .await // .expect("Failed the query to the database") // .expect("No entity found for the primary key value passed in"); @@ -93,7 +93,7 @@ // /// the specified datasource // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_insert_datasource_mysql_operation() { +// fn test_crud_insert_with_mysql_operation() { // let mut new_league: League = League { // id: Default::default(), // ext_id: 7892635306594_i64, @@ -105,7 +105,7 @@ // // We insert the instance on the database, on the `League` entity // new_league -// .insert_datasource(MYSQL_DS) +// .insert_with(MYSQL_DS) // .await // .expect("Failed insert datasource operation"); @@ -113,7 +113,7 @@ // // value for the primary key field, which is id. So, we can query the // // database again with the find by primary key operation to check if // // the value was really inserted -// let inserted_league = League::find_by_pk_datasource(&new_league.id, MYSQL_DS) +// let inserted_league = League::find_by_pk_with(&new_league.id, MYSQL_DS) // .await // .expect("Failed the query to the database") // .expect("No entity found for the primary key value passed in"); @@ -195,7 +195,7 @@ // /// Same as the multi insert above, but with the specified datasource // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_multi_insert_datasource_mssql_operation() { +// fn test_crud_multi_insert_with_mssql_operation() { // let mut new_league_mi: League = League { // id: Default::default(), // ext_id: 54376478_i64, @@ -223,28 +223,28 @@ // // Insert the instance as database entities // new_league_mi -// .insert_datasource(SQL_SERVER_DS) +// .insert_with(SQL_SERVER_DS) // .await // .expect("Failed insert datasource operation"); // new_league_mi_2 -// .insert_datasource(SQL_SERVER_DS) +// .insert_with(SQL_SERVER_DS) // .await // .expect("Failed insert datasource operation"); // new_league_mi_3 -// .insert_datasource(SQL_SERVER_DS) +// .insert_with(SQL_SERVER_DS) // .await // .expect("Failed insert datasource operation"); // // Recover the inserted data by primary key -// let inserted_league = League::find_by_pk_datasource(&new_league_mi.id, SQL_SERVER_DS) +// let inserted_league = League::find_by_pk_with(&new_league_mi.id, SQL_SERVER_DS) // .await // .expect("[1] - Failed the query to the database") // .expect("[1] - No entity found for the primary key value passed in"); -// let inserted_league_2 = League::find_by_pk_datasource(&new_league_mi_2.id, SQL_SERVER_DS) +// let inserted_league_2 = League::find_by_pk_with(&new_league_mi_2.id, SQL_SERVER_DS) // .await // .expect("[2] - Failed the query to the database") // .expect("[2] - No entity found for the primary key value passed in"); -// let inserted_league_3 = League::find_by_pk_datasource(&new_league_mi_3.id, SQL_SERVER_DS) +// let inserted_league_3 = League::find_by_pk_with(&new_league_mi_3.id, SQL_SERVER_DS) // .await // .expect("[3] - Failed the query to the database") // .expect("[3] - No entity found for the primary key value passed in"); @@ -257,7 +257,7 @@ // /// Same as the multi insert above, but with the specified datasource // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_multi_insert_datasource_mysql_operation() { +// fn test_crud_multi_insert_with_mysql_operation() { // let mut new_league_mi: League = League { // id: Default::default(), // ext_id: 54376478_i64, @@ -285,28 +285,28 @@ // // Insert the instance as database entities // new_league_mi -// .insert_datasource(MYSQL_DS) +// .insert_with(MYSQL_DS) // .await // .expect("Failed insert datasource operation"); // new_league_mi_2 -// .insert_datasource(MYSQL_DS) +// .insert_with(MYSQL_DS) // .await // .expect("Failed insert datasource operation"); // new_league_mi_3 -// .insert_datasource(MYSQL_DS) +// .insert_with(MYSQL_DS) // .await // .expect("Failed insert datasource operation"); // // Recover the inserted data by primary key -// let inserted_league = League::find_by_pk_datasource(&new_league_mi.id, MYSQL_DS) +// let inserted_league = League::find_by_pk_with(&new_league_mi.id, MYSQL_DS) // .await // .expect("[1] - Failed the query to the database") // .expect("[1] - No entity found for the primary key value passed in"); -// let inserted_league_2 = League::find_by_pk_datasource(&new_league_mi_2.id, MYSQL_DS) +// let inserted_league_2 = League::find_by_pk_with(&new_league_mi_2.id, MYSQL_DS) // .await // .expect("[2] - Failed the query to the database") // .expect("[2] - No entity found for the primary key value passed in"); -// let inserted_league_3 = League::find_by_pk_datasource(&new_league_mi_3.id, MYSQL_DS) +// let inserted_league_3 = League::find_by_pk_with(&new_league_mi_3.id, MYSQL_DS) // .await // .expect("[3] - Failed the query to the database") // .expect("[3] - No entity found for the primary key value passed in"); diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index f1874d72..45268000 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -80,9 +80,9 @@ fn test_crud_find_with_querybuilder_and_fulllike() { /// with the parameters that modifies the base SQL to SELECT * FROM #[cfg(feature = "mssql")] #[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_fulllike_datasource_mssql() { +fn test_crud_find_with_querybuilder_and_fulllike_with_mssql() { // Find all the leagues with "LC" in their name - let mut filtered_leagues_result = League::select_query_datasource(SQL_SERVER_DS); + let mut filtered_leagues_result = League::select_query_with(SQL_SERVER_DS); filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); assert_eq!( @@ -95,9 +95,9 @@ fn test_crud_find_with_querybuilder_and_fulllike_datasource_mssql() { /// with the parameters that modifies the base SQL to SELECT * FROM #[cfg(feature = "mysql")] #[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_fulllike_datasource_mysql() { +fn test_crud_find_with_querybuilder_and_fulllike_with_mysql() { // Find all the leagues with "LC" in their name - let mut filtered_leagues_result = League::select_query_datasource(MYSQL_DS); + let mut filtered_leagues_result = League::select_query_with(MYSQL_DS); filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); assert_eq!( @@ -125,7 +125,7 @@ fn test_crud_find_with_querybuilder_and_leftlike() { /// with the parameters that modifies the base SQL to SELECT * FROM #[cfg(feature = "mssql")] #[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_leftlike_datasource_mssql() { +fn test_crud_find_with_querybuilder_and_leftlike_with_mssql() { // Find all the leagues whose name ends with "CK" let mut filtered_leagues_result = League::select_query(); filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); @@ -140,9 +140,9 @@ fn test_crud_find_with_querybuilder_and_leftlike_datasource_mssql() { /// with the parameters that modifies the base SQL to SELECT * FROM #[cfg(feature = "mysql")] #[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_leftlike_datasource_mysql() { +fn test_crud_find_with_querybuilder_and_leftlike_with_mysql() { // Find all the leagues whose name ends with "CK" - let mut filtered_leagues_result = League::select_query_datasource(MYSQL_DS); + let mut filtered_leagues_result = League::select_query_with(MYSQL_DS); filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); assert_eq!( @@ -170,9 +170,9 @@ fn test_crud_find_with_querybuilder_and_rightlike() { /// with the parameters that modifies the base SQL to SELECT * FROM #[cfg(feature = "mssql")] #[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_rightlike_datasource_mssql() { +fn test_crud_find_with_querybuilder_and_rightlike_with_mssql() { // Find all the leagues whose name starts with "LC" - let mut filtered_leagues_result = League::select_query_datasource(SQL_SERVER_DS); + let mut filtered_leagues_result = League::select_query_with(SQL_SERVER_DS); filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); assert_eq!( @@ -185,9 +185,9 @@ fn test_crud_find_with_querybuilder_and_rightlike_datasource_mssql() { /// with the parameters that modifies the base SQL to SELECT * FROM #[cfg(feature = "mysql")] #[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_rightlike_datasource_mysql() { +fn test_crud_find_with_querybuilder_and_rightlike_with_mysql() { // Find all the leagues whose name starts with "LC" - let mut filtered_leagues_result = League::select_query_datasource(MYSQL_DS); + let mut filtered_leagues_result = League::select_query_with(MYSQL_DS); filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); assert_eq!( @@ -199,9 +199,9 @@ fn test_crud_find_with_querybuilder_and_rightlike_datasource_mysql() { /// Same than the above but with the specified datasource #[cfg(feature = "mssql")] #[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_datasource_mssql() { +fn test_crud_find_with_querybuilder_with_mssql() { // Find all the players where its ID column value is greater that 50 - let filtered_find_players = Player::select_query_datasource(SQL_SERVER_DS) + let filtered_find_players = Player::select_query_with(SQL_SERVER_DS) .r#where(PlayerFieldValue::id(&50), Comp::Gt) .query() .await; @@ -212,9 +212,9 @@ fn test_crud_find_with_querybuilder_datasource_mssql() { /// Same than the above but with the specified datasource #[cfg(feature = "mysql")] #[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_datasource_mysql() { +fn test_crud_find_with_querybuilder_with_mysql() { // Find all the players where its ID column value is greater that 50 - let filtered_find_players = Player::select_query_datasource(MYSQL_DS) + let filtered_find_players = Player::select_query_with(MYSQL_DS) .r#where(PlayerFieldValue::id(&50), Comp::Gt) .query() .await; @@ -261,10 +261,10 @@ fn test_crud_find_with_querybuilder_datasource_mysql() { // /// Same as above, but with the specified datasource // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_with_querybuilder_datasource_mssql() { +// fn test_crud_update_with_querybuilder_with_mssql() { // // Find all the leagues with ID less or equals that 7 // // and where it's region column value is equals to 'Korea' -// let mut q = Player::update_query_datasource(SQL_SERVER_DS); +// let mut q = Player::update_query_with(SQL_SERVER_DS); // q.set(&[ // (PlayerField::summoner_name, "Random updated player name"), // (PlayerField::first_name, "I am an updated first name"), @@ -275,7 +275,7 @@ fn test_crud_find_with_querybuilder_datasource_mysql() { // .await // .expect("Failed to update records with the querybuilder"); -// let found_updated_values = Player::select_query_datasource(SQL_SERVER_DS) +// let found_updated_values = Player::select_query_with(SQL_SERVER_DS) // .r#where(PlayerFieldValue::id(&1), Comp::Gt) // .and(PlayerFieldValue::id(&7), Comp::LtEq) // .query() @@ -291,11 +291,11 @@ fn test_crud_find_with_querybuilder_datasource_mysql() { // /// Same as above, but with the specified datasource // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_with_querybuilder_datasource_mysql() { +// fn test_crud_update_with_querybuilder_with_mysql() { // // Find all the leagues with ID less or equals that 7 // // and where it's region column value is equals to 'Korea' -// let mut q = Player::update_query_datasource(MYSQL_DS); +// let mut q = Player::update_query_with(MYSQL_DS); // q.set(&[ // (PlayerField::summoner_name, "Random updated player name"), // (PlayerField::first_name, "I am an updated first name"), @@ -306,7 +306,7 @@ fn test_crud_find_with_querybuilder_datasource_mysql() { // .await // .expect("Failed to update records with the querybuilder"); -// let found_updated_values = Player::select_query_datasource(MYSQL_DS) +// let found_updated_values = Player::select_query_with(MYSQL_DS) // .r#where(PlayerFieldValue::id(&1), Comp::Gt) // .and(PlayerFieldValue::id(&7), Comp::LtEq) // .query() @@ -341,15 +341,15 @@ fn test_crud_find_with_querybuilder_datasource_mysql() { // /// Same as the above delete, but with the specified datasource // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_with_querybuilder_datasource_mssql() { -// Player::delete_query_datasource(SQL_SERVER_DS) +// fn test_crud_delete_with_querybuilder_with_mssql() { +// Player::delete_query_with(SQL_SERVER_DS) // .r#where(PlayerFieldValue::id(&120), Comp::Gt) // .and(PlayerFieldValue::id(&130), Comp::Lt) // .query() // .await // .expect("Error connecting with the database when we are going to delete data! :)"); -// assert!(Player::select_query_datasource(SQL_SERVER_DS) +// assert!(Player::select_query_with(SQL_SERVER_DS) // .r#where(PlayerFieldValue::id(&122), Comp::Eq) // .query() // .await @@ -360,15 +360,15 @@ fn test_crud_find_with_querybuilder_datasource_mysql() { // /// Same as the above delete, but with the specified datasource // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_with_querybuilder_datasource_mysql() { -// Player::delete_query_datasource(MYSQL_DS) +// fn test_crud_delete_with_querybuilder_with_mysql() { +// Player::delete_query_with(MYSQL_DS) // .r#where(PlayerFieldValue::id(&120), Comp::Gt) // .and(PlayerFieldValue::id(&130), Comp::Lt) // .query() // .await // .expect("Error connecting with the database when we are going to delete data! :)"); -// assert!(Player::select_query_datasource(MYSQL_DS) +// assert!(Player::select_query_with(MYSQL_DS) // .r#where(PlayerFieldValue::id(&122), Comp::Eq) // .query() // .await diff --git a/tests/crud/select_operations.rs b/tests/crud/select_operations.rs index 154b0445..cd345eda 100644 --- a/tests/crud/select_operations.rs +++ b/tests/crud/select_operations.rs @@ -45,9 +45,9 @@ fn test_crud_find_all_unchecked() { /// and using the specified datasource #[cfg(feature = "mssql")] #[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_all_datasource_mssql() { +fn test_crud_find_all_with_mssql() { let find_all_result: Result, Box> = - League::find_all_datasource(SQL_SERVER_DS).await; + League::find_all_with(SQL_SERVER_DS).await; // Connection doesn't return an error assert!(!find_all_result.is_err()); assert!(!find_all_result.unwrap().is_empty()); @@ -55,21 +55,21 @@ fn test_crud_find_all_datasource_mssql() { #[cfg(feature = "mysql")] #[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_all_datasource_mysql() { +fn test_crud_find_all_with_mysql() { let find_all_result: Result, Box> = - League::find_all_datasource(MYSQL_DS).await; + League::find_all_with(MYSQL_DS).await; // Connection doesn't return an error assert!(!find_all_result.is_err()); assert!(!find_all_result.unwrap().is_empty()); } -/// Same as the `find_all_datasource()`, but with the unchecked variant and the specified dataosource, +/// Same as the `find_all_with()`, but with the unchecked variant and the specified dataosource, /// returning directly `Vec` and not `Result, Err>` #[cfg(feature = "mssql")] #[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_all_unchecked_datasource() { - let find_all_result: Vec = League::find_all_unchecked_datasource(SQL_SERVER_DS).await; +fn test_crud_find_all_unchecked_with() { + let find_all_result: Vec = League::find_all_unchecked_with(SQL_SERVER_DS).await; assert!(!find_all_result.is_empty()); } @@ -102,9 +102,9 @@ fn test_crud_find_by_pk() { /// Uses the *specified datasource mssql* in the second parameter of the function call. #[cfg(feature = "mssql")] #[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_by_pk_datasource_mssql() { +fn test_crud_find_by_pk_with_mssql() { let find_by_pk_result: Result, Box> = - League::find_by_pk_datasource(&27, SQL_SERVER_DS).await; + League::find_by_pk_with(&27, SQL_SERVER_DS).await; assert!(find_by_pk_result.as_ref().unwrap().is_some()); let some_league = find_by_pk_result.unwrap().unwrap(); @@ -125,9 +125,9 @@ fn test_crud_find_by_pk_datasource_mssql() { /// Uses the *specified datasource mysql* in the second parameter of the function call. #[cfg(feature = "mysql")] #[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_by_pk_datasource_mysql() { +fn test_crud_find_by_pk_with_mysql() { let find_by_pk_result: Result, Box> = - League::find_by_pk_datasource(&27, MYSQL_DS).await; + League::find_by_pk_with(&27, MYSQL_DS).await; assert!(find_by_pk_result.as_ref().unwrap().is_some()); let some_league = find_by_pk_result.unwrap().unwrap(); @@ -156,13 +156,13 @@ fn test_crud_count_operation() { /// the specified datasource mssql #[cfg(feature = "mssql")] #[canyon_sql::macros::canyon_tokio_test] -fn test_crud_count_datasource_operation_mssql() { +fn test_crud_count_with_operation_mssql() { assert_eq!( - League::find_all_datasource(SQL_SERVER_DS) + League::find_all_with(SQL_SERVER_DS) .await .unwrap() .len() as i64, - League::count_datasource(SQL_SERVER_DS).await.unwrap() + League::count_with(SQL_SERVER_DS).await.unwrap() ); } @@ -170,9 +170,9 @@ fn test_crud_count_datasource_operation_mssql() { /// the specified datasource mysql #[cfg(feature = "mysql")] #[canyon_sql::macros::canyon_tokio_test] -fn test_crud_count_datasource_operation_mysql() { +fn test_crud_count_with_operation_mysql() { assert_eq!( - League::find_all_datasource(MYSQL_DS).await.unwrap().len() as i64, - League::count_datasource(MYSQL_DS).await.unwrap() + League::find_all_with(MYSQL_DS).await.unwrap().len() as i64, + League::count_with(MYSQL_DS).await.unwrap() ); } diff --git a/tests/crud/update_operations.rs b/tests/crud/update_operations.rs index 36b8c090..cf00f595 100644 --- a/tests/crud/update_operations.rs +++ b/tests/crud/update_operations.rs @@ -61,10 +61,10 @@ // /// Same as the above test, but with the specified datasource. // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_datasource_mssql_method_operation() { +// fn test_crud_update_with_mssql_method_operation() { // // We first retrieve some entity from the database. Note that we must make // // the retrieved instance mutable of clone it to a new mutable resource -// let mut updt_candidate: League = League::find_by_pk_datasource(&1, SQL_SERVER_DS) +// let mut updt_candidate: League = League::find_by_pk_with(&1, SQL_SERVER_DS) // .await // .expect("[1] - Failed the query to the database") // .expect("[1] - No entity found for the primary key value passed in"); @@ -78,12 +78,12 @@ // let updt_value: i64 = 59306442534_i64; // updt_candidate.ext_id = updt_value; // updt_candidate -// .update_datasource(SQL_SERVER_DS) +// .update_with(SQL_SERVER_DS) // .await // .expect("Failed the update operation"); // // Retrieve it again, and check if the value was really updated -// let updt_entity: League = League::find_by_pk_datasource(&1, SQL_SERVER_DS) +// let updt_entity: League = League::find_by_pk_with(&1, SQL_SERVER_DS) // .await // .expect("[2] - Failed the query to the database") // .expect("[2] - No entity found for the primary key value passed in"); @@ -94,7 +94,7 @@ // // the next time that will run // updt_candidate.ext_id = 100695891328981122_i64; // updt_candidate -// .update_datasource(SQL_SERVER_DS) +// .update_with(SQL_SERVER_DS) // .await // .expect("Failed to restablish the initial value update operation"); // } @@ -102,11 +102,11 @@ // /// Same as the above test, but with the specified datasource. // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_datasource_mysql_method_operation() { +// fn test_crud_update_with_mysql_method_operation() { // // We first retrieve some entity from the database. Note that we must make // // the retrieved instance mutable of clone it to a new mutable resource -// let mut updt_candidate: League = League::find_by_pk_datasource(&1, MYSQL_DS) +// let mut updt_candidate: League = League::find_by_pk_with(&1, MYSQL_DS) // .await // .expect("[1] - Failed the query to the database") // .expect("[1] - No entity found for the primary key value passed in"); @@ -120,12 +120,12 @@ // let updt_value: i64 = 59306442534_i64; // updt_candidate.ext_id = updt_value; // updt_candidate -// .update_datasource(MYSQL_DS) +// .update_with(MYSQL_DS) // .await // .expect("Failed the update operation"); // // Retrieve it again, and check if the value was really updated -// let updt_entity: League = League::find_by_pk_datasource(&1, MYSQL_DS) +// let updt_entity: League = League::find_by_pk_with(&1, MYSQL_DS) // .await // .expect("[2] - Failed the query to the database") // .expect("[2] - No entity found for the primary key value passed in"); @@ -136,7 +136,7 @@ // // the next time that will run // updt_candidate.ext_id = 100695891328981122_i64; // updt_candidate -// .update_datasource(MYSQL_DS) +// .update_with(MYSQL_DS) // .await // .expect("Failed to restablish the initial value update operation"); // } From 02d45bfbeb0bd7998ac5d5878d67340f99da30f4 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sat, 25 Jan 2025 18:51:29 +0100 Subject: [PATCH 042/155] feat(wip): working around the limitations of the new generic input I param for getting the datasources from diverse inputs --- canyon_core/src/query.rs | 6 + canyon_crud/src/crud.rs | 32 +- .../src/query_elements/query_builder.rs | 2 +- .../src/query_operations/foreign_key.rs | 180 ++++ .../src/query_operations/macro_builder.rs | 230 ----- .../src/query_operations/macro_template.rs | 224 +---- canyon_macros/src/query_operations/mod.rs | 3 +- canyon_macros/src/query_operations/select.rs | 290 ++---- src/lib.rs | 1 + tests/crud/init_mssql.rs | 124 +-- tests/crud/querybuilder_operations.rs | 836 +++++++++--------- tests/crud/select_operations.rs | 356 ++++---- tests/tests_models/player.rs | 48 +- tests/tests_models/tournament.rs | 30 +- 14 files changed, 984 insertions(+), 1378 deletions(-) create mode 100644 canyon_macros/src/query_operations/foreign_key.rs delete mode 100644 canyon_macros/src/query_operations/macro_builder.rs diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index 9ecda910..16c913df 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -106,3 +106,9 @@ impl<'a> From<&'a str> for TransactionInput<'a> { TransactionInput::DatasourceName(ds_name) } } + +impl<'a> From<&'a &'a str> for TransactionInput<'a> { + fn from(ds_name: &'a &'a str) -> Self { + TransactionInput::DatasourceName(ds_name) + } +} diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index a10a29a6..5c16b32c 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -1,6 +1,7 @@ use async_trait::async_trait; use canyon_core::query_parameters::QueryParameter; use canyon_core::{mapper::RowMapper, query::Transaction}; +use canyon_core::connection::db_connector::DatabaseConnection; use canyon_core::query::TransactionInput; use crate::query_elements::query_builder::{ DeleteQueryBuilder, SelectQueryBuilder, UpdateQueryBuilder, @@ -28,26 +29,29 @@ where { async fn find_all() -> Result, Box<(dyn std::error::Error + Send + Sync)>>; - async fn find_all_with<'a, I>(input: I) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> - where I: Into> + Sync + Send + 'a; + async fn find_all_with<'a, I>(input: &'a I) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + where I: Into> + Sync + Send + 'a, + TransactionInput<'a>: From<&'a I>; async fn find_all_unchecked() -> Vec; - async fn find_all_unchecked_with<'a, I>(input: I) -> Vec - where I: Into> + Sync + Send + 'a; - - fn select_query<'a, I>() -> SelectQueryBuilder<'a, T, I> where I: Into> + Sync + Send + 'a, TransactionInput<'a>: From<&'a I>,; - - fn select_query_with<'a, I>(input: I) -> SelectQueryBuilder<'a, T, I> + async fn find_all_unchecked_with<'a, I>(input: &'a I) -> Vec where I: Into> + Sync + Send + 'a, - TransactionInput<'a>: From<&'a I>,; + TransactionInput<'a>: From<&'a I>; + + // fn select_query<'a>() -> SelectQueryBuilder<'a, T, &'a str>; + // + // fn select_query_with<'a, I>(input: &'a I) -> SelectQueryBuilder<'a, T, I> + // where I: Into> + Sync + Send + 'a, + // TransactionInput<'a>: From<&'a I>; async fn count() -> Result>; async fn count_with<'a, I>( - input: I, + input: &'a I, ) -> Result> - where I: Into> + Sync + Send + 'a; + where I: Into> + Sync + Send + 'a, + TransactionInput<'a>: From<&'a I>; async fn find_by_pk<'a>( value: &'a dyn QueryParameter<'a>, @@ -55,8 +59,10 @@ where async fn find_by_pk_with<'a, I>( value: &'a dyn QueryParameter<'a>, - input: I, - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>>; + input: &'a I, + ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + where I: Into> + Sync + Send + 'a, + TransactionInput<'a>: From<&'a I>; // async fn insert<'a>(&mut self) -> Result<(), Box>; diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index e2f0c1ac..99e1ad93 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -304,7 +304,7 @@ where pub struct SelectQueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: Into> + Send + Sync + 'a, + I: Into> + Send + Sync + 'a + ?Sized, TransactionInput<'a>: From<&'a I>, { _inner: QueryBuilder<'a, T, I>, diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs new file mode 100644 index 00000000..1c0b8080 --- /dev/null +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -0,0 +1,180 @@ + +// /// Generates the TokenStream for build the search by foreign key feature, also as a method instance +// /// of a T type of as an associated function of same T type, but wrapped as a Result, representing +// /// a possible failure querying the database, a bad or missing FK annotation or a missed ForeignKeyable +// /// derive macro on the parent side of the relation +// pub fn generate_find_by_foreign_key_tokens( +// macro_data: &MacroTokens<'_>, +// ) -> Vec<(TokenStream, TokenStream)> { +// let mut fk_quotes: Vec<(TokenStream, TokenStream)> = Vec::new(); + +// for (field_ident, fk_annot) in macro_data.get_fk_annotations().iter() { +// if let EntityFieldAnnotation::ForeignKey(table, column) = fk_annot { +// let method_name = "search_".to_owned() + table; + +// // TODO this is not a good implementation. We must try to capture the +// // related entity in some way, and compare it with something else +// let fk_ty = database_table_name_to_struct_ident(table); + +// // Generate and identifier for the method based on the convention of "search_related_types" +// // where types is a placeholder for the plural name of the type referenced +// let method_name_ident = +// proc_macro2::Ident::new(&method_name, proc_macro2::Span::call_site()); +// let method_name_ident_with = proc_macro2::Ident::new( +// &format!("{}_with", &method_name), +// proc_macro2::Span::call_site(), +// ); +// let quoted_method_signature: TokenStream = quote! { +// async fn #method_name_ident(&self) -> +// Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> +// }; +// let quoted_with_method_signature: TokenStream = quote! { +// async fn #method_name_ident_with<'a>(&self, input: I) -> +// Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> +// }; + +// let stmt = format!( +// "SELECT * FROM {} WHERE {} = $1", +// table, +// format!("\"{column}\"").as_str(), +// ); +// let result_handler = quote! { +// match result { +// n if n.len() == 0 => Ok(None), +// _ => Ok(Some( +// result.into_results::<#fk_ty>().remove(0) +// )) +// } +// }; + +// fk_quotes.push(( +// quote! { #quoted_method_signature; }, +// quote! { +// /// Searches the parent entity (if exists) for this type +// #quoted_method_signature { +// let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( +// #stmt, +// &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], +// "" +// ).await?; + +// #result_handler +// } +// }, +// )); + +// fk_quotes.push(( +// quote! { #quoted_with_method_signature; }, +// quote! { +// /// Searches the parent entity (if exists) for this type with the specified datasource +// #quoted_with_method_signature { +// let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( +// #stmt, +// &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], +// datasource_name +// ).await?; + +// #result_handler +// } +// }, +// )); +// } +// } + +// fk_quotes +// } + +// /// Generates the TokenStream for build the __search_by_foreign_key() CRUD +// /// associated function, but wrapped as a Result, representing +// /// a possible failure querying the database, a bad or missing FK annotation or a missed ForeignKeyable +// /// derive macro on the parent side of the relation +// pub fn generate_find_by_reverse_foreign_key_tokens( +// macro_data: &MacroTokens<'_>, +// table_schema_data: &String, +// ) -> Vec<(TokenStream, TokenStream)> { +// let mut rev_fk_quotes: Vec<(TokenStream, TokenStream)> = Vec::new(); +// let ty = macro_data.ty; + +// for (field_ident, fk_annot) in macro_data.get_fk_annotations().iter() { +// if let EntityFieldAnnotation::ForeignKey(table, column) = fk_annot { +// let method_name = format!("search_{table}_childrens"); + +// // Generate and identifier for the method based on the convention of "search_by__" (note the double underscore) +// // plus the 'table_name' property of the ForeignKey annotation +// let method_name_ident = +// proc_macro2::Ident::new(&method_name, proc_macro2::Span::call_site()); +// let method_name_ident_with = proc_macro2::Ident::new( +// &format!("{}_with", &method_name), +// proc_macro2::Span::call_site(), +// ); +// let quoted_method_signature: TokenStream = quote! { +// async fn #method_name_ident<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send>(value: &F) -> +// Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> +// }; +// let quoted_with_method_signature: TokenStream = quote! { +// async fn #method_name_ident_with<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send> +// (value: &F, input: I) -> +// Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> +// }; + +// let f_ident = field_ident.to_string(); + +// rev_fk_quotes.push(( +// quote! { #quoted_method_signature; }, +// quote! { +// /// Given a parent entity T annotated with the derive proc macro `ForeignKeyable`, +// /// performns a search to find the children that belong to that concrete parent. +// #quoted_method_signature +// { +// let lookage_value = value.get_fk_column(#column) +// .expect(format!( +// "Column: {:?} not found in type: {:?}", #column, #table +// ).as_str()); + +// let stmt = format!( +// "SELECT * FROM {} WHERE {} = $1", +// #table_schema_data, +// format!("\"{}\"", #f_ident).as_str() +// ); + +// Ok(<#ty as canyon_sql::core::Transaction<#ty>>::query( +// stmt, +// &[lookage_value], +// "" +// ).await?.into_results::<#ty>()) +// } +// }, +// )); + +// rev_fk_quotes.push(( +// quote! { #quoted_with_method_signature; }, +// quote! { +// /// Given a parent entity T annotated with the derive proc macro `ForeignKeyable`, +// /// performns a search to find the children that belong to that concrete parent +// /// with the specified datasource. +// #quoted_with_method_signature +// { +// let lookage_value = value.get_fk_column(#column) +// .expect(format!( +// "Column: {:?} not found in type: {:?}", #column, #table +// ).as_str()); + +// let stmt = format!( +// "SELECT * FROM {} WHERE {} = $1", +// #table_schema_data, +// format!("\"{}\"", #f_ident).as_str() +// ); + +// Ok(<#ty as canyon_sql::core::Transaction<#ty>>::query( +// stmt, +// &[lookage_value], +// datasource_name +// ).await?.into_results::<#ty>()) +// } +// }, +// )); +// } +// } + +// rev_fk_quotes +// } \ No newline at end of file diff --git a/canyon_macros/src/query_operations/macro_builder.rs b/canyon_macros/src/query_operations/macro_builder.rs deleted file mode 100644 index eb5e3fe2..00000000 --- a/canyon_macros/src/query_operations/macro_builder.rs +++ /dev/null @@ -1,230 +0,0 @@ -use quote::quote; -use syn::{Ident, Type}; - -/// Builder for constructing CRUD operation metadata -pub struct OperationBuilder { - operation_name: Option, - fn_name: Option, - datasource_param: Option, - datasource_arg: Option, - return_type: Option, - base_doc_comment: Option, - doc_comment: Option, - body_tokens: Option, - with_unwrap: bool, -} - -impl OperationBuilder { - /// Creates a new builder instance - pub fn new() -> Self { - Self { - operation_name: None, - fn_name: None, - datasource_param: None, - datasource_arg: None, - return_type: None, - base_doc_comment: None, - doc_comment: None, - body_tokens: None, - with_unwrap: false, - } - } - - /// Sets the name of the operation - pub fn operation_name(mut self, name: &str) -> Self { - self.operation_name = Some(syn::Ident::new(name, proc_macro2::Span::call_site())); - self - } - - /// Sets the function name - pub fn fn_name(mut self, name: &str) -> Self { - self.fn_name = Some(syn::Ident::new(name, proc_macro2::Span::call_site())); - self - } - - /// Sets the datasource parameter - pub fn datasource_param(mut self, param: &str) -> Self { - self.datasource_param = Some(syn::Ident::new(param, proc_macro2::Span::call_site())); - self - } - - /// Sets the datasource argument - pub fn datasource_arg(mut self, arg: &str) -> Self { - self.datasource_arg = Some(syn::Ident::new(arg, proc_macro2::Span::call_site())); - self - } - - /// Sets the return type - pub fn return_type(mut self, ty: Type) -> Self { - self.return_type = Some(ty); - self - } - - /// Adds a base doc comment - pub fn base_doc_comment(mut self, comment: &str) -> Self { - self.base_doc_comment = Some(comment.to_string()); - self - } - - /// Adds an additional doc comment - pub fn doc_comment(mut self, comment: &str) -> Self { - self.doc_comment = Some(comment.to_string()); - self - } - - /// Sets the body of the function - pub fn body_tokens(mut self, tokens: proc_macro2::TokenStream) -> Self { - self.body_tokens = Some(tokens); - self - } - - /// Configures whether to use `.unwrap()` - pub fn with_unwrap(mut self, unwrap: bool) -> Self { - self.with_unwrap = unwrap; - self - } - - /// Finalizes the builder and returns the operation - pub fn build(self) -> Operation { - Operation { - operation_name: self.operation_name.unwrap(), - fn_name: self.fn_name.unwrap(), - datasource_param: self.datasource_param.unwrap(), - datasource_arg: self.datasource_arg.unwrap(), - return_type: self.return_type.unwrap(), - base_doc_comment: self.base_doc_comment.unwrap(), - doc_comment: self.doc_comment.unwrap(), - body_tokens: self.body_tokens.unwrap(), - with_unwrap: self.with_unwrap, - } - } -} - -/// Represents a fully constructed CRUD operation -pub struct Operation { - pub operation_name: Ident, - pub fn_name: Ident, - pub datasource_param: Ident, - pub datasource_arg: Ident, - pub return_type: Type, - pub base_doc_comment: String, - pub doc_comment: String, - pub body_tokens: proc_macro2::TokenStream, - pub with_unwrap: bool, -} - -impl Operation { - /// Generates the final `quote!` tokens for this operation - pub fn generate_tokens(&self) -> proc_macro2::TokenStream { - let base_doc_comment = &self.base_doc_comment; - let doc_comment = &self.doc_comment; - let fn_name = &self.fn_name; - let datasource_param = &self.datasource_param; - let return_type = &self.return_type; - let body_tokens = &self.body_tokens; - let unwrap_tokens = if self.with_unwrap { - quote! { .unwrap() } - } else { - quote! {} - }; - - quote! { - #[doc = #base_doc_comment] - #[doc = #doc_comment] - async fn #fn_name(#datasource_param) -> #return_type { - #body_tokens - #unwrap_tokens - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; // Import your structs and builder - use quote::quote; - use syn::parse_quote; - - #[test] - fn test_find_operation_tokens() { - // Arrange: Build the operation - let find_operation = OperationBuilder::new() - .operation_name("find") - .fn_name("find_user_by_id") - .datasource_param(parse_quote!(datasource)) - .datasource_arg(quote! { datasource_arg }) - .return_type((Result)) - .base_doc_comment("Finds a user by their ID.") - .doc_comment("This operation retrieves a single user record based on the provided ID.") - .query_string("SELECT * FROM users WHERE id = ?") - .input_parameters(quote! { &[id] }) - .parameterized(true) - .with_unwrap(false) - .build(); - - // Act: Generate tokens - let generated_tokens = find_operation.generate_tokens(); - - // Assert: Compare against expected tokens - let expected_tokens = quote! { - #[doc = "Finds a user by their ID."] - #[doc = "This operation retrieves a single user record based on the provided ID."] - async fn find_user_by_id(datasource) -> Result { - >::query( - "SELECT * FROM users WHERE id = ?", - &[id], - datasource_arg - ).await - .into_results::() - } - }; - - assert_eq!( - generated_tokens.to_string(), - expected_tokens.to_string(), - "Generated tokens do not match expected tokens!" - ); - } - - #[test] - fn test_insert_operation_tokens() { - // Arrange: Build the operation - let insert_operation = OperationBuilder::new() - .operation_name("insert") - .fn_name("insert_user") - .datasource_param(parse_quote!(datasource)) - .datasource_arg(quote! { datasource_arg }) - .return_type(parse_quote!(Result<(), Error>)) - .base_doc_comment("Inserts a new user into the database.") - .doc_comment("This operation inserts a new user record with the provided data.") - .query_string("INSERT INTO users (name, email) VALUES (?, ?)") - .input_parameters(quote! { &dyn QueryParameters }) - .parameterized(true) - .with_unwrap(false) - .build(); - - // Act: Generate tokens - let generated_tokens = insert_operation.generate_tokens(); - - // Assert: Compare against expected tokens - let expected_tokens = quote! { - #[doc = "Inserts a new user into the database."] - #[doc = "This operation inserts a new user record with the provided data."] - async fn insert_user(datasource) -> Result<(), Error> { - >::query( - "INSERT INTO users (name, email) VALUES (?, ?)", - &dyn QueryParameters, - datasource_arg - ).await - .into_results::() - } - }; - - assert_eq!( - generated_tokens.to_string(), - expected_tokens.to_string(), - "Generated tokens do not match expected tokens!" - ); - } -} - diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index a45785cc..5fe01647 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -80,10 +80,20 @@ impl MacroOperationBuilder { self.user_type = Some(ty.clone()); self } - - fn get_lifetime(&self) -> TokenStream { - if self.lifetime { + + fn compose_fn_signature_generics(&self) -> TokenStream { + if !&self.lifetime && self.input_param.is_none() { + quote!{} + } else if self.lifetime && self.input_param.is_none() { quote! { <'a> } + } else { + quote! { <'a, I> } + } + } + + fn compose_params_separator(&self) -> TokenStream { + if self.input_parameters.is_some() && self.input_param.is_some() { + quote! {, } } else { quote! {} } @@ -99,7 +109,7 @@ impl MacroOperationBuilder { let ds_arg0 = input_arg; quote! { #ds_arg0 } } else { - quote! { "" } + quote! { &"" } } } @@ -109,11 +119,14 @@ impl MacroOperationBuilder { } pub fn with_input_param(mut self) -> Self { - self.input_param = Some(quote! { input: I }); + self.input_param = Some(quote! { input: &'a I }); self.input_fwd_arg = Some(quote! { input }); self.lifetime = true; self.where_clause_bounds - .push(quote! { I: Into> + Sync + Send + 'a }); + .push(quote! { + I: Into> + Sync + Send + 'a, + canyon_sql::core::TransactionInput<'a>: From<&'a I> + }); self } @@ -246,7 +259,7 @@ impl MacroOperationBuilder { let ty = self.get_user_type(); let fn_name = self.get_fn_name(); - let lifetime = self.get_lifetime(); // TODO: generics instead + let generics = self.compose_fn_signature_generics(); let input_param = self.get_input_param(); let input_fwd_arg = self.get_input_arg(); // TODO: replace @@ -283,19 +296,11 @@ impl MacroOperationBuilder { base_body_tokens }; - let separate_params = if self.input_parameters.is_some() && self.input_param.is_some() // TODO: - // change - // for - // getter? - { - quote! {, } - } else { - quote! {} - }; + let separate_params = self.compose_params_separator(); quote! { #(#doc_comments)* - async fn #fn_name #lifetime(#fn_parameters #separate_params #input_param) -> #return_type + async fn #fn_name #generics(#fn_parameters #separate_params #input_param) -> #return_type #where_clause { #body_tokens @@ -304,188 +309,3 @@ impl MacroOperationBuilder { } } } - -#[cfg(test)] -mod tests { - use super::*; - use quote::quote; - use syn::parse_quote; - - #[test] - fn test_find_operation_tokens() { - let user_type = Ident::new("User", Span::call_site()); - - let find_operation = MacroOperationBuilder::new() - .fn_name("find_user_by_id") - .user_type(&user_type) - .with_input_param() - .return_type(&user_type) - .add_doc_comment("Finds a user by their ID.") - .add_doc_comment( - "This operation retrieves a single user record based on the provided ID.", - ) - .query_string("SELECT * FROM users WHERE id = ?") - .input_parameters(quote! { id: &dyn QueryParameters<'_> }) - .forwarded_parameters(quote! { &[id] }) - .single_result(); - - let generated_tokens = find_operation.generate_tokens(); - let expected_tokens = quote! { - #[doc = "Finds a user by their ID."] - #[doc = "This operation retrieves a single user record based on the provided ID."] - async fn find_user_by_id<'a>(id: &dyn QueryParameters<'_>, input: I) - where I: Into> + Sync + Send + 'aResult, Box<(dyn std::error::Error + Send + Sync + 'a)> > { - >::query( - "SELECT * FROM users WHERE id = ?", - &[id], - datasource_name - ).await - .into_results::() - } - }; - - assert_eq!(generated_tokens.to_string(), expected_tokens.to_string()); - } - - #[test] - fn test_find_all_operation_tokens() { - let user_type = Ident::new("User", Span::call_site()); - - let find_operation = MacroOperationBuilder::new() - .fn_name("find_all") - .user_type(&user_type) - .return_type(&user_type) - .add_doc_comment("Executes a 'SELECT * FROM '") - .add_doc_comment( - "This operation retrieves all the users records stored with the default datasource", - ) - .query_string("SELECT * FROM users"); - - let generated_tokens = find_operation.generate_tokens(); - let expected_tokens = quote! { - #[doc = "Executes a 'SELECT * FROM '"] - #[doc = "This operation retrieves all the users records stored with the default datasource"] - async fn find_all() -> Result, Box<(dyn std::error::Error + Send + Sync)> > { - >::query( - "SELECT * FROM users", - &[], - "" - ).await - .into_results::() - } - }; - - assert_eq!(generated_tokens.to_string(), expected_tokens.to_string()); - } - - #[test] - fn test_find_all_with_operation_tokens() { - let user_type = Ident::new("User", Span::call_site()); - - let find_operation = MacroOperationBuilder::new() - .fn_name("find_all_with") - .with_input_param() - .user_type(&user_type) - .return_type(&user_type) - .add_doc_comment("Executes a 'SELECT * FROM '") - .add_doc_comment( - "This operation retrieves all the users records stored in the provided datasource", - ) - .query_string("SELECT * FROM users"); - - let generated_tokens = find_operation.generate_tokens(); - let expected_tokens = quote! { - #[doc = "Executes a 'SELECT * FROM '"] - #[doc = "This operation retrieves all the users records stored in the provided datasource"] - async fn find_all_with<'a, I>(input: I) - where I: Into> + Sync + Send + 'aResult, Box<(dyn std::error::Error + Send + Sync + 'a)> > { - >::query( - "SELECT * FROM users", - &[], - datasource_name - ).await - .into_results::() - } - }; - - assert_eq!(generated_tokens.to_string(), expected_tokens.to_string()); - } - - // #[test] - // fn test_find_operation_tokens() { - // // Arrange: Build the operation - // let find_operation = MacroOperationBuilder::new() - // .fn_name("find_user_by_id") - // .datasource_param(parse_quote!(datasource)) - // .datasource_arg(quote! { datasource_arg }) - // .return_type(parse_quote!(Result)) - // .base_doc_comment("Finds a user by their ID.") - // .doc_comment("This operation retrieves a single user record based on the provided ID.") - // .query_string("SELECT * FROM users WHERE id = ?") - // .input_parameters(quote! { &[id] }) - // // .parameterized(true) - // .with_unwrap(false); - - // // Act: Generate tokens - // let generated_tokens = find_operation.generate_tokens(); - - // // Assert: Compare against expected tokens - // let expected_tokens = quote! { - // #[doc = "Finds a user by their ID."] - // #[doc = "This operation retrieves a single user record based on the provided ID."] - // async fn find_user_by_id(datasource) -> Result { - // >::query( - // "SELECT * FROM users WHERE id = ?", - // &[id], - // datasource_arg - // ).await - // .into_results::() - // } - // }; - - // assert_eq!( - // generated_tokens.to_string(), - // expected_tokens.to_string(), - // "Generated tokens do not match expected tokens!" - // ); - // } - - // #[test] - // fn test_insert_operation_tokens() { - // // Arrange: Build the operation - // let insert_operation = MacroOperationBuilder::new() - // .fn_name("insert_user") - // .datasource_param(parse_quote!(datasource)) - // .datasource_arg(quote! { datasource_arg }) - // .return_type(parse_quote!(Result<(), Error>)) - // .base_doc_comment("Inserts a new user into the database.") - // .doc_comment("This operation inserts a new user record with the provided data.") - // .query_string("INSERT INTO users (name, email) VALUES (?, ?)") - // .input_parameters(quote! { &dyn QueryParameters }) - // // .parameterized(true) - // .with_unwrap(false); - - // // Act: Generate tokens - // let generated_tokens = insert_operation.generate_tokens(); - - // // Assert: Compare against expected tokens - // let expected_tokens = quote! { - // #[doc = "Inserts a new user into the database."] - // #[doc = "This operation inserts a new user record with the provided data."] - // async fn insert_user(datasource) -> Result<(), Error> { - // >::query( - // "INSERT INTO users (name, email) VALUES (?, ?)", - // &dyn QueryParameters, - // datasource_arg - // ).await - // .into_results::() - // } - // }; - - // assert_eq!( - // generated_tokens.to_string(), - // expected_tokens.to_string(), - // "Generated tokens do not match expected tokens!" - // ); - // } -} diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index 5eb1c6c5..25d6561e 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -1,7 +1,8 @@ pub mod delete; pub mod insert; pub mod select; +pub mod foreign_key; pub mod update; mod macro_template; -mod doc_comments; \ No newline at end of file +mod doc_comments; diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index 431cd92d..65ef67ed 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -51,33 +51,34 @@ pub fn generate_find_all_query_tokens( let ty = macro_data.ty; quote! { - /// Generates a [`canyon_sql::query::SelectQueryBuilder`] - /// that allows you to customize the query by adding parameters and constrains dynamically. - /// - /// It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your - /// entity but converted to the corresponding database convention, - /// unless concrete values are set on the available parameters of the - /// `canyon_macro(table_name = "table_name", schema = "schema")` - fn select_query<'a>() -> canyon_sql::query::SelectQueryBuilder<'a, #ty, &str> { - canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, "") - } - - /// Generates a [`canyon_sql::query::SelectQueryBuilder`] - /// that allows you to customize the query by adding parameters and constrains dynamically. - /// - /// It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your - /// entity but converted to the corresponding database convention, - /// unless concrete values are set on the available parameters of the - /// `canyon_macro(table_name = "table_name", schema = "schema")` - /// - /// The query it's made against the database with the configured datasource - /// described in the configuration file, and selected with the [`&str`] - /// passed as parameter. - fn select_query_datasource<'a, I>(input: I) -> canyon_sql::query::SelectQueryBuilder<'a, #ty, I> - where I: Into> + Sync + Send + 'a - { - canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, datasource_name) - } + // Generates a [`canyon_sql::query::SelectQueryBuilder`] + // that allows you to customize the query by adding parameters and constrains dynamically. + // + // It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your + // entity but converted to the corresponding database convention, + // unless concrete values are set on the available parameters of the + // `canyon_macro(table_name = "table_name", schema = "schema")` + // fn select_query<'a>() -> canyon_sql::query::SelectQueryBuilder<'a, #ty, &'a str> { + // canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, "") + // } + + // Generates a [`canyon_sql::query::SelectQueryBuilder`] + // that allows you to customize the query by adding parameters and constrains dynamically. + // + // It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your + // entity but converted to the corresponding database convention, + // unless concrete values are set on the available parameters of the + // `canyon_macro(table_name = "table_name", schema = "schema")` + // + // The query it's made against the database with the configured datasource + // described in the configuration file, and selected with the [`&str`] + // passed as parameter. + // fn select_query_with<'a, I>(input: &'a I) -> canyon_sql::query::SelectQueryBuilder<'a, #ty, I> + // where I: Into> + Sync + Send + 'a, + // canyon_sql::core::TransactionInput<'a>: From<&'a I> + // { + // canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, input) + // } } } @@ -97,7 +98,6 @@ fn generate_find_by_pk_tokens( return quote! { async fn find_by_pk<'a>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> - where I: Into> + Sync + Send + 'a { Err( std::io::Error::new( @@ -111,9 +111,9 @@ fn generate_find_by_pk_tokens( async fn find_by_pk_with<'a, I>( value: &'a dyn canyon_sql::core::QueryParameter<'a>, - input: I + input: &'a I ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> - where I: Into> + Sync + Send + 'a + where I: Into> + Sync + Send + 'a { Err( std::io::Error::new( @@ -136,194 +136,14 @@ fn generate_find_by_pk_tokens( }; let find_by_pk = create_find_by_pk_macro(ty, &stmt, &result_handling); - let find_by_pk_ds = create_find_by_pk_with(ty, &stmt, &result_handling); + let find_by_pk_with = create_find_by_pk_with(ty, &stmt, &result_handling); quote! { #find_by_pk - #find_by_pk_ds + #find_by_pk_with } } -// /// Generates the TokenStream for build the search by foreign key feature, also as a method instance -// /// of a T type of as an associated function of same T type, but wrapped as a Result, representing -// /// a possible failure querying the database, a bad or missing FK annotation or a missed ForeignKeyable -// /// derive macro on the parent side of the relation -// pub fn generate_find_by_foreign_key_tokens( -// macro_data: &MacroTokens<'_>, -// ) -> Vec<(TokenStream, TokenStream)> { -// let mut fk_quotes: Vec<(TokenStream, TokenStream)> = Vec::new(); - -// for (field_ident, fk_annot) in macro_data.get_fk_annotations().iter() { -// if let EntityFieldAnnotation::ForeignKey(table, column) = fk_annot { -// let method_name = "search_".to_owned() + table; - -// // TODO this is not a good implementation. We must try to capture the -// // related entity in some way, and compare it with something else -// let fk_ty = database_table_name_to_struct_ident(table); - -// // Generate and identifier for the method based on the convention of "search_related_types" -// // where types is a placeholder for the plural name of the type referenced -// let method_name_ident = -// proc_macro2::Ident::new(&method_name, proc_macro2::Span::call_site()); -// let method_name_ident_ds = proc_macro2::Ident::new( -// &format!("{}_with", &method_name), -// proc_macro2::Span::call_site(), -// ); -// let quoted_method_signature: TokenStream = quote! { -// async fn #method_name_ident(&self) -> -// Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> -// }; -// let quoted_with_method_signature: TokenStream = quote! { -// async fn #method_name_ident_ds<'a>(&self, input: I) -> -// Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> -// }; - -// let stmt = format!( -// "SELECT * FROM {} WHERE {} = $1", -// table, -// format!("\"{column}\"").as_str(), -// ); -// let result_handler = quote! { -// match result { -// n if n.len() == 0 => Ok(None), -// _ => Ok(Some( -// result.into_results::<#fk_ty>().remove(0) -// )) -// } -// }; - -// fk_quotes.push(( -// quote! { #quoted_method_signature; }, -// quote! { -// /// Searches the parent entity (if exists) for this type -// #quoted_method_signature { -// let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( -// #stmt, -// &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], -// "" -// ).await?; - -// #result_handler -// } -// }, -// )); - -// fk_quotes.push(( -// quote! { #quoted_with_method_signature; }, -// quote! { -// /// Searches the parent entity (if exists) for this type with the specified datasource -// #quoted_with_method_signature { -// let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( -// #stmt, -// &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], -// datasource_name -// ).await?; - -// #result_handler -// } -// }, -// )); -// } -// } - -// fk_quotes -// } - -// /// Generates the TokenStream for build the __search_by_foreign_key() CRUD -// /// associated function, but wrapped as a Result, representing -// /// a possible failure querying the database, a bad or missing FK annotation or a missed ForeignKeyable -// /// derive macro on the parent side of the relation -// pub fn generate_find_by_reverse_foreign_key_tokens( -// macro_data: &MacroTokens<'_>, -// table_schema_data: &String, -// ) -> Vec<(TokenStream, TokenStream)> { -// let mut rev_fk_quotes: Vec<(TokenStream, TokenStream)> = Vec::new(); -// let ty = macro_data.ty; - -// for (field_ident, fk_annot) in macro_data.get_fk_annotations().iter() { -// if let EntityFieldAnnotation::ForeignKey(table, column) = fk_annot { -// let method_name = format!("search_{table}_childrens"); - -// // Generate and identifier for the method based on the convention of "search_by__" (note the double underscore) -// // plus the 'table_name' property of the ForeignKey annotation -// let method_name_ident = -// proc_macro2::Ident::new(&method_name, proc_macro2::Span::call_site()); -// let method_name_ident_ds = proc_macro2::Ident::new( -// &format!("{}_with", &method_name), -// proc_macro2::Span::call_site(), -// ); -// let quoted_method_signature: TokenStream = quote! { -// async fn #method_name_ident<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send>(value: &F) -> -// Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> -// }; -// let quoted_with_method_signature: TokenStream = quote! { -// async fn #method_name_ident_ds<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send> -// (value: &F, input: I) -> -// Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> -// }; - -// let f_ident = field_ident.to_string(); - -// rev_fk_quotes.push(( -// quote! { #quoted_method_signature; }, -// quote! { -// /// Given a parent entity T annotated with the derive proc macro `ForeignKeyable`, -// /// performns a search to find the children that belong to that concrete parent. -// #quoted_method_signature -// { -// let lookage_value = value.get_fk_column(#column) -// .expect(format!( -// "Column: {:?} not found in type: {:?}", #column, #table -// ).as_str()); - -// let stmt = format!( -// "SELECT * FROM {} WHERE {} = $1", -// #table_schema_data, -// format!("\"{}\"", #f_ident).as_str() -// ); - -// Ok(<#ty as canyon_sql::core::Transaction<#ty>>::query( -// stmt, -// &[lookage_value], -// "" -// ).await?.into_results::<#ty>()) -// } -// }, -// )); - -// rev_fk_quotes.push(( -// quote! { #quoted_with_method_signature; }, -// quote! { -// /// Given a parent entity T annotated with the derive proc macro `ForeignKeyable`, -// /// performns a search to find the children that belong to that concrete parent -// /// with the specified datasource. -// #quoted_with_method_signature -// { -// let lookage_value = value.get_fk_column(#column) -// .expect(format!( -// "Column: {:?} not found in type: {:?}", #column, #table -// ).as_str()); - -// let stmt = format!( -// "SELECT * FROM {} WHERE {} = $1", -// #table_schema_data, -// format!("\"{}\"", #f_ident).as_str() -// ); - -// Ok(<#ty as canyon_sql::core::Transaction<#ty>>::query( -// stmt, -// &[lookage_value], -// datasource_name -// ).await?.into_results::<#ty>()) -// } -// }, -// )); -// } -// } - -// rev_fk_quotes -// } - mod __details { use crate::query_operations::{doc_comments, macro_template::MacroOperationBuilder}; use proc_macro2::Span; @@ -537,7 +357,9 @@ mod macro_builder_read_ops_tests { const MAPS_TO: &str = "into_results :: < User > ()"; const LT_CONSTRAINT: &str = "< 'a >"; - const DS_PARAM: &str = "datasource_name : & 'a str"; + const INPUT_PARAM: &str = "input : & 'a I"; + + const WITH_WHERE_BOUNDS: &str = "where I : Into < canyon_sql::core::TransactionInput < 'a >> + Sync + Send + 'a "; #[test] fn test_macro_builder_find_all() { @@ -553,12 +375,12 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_find_all_with() { let ty: Ident = Ident::new("User", Span::call_site()); let find_all_builder = create_find_all_with_macro(&ty, SELECT_ALL_STMT); - let find_all_ds = find_all_builder.generate_tokens().to_string(); + let find_all_with = find_all_builder.generate_tokens().to_string(); - assert!(find_all_ds.contains("async fn find_all_with")); - assert!(find_all_ds.contains(RES_RET_TY_LT)); - assert!(find_all_ds.contains(LT_CONSTRAINT)); - assert!(find_all_ds.contains(DS_PARAM)); + assert!(find_all_with.contains("async fn find_all_with")); + assert!(find_all_with.contains(RES_RET_TY_LT)); + assert!(find_all_with.contains(LT_CONSTRAINT)); + assert!(find_all_with.contains(WITH_WHERE_BOUNDS)); } #[test] @@ -575,12 +397,12 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_find_all_unchecked_with() { let ty: Ident = Ident::new("User", Span::call_site()); let find_all_unc_with_builder = create_find_all_unchecked_with_macro(&ty, SELECT_ALL_STMT); - let find_all_unc_ds = find_all_unc_with_builder.generate_tokens().to_string(); + let find_all_unc_with = find_all_unc_with_builder.generate_tokens().to_string(); - assert!(find_all_unc_ds.contains("async fn find_all_unchecked_with")); - assert!(find_all_unc_ds.contains(RAW_RET_TY)); - assert!(find_all_unc_ds.contains(LT_CONSTRAINT)); - assert!(find_all_unc_ds.contains(DS_PARAM)); + assert!(find_all_unc_with.contains("async fn find_all_unchecked_with")); + assert!(find_all_unc_with.contains(RAW_RET_TY)); + assert!(find_all_unc_with.contains(LT_CONSTRAINT)); + assert!(find_all_unc_with.contains(INPUT_PARAM)); } #[test] @@ -597,12 +419,12 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_count_with() { let ty: Ident = Ident::new("User", Span::call_site()); let count_with_builder = create_count_with_macro(&ty, COUNT_STMT); - let count_ds = count_with_builder.generate_tokens().to_string(); + let count_with = count_with_builder.generate_tokens().to_string(); - assert!(count_ds.contains("async fn count_with")); - assert!(count_ds.contains("Result < i64")); - assert!(count_ds.contains(LT_CONSTRAINT)); - assert!(count_ds.contains(DS_PARAM)); + assert!(count_with.contains("async fn count_with")); + assert!(count_with.contains("Result < i64")); + assert!(count_with.contains(LT_CONSTRAINT)); + assert!(count_with.contains(INPUT_PARAM)); } #[test] @@ -620,13 +442,13 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_find_by_pk_with() { let ty: Ident = Ident::new("User", Span::call_site()); let find_by_pk_with_builder = create_find_by_pk_with(&ty, FIND_BY_PK_STMT, "e! {}); - let find_by_pk_ds = find_by_pk_with_builder.generate_tokens().to_string(); - println!("{:?}", find_by_pk_ds.split("\n").collect::>()); + let find_by_pk_with = find_by_pk_with_builder.generate_tokens().to_string(); + println!("{:?}", find_by_pk_with.split("\n").collect::>()); - assert!(find_by_pk_ds.contains("async fn find_by_pk_with")); - assert!(find_by_pk_ds.contains(LT_CONSTRAINT)); - assert!(find_by_pk_ds.contains(DS_PARAM)); - assert!(find_by_pk_ds.contains(OPT_RET_TY_LT)); + assert!(find_by_pk_with.contains("async fn find_by_pk_with")); + assert!(find_by_pk_with.contains(LT_CONSTRAINT)); + assert!(find_by_pk_with.contains(INPUT_PARAM)); + assert!(find_by_pk_with.contains(OPT_RET_TY_LT)); } } diff --git a/src/lib.rs b/src/lib.rs index 6c085d9b..cb0d2725 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,7 @@ pub mod core { pub use canyon_core::mapper::*; pub use canyon_core::query::DbConnection; pub use canyon_core::query::Transaction; + pub use canyon_core::query::TransactionInput; pub use canyon_core::query_parameters::QueryParameter; pub use canyon_core::rows::CanyonRows; } diff --git a/tests/crud/init_mssql.rs b/tests/crud/init_mssql.rs index 2f4f43a9..c7630478 100644 --- a/tests/crud/init_mssql.rs +++ b/tests/crud/init_mssql.rs @@ -1,62 +1,62 @@ -use crate::constants::SQL_SERVER_CREATE_TABLES; -use crate::constants::SQL_SERVER_DS; -use crate::constants::SQL_SERVER_FILL_TABLE_VALUES; -use crate::tests_models::league::League; - -use canyon_sql::crud::CrudOperations; -use canyon_sql::db_clients::tiberius::{Client, Config}; -use canyon_sql::runtime::tokio::net::TcpStream; -use canyon_sql::runtime::tokio_util::compat::TokioAsyncWriteCompatExt; - -/// In order to initialize data on `SqlServer`. we must manually insert it -/// when the docker starts. SqlServer official docker from Microsoft does -/// not allow you to run `.sql` files against the database (not at least, without) -/// using a workaround. So, we are going to query the `SqlServer` to check if already -/// has some data (other processes, persistence or multi-threading envs), af if not, -/// we are going to retrieve the inserted data on the `postgreSQL` at start-up and -/// inserting into the `SqlServer` instance. -/// -/// This will be marked as `#[ignore]`, so we can force to run first the marked as -/// ignored, check the data available, perform the necessary init operations and -/// then *cargo test * the real integration tests -#[canyon_sql::macros::canyon_tokio_test] -#[ignore] -fn initialize_sql_server_docker_instance() { - static CONN_STR: &str = // TODO: change this for the DS when will be in the public API - "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true"; - - canyon_sql::runtime::futures::executor::block_on(async { - let config = Config::from_ado_string(CONN_STR).unwrap(); - - let tcp = TcpStream::connect(config.get_addr()).await.unwrap(); - let tcp2 = TcpStream::connect(config.get_addr()).await.unwrap(); - tcp.set_nodelay(true).ok(); - - let mut client = Client::connect(config.clone(), tcp.compat_write()) - .await - .unwrap(); - - // Create the tables - let query_result = client.query(SQL_SERVER_CREATE_TABLES, &[]).await; - assert!(query_result.is_ok()); - - let leagues_sql = League::find_all_with(SQL_SERVER_DS).await; - println!("LSQL ERR: {leagues_sql:?}"); - assert!(leagues_sql.is_ok()); - - match leagues_sql { - Ok(ref leagues) => { - let leagues_len = leagues.len(); - println!("Leagues already inserted on SQLSERVER: {:?}", &leagues_len); - if leagues.len() < 10 { - let mut client2 = Client::connect(config, tcp2.compat_write()) - .await - .expect("Can't connect to MSSQL"); - let result = client2.query(SQL_SERVER_FILL_TABLE_VALUES, &[]).await; - assert!(result.is_ok()); - } - } - Err(e) => eprintln!("Error retrieving the leagues: {e}"), - } - }); -} +// use crate::constants::SQL_SERVER_CREATE_TABLES; +// use crate::constants::SQL_SERVER_DS; +// use crate::constants::SQL_SERVER_FILL_TABLE_VALUES; +// use crate::tests_models::league::League; +// +// use canyon_sql::crud::CrudOperations; +// use canyon_sql::db_clients::tiberius::{Client, Config}; +// use canyon_sql::runtime::tokio::net::TcpStream; +// use canyon_sql::runtime::tokio_util::compat::TokioAsyncWriteCompatExt; +// +// /// In order to initialize data on `SqlServer`. we must manually insert it +// /// when the docker starts. SqlServer official docker from Microsoft does +// /// not allow you to run `.sql` files against the database (not at least, without) +// /// using a workaround. So, we are going to query the `SqlServer` to check if already +// /// has some data (other processes, persistence or multi-threading envs), af if not, +// /// we are going to retrieve the inserted data on the `postgreSQL` at start-up and +// /// inserting into the `SqlServer` instance. +// /// +// /// This will be marked as `#[ignore]`, so we can force to run first the marked as +// /// ignored, check the data available, perform the necessary init operations and +// /// then *cargo test * the real integration tests +// #[canyon_sql::macros::canyon_tokio_test] +// #[ignore] +// fn initialize_sql_server_docker_instance() { +// static CONN_STR: &str = // TODO: change this for the DS when will be in the public API +// "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true"; +// +// canyon_sql::runtime::futures::executor::block_on(async { +// let config = Config::from_ado_string(CONN_STR).unwrap(); +// +// let tcp = TcpStream::connect(config.get_addr()).await.unwrap(); +// let tcp2 = TcpStream::connect(config.get_addr()).await.unwrap(); +// tcp.set_nodelay(true).ok(); +// +// let mut client = Client::connect(config.clone(), tcp.compat_write()) +// .await +// .unwrap(); +// +// // Create the tables +// let query_result = client.query(SQL_SERVER_CREATE_TABLES, &[]).await; +// assert!(query_result.is_ok()); +// +// let leagues_sql = League::find_all_with(&SQL_SERVER_DS).await; +// println!("LSQL ERR: {leagues_sql:?}"); +// assert!(leagues_sql.is_ok()); +// +// match leagues_sql { +// Ok(ref leagues) => { +// let leagues_len = leagues.len(); +// println!("Leagues already inserted on SQLSERVER: {:?}", &leagues_len); +// if leagues.len() < 10 { +// let mut client2 = Client::connect(config, tcp2.compat_write()) +// .await +// .expect("Can't connect to MSSQL"); +// let result = client2.query(SQL_SERVER_FILL_TABLE_VALUES, &[]).await; +// assert!(result.is_ok()); +// } +// } +// Err(e) => eprintln!("Error retrieving the leagues: {e}"), +// } +// }); +// } diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index 45268000..62b8e2ab 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -1,457 +1,457 @@ -#[cfg(feature = "mysql")] -use crate::constants::MYSQL_DS; -#[cfg(feature = "mssql")] -use crate::constants::SQL_SERVER_DS; - -/// Tests for the QueryBuilder available operations within Canyon. -/// -/// QueryBuilder are the way of obtain more flexibility that with -/// the default generated queries, essentially for build the queries -/// with the SQL filters -/// -use canyon_sql::{ - crud::CrudOperations, - query::{operators::Comp, operators::Like, ops::QueryBuilder}, -}; - -use crate::tests_models::league::*; -use crate::tests_models::player::*; -use crate::tests_models::tournament::*; - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[canyon_sql::macros::canyon_tokio_test] -fn test_generated_sql_by_the_select_querybuilder() { - let mut select_with_joins = League::select_query(); - select_with_joins - .inner_join("tournament", "league.id", "tournament.league_id") - .left_join("team", "tournament.id", "player.tournament_id") - .r#where(LeagueFieldValue::id(&7), Comp::Gt) - .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) - .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); - // .query() - // .await; - // NOTE: We don't have in the docker the generated relationships - // with the joins, so for now, we are just going to check that the - // generated SQL by the SelectQueryBuilder is the spected - assert_eq!( - select_with_joins.read_sql(), - "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder() { - // Find all the leagues with ID less or equals that 7 - // and where it's region column value is equals to 'Korea' - let filtered_leagues_result: Result, _> = League::select_query() - .r#where(LeagueFieldValue::id(&50), Comp::LtEq) - .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) - .query() - .await; - - let filtered_leagues: Vec = filtered_leagues_result.unwrap(); - assert!(!filtered_leagues.is_empty()); - - let league_idx_0 = filtered_leagues.first().unwrap(); - assert_eq!(league_idx_0.id, 34); - assert_eq!(league_idx_0.region, "KOREA"); -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_fulllike() { - // Find all the leagues with "LC" in their name - let mut filtered_leagues_result = League::select_query(); - filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_fulllike_with_mssql() { - // Find all the leagues with "LC" in their name - let mut filtered_leagues_result = League::select_query_with(SQL_SERVER_DS); - filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_fulllike_with_mysql() { - // Find all the leagues with "LC" in their name - let mut filtered_leagues_result = League::select_query_with(MYSQL_DS); - filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR) ,'%')" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_leftlike() { - // Find all the leagues whose name ends with "CK" - let mut filtered_leagues_result = League::select_query(); - filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_leftlike_with_mssql() { - // Find all the leagues whose name ends with "CK" - let mut filtered_leagues_result = League::select_query(); - filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_leftlike_with_mysql() { - // Find all the leagues whose name ends with "CK" - let mut filtered_leagues_result = League::select_query_with(MYSQL_DS); - filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR))" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_rightlike() { - // Find all the leagues whose name starts with "LC" - let mut filtered_leagues_result = League::select_query(); - filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_rightlike_with_mssql() { - // Find all the leagues whose name starts with "LC" - let mut filtered_leagues_result = League::select_query_with(SQL_SERVER_DS); - filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_rightlike_with_mysql() { - // Find all the leagues whose name starts with "LC" - let mut filtered_leagues_result = League::select_query_with(MYSQL_DS); - filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS CHAR) ,'%')" - ) -} - -/// Same than the above but with the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_with_mssql() { - // Find all the players where its ID column value is greater that 50 - let filtered_find_players = Player::select_query_with(SQL_SERVER_DS) - .r#where(PlayerFieldValue::id(&50), Comp::Gt) - .query() - .await; - - assert!(!filtered_find_players.unwrap().is_empty()); -} - -/// Same than the above but with the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_with_mysql() { - // Find all the players where its ID column value is greater that 50 - let filtered_find_players = Player::select_query_with(MYSQL_DS) - .r#where(PlayerFieldValue::id(&50), Comp::Gt) - .query() - .await; - - assert!(!filtered_find_players.unwrap().is_empty()); -} - -// /// Updates the values of the range on entries defined by the constraint parameters -// /// in the database entity -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_with_querybuilder() { -// // Find all the leagues with ID less or equals that 7 -// // and where it's region column value is equals to 'Korea' -// let mut q = League::update_query(); -// q.set(&[ -// (LeagueField::slug, "Updated with the QueryBuilder"), -// (LeagueField::name, "Random"), -// ]) -// .r#where(LeagueFieldValue::id(&1), Comp::Gt) -// .and(LeagueFieldValue::id(&8), Comp::Lt); - -// /* NOTE: Family of QueryBuilders are clone, useful in case of need to read the generated SQL -// let qpr = q.clone(); -// println!("PSQL: {:?}", qpr.read_sql()); -// */ -// // We can now back to the original an throw the query -// q.query() -// .await -// .expect("Failed to update records with the querybuilder"); - -// let found_updated_values = League::select_query() -// .r#where(LeagueFieldValue::id(&1), Comp::Gt) -// .and(LeagueFieldValue::id(&7), Comp::Lt) -// .query() -// .await -// .expect("Failed to retrieve database League entries with the querybuilder"); - -// found_updated_values -// .iter() -// .for_each(|league| assert_eq!(league.slug, "Updated with the QueryBuilder")); -// } - -// /// Same as above, but with the specified datasource +// #[cfg(feature = "mysql")] +// use crate::constants::MYSQL_DS; // #[cfg(feature = "mssql")] +// use crate::constants::SQL_SERVER_DS; +// +// /// Tests for the QueryBuilder available operations within Canyon. +// /// +// /// QueryBuilder are the way of obtain more flexibility that with +// /// the default generated queries, essentially for build the queries +// /// with the SQL filters +// /// +// use canyon_sql::{ +// crud::CrudOperations, +// query::{operators::Comp, operators::Like, ops::QueryBuilder}, +// }; +// +// use crate::tests_models::league::*; +// use crate::tests_models::player::*; +// use crate::tests_models::tournament::*; +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_with_querybuilder_with_mssql() { -// // Find all the leagues with ID less or equals that 7 -// // and where it's region column value is equals to 'Korea' -// let mut q = Player::update_query_with(SQL_SERVER_DS); -// q.set(&[ -// (PlayerField::summoner_name, "Random updated player name"), -// (PlayerField::first_name, "I am an updated first name"), -// ]) -// .r#where(PlayerFieldValue::id(&1), Comp::Gt) -// .and(PlayerFieldValue::id(&8), Comp::Lt) -// .query() -// .await -// .expect("Failed to update records with the querybuilder"); - -// let found_updated_values = Player::select_query_with(SQL_SERVER_DS) -// .r#where(PlayerFieldValue::id(&1), Comp::Gt) -// .and(PlayerFieldValue::id(&7), Comp::LtEq) -// .query() -// .await -// .expect("Failed to retrieve database League entries with the querybuilder"); - -// found_updated_values.iter().for_each(|player| { -// assert_eq!(player.summoner_name, "Random updated player name"); -// assert_eq!(player.first_name, "I am an updated first name"); -// }); +// fn test_generated_sql_by_the_select_querybuilder() { +// let mut select_with_joins = League::select_query(); +// select_with_joins +// .inner_join("tournament", "league.id", "tournament.league_id") +// .left_join("team", "tournament.id", "player.tournament_id") +// .r#where(LeagueFieldValue::id(&7), Comp::Gt) +// .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) +// .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); +// // .query() +// // .await; +// // NOTE: We don't have in the docker the generated relationships +// // with the joins, so for now, we are just going to check that the +// // generated SQL by the SelectQueryBuilder is the spected +// assert_eq!( +// select_with_joins.read_sql(), +// "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" +// ) // } - -// /// Same as above, but with the specified datasource -// #[cfg(feature = "mysql")] +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "postgres")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_with_querybuilder_with_mysql() { +// fn test_crud_find_with_querybuilder() { // // Find all the leagues with ID less or equals that 7 // // and where it's region column value is equals to 'Korea' - -// let mut q = Player::update_query_with(MYSQL_DS); -// q.set(&[ -// (PlayerField::summoner_name, "Random updated player name"), -// (PlayerField::first_name, "I am an updated first name"), -// ]) -// .r#where(PlayerFieldValue::id(&1), Comp::Gt) -// .and(PlayerFieldValue::id(&8), Comp::Lt) -// .query() -// .await -// .expect("Failed to update records with the querybuilder"); - -// let found_updated_values = Player::select_query_with(MYSQL_DS) -// .r#where(PlayerFieldValue::id(&1), Comp::Gt) -// .and(PlayerFieldValue::id(&7), Comp::LtEq) +// let filtered_leagues_result: Result, _> = League::select_query() +// .r#where(LeagueFieldValue::id(&50), Comp::LtEq) +// .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) // .query() -// .await -// .expect("Failed to retrieve database League entries with the querybuilder"); - -// found_updated_values.iter().for_each(|player| { -// assert_eq!(player.summoner_name, "Random updated player name"); -// assert_eq!(player.first_name, "I am an updated first name"); -// }); +// .await; +// +// let filtered_leagues: Vec = filtered_leagues_result.unwrap(); +// assert!(!filtered_leagues.is_empty()); +// +// let league_idx_0 = filtered_leagues.first().unwrap(); +// assert_eq!(league_idx_0.id, 34); +// assert_eq!(league_idx_0.region, "KOREA"); // } - -// /// Deletes entries from the mapped entity `T` that are in the ranges filtered -// /// with the QueryBuilder -// /// -// /// Note if the database is persisted (not created and destroyed on every docker or -// /// GitHub Action wake up), it won't delete things that already have been deleted, -// /// but this isn't an error. They just don't exists. +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "postgres")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_with_querybuilder() { -// Tournament::delete_query() -// .r#where(TournamentFieldValue::id(&14), Comp::Gt) -// .and(TournamentFieldValue::id(&16), Comp::Lt) -// .query() -// .await -// .expect("Error connecting with the database on the delete operation"); - -// assert_eq!(Tournament::find_by_pk(&15).await.unwrap(), None); +// fn test_crud_find_with_querybuilder_and_fulllike() { +// // Find all the leagues with "LC" in their name +// let mut filtered_leagues_result = League::select_query(); +// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" +// ) // } - -// /// Same as the above delete, but with the specified datasource +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_with_querybuilder_with_mssql() { -// Player::delete_query_with(SQL_SERVER_DS) -// .r#where(PlayerFieldValue::id(&120), Comp::Gt) -// .and(PlayerFieldValue::id(&130), Comp::Lt) -// .query() -// .await -// .expect("Error connecting with the database when we are going to delete data! :)"); - -// assert!(Player::select_query_with(SQL_SERVER_DS) -// .r#where(PlayerFieldValue::id(&122), Comp::Eq) -// .query() -// .await -// .unwrap() -// .is_empty()); +// fn test_crud_find_with_querybuilder_and_fulllike_with_mssql() { +// // Find all the leagues with "LC" in their name +// let mut filtered_leagues_result = League::select_query_with(&SQL_SERVER_DS); +// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" +// ) // } - -// /// Same as the above delete, but with the specified datasource +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_with_querybuilder_with_mysql() { -// Player::delete_query_with(MYSQL_DS) -// .r#where(PlayerFieldValue::id(&120), Comp::Gt) -// .and(PlayerFieldValue::id(&130), Comp::Lt) -// .query() -// .await -// .expect("Error connecting with the database when we are going to delete data! :)"); - -// assert!(Player::select_query_with(MYSQL_DS) -// .r#where(PlayerFieldValue::id(&122), Comp::Eq) -// .query() -// .await -// .unwrap() -// .is_empty()); +// fn test_crud_find_with_querybuilder_and_fulllike_with_mysql() { +// // Find all the leagues with "LC" in their name +// let mut filtered_leagues_result = League::select_query_with(&MYSQL_DS); +// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR) ,'%')" +// ) // } - -// /// Tests for the generated SQL query after use the -// /// WHERE clause +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "postgres")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_where_clause() { -// let mut l = League::select_query(); -// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq); - -// assert_eq!(l.read_sql(), "SELECT * FROM league WHERE name = $1") +// fn test_crud_find_with_querybuilder_and_leftlike() { +// // Find all the leagues whose name ends with "CK" +// let mut filtered_leagues_result = League::select_query(); +// filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" +// ) // } - -// /// Tests for the generated SQL query after use the -// /// AND clause +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_and_clause() { -// let mut l = League::select_query(); -// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) -// .and(LeagueFieldValue::id(&10), Comp::LtEq); - +// fn test_crud_find_with_querybuilder_and_leftlike_with_mssql() { +// // Find all the leagues whose name ends with "CK" +// let mut filtered_leagues_result = League::select_query(); +// filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); +// // assert_eq!( -// l.read_sql().trim(), -// "SELECT * FROM league WHERE name = $1 AND id <= $2" +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" // ) // } - -// /// Tests for the generated SQL query after use the -// /// AND clause +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_and_clause_with_in_constraint() { -// let mut l = League::select_query(); -// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) -// .and_values_in(LeagueField::id, &[1, 7, 10]); - +// fn test_crud_find_with_querybuilder_and_leftlike_with_mysql() { +// // Find all the leagues whose name ends with "CK" +// let mut filtered_leagues_result = League::select_query_with(&MYSQL_DS); +// filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); +// // assert_eq!( -// l.read_sql().trim(), -// "SELECT * FROM league WHERE name = $1 AND id IN ($1, $2, $3)" +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR))" // ) // } - -// /// Tests for the generated SQL query after use the -// /// AND clause +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "postgres")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_or_clause() { -// let mut l = League::select_query(); -// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) -// .or(LeagueFieldValue::id(&10), Comp::LtEq); - +// fn test_crud_find_with_querybuilder_and_rightlike() { +// // Find all the leagues whose name starts with "LC" +// let mut filtered_leagues_result = League::select_query(); +// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); +// // assert_eq!( -// l.read_sql().trim(), -// "SELECT * FROM league WHERE name = $1 OR id <= $2" +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" // ) // } - -// /// Tests for the generated SQL query after use the -// /// AND clause +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_or_clause_with_in_constraint() { -// let mut l = League::select_query(); -// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) -// .or_values_in(LeagueField::id, &[1, 7, 10]); - +// fn test_crud_find_with_querybuilder_and_rightlike_with_mssql() { +// // Find all the leagues whose name starts with "LC" +// let mut filtered_leagues_result = League::select_query_with(&SQL_SERVER_DS); +// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); +// // assert_eq!( -// l.read_sql(), -// "SELECT * FROM league WHERE name = $1 OR id IN ($1, $2, $3)" +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" // ) // } - -// /// Tests for the generated SQL query after use the -// /// AND clause +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_order_by_clause() { -// let mut l = League::select_query(); -// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) -// .order_by(LeagueField::id, false); - +// fn test_crud_find_with_querybuilder_and_rightlike_with_mysql() { +// // Find all the leagues whose name starts with "LC" +// let mut filtered_leagues_result = League::select_query_with(&MYSQL_DS); +// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); +// // assert_eq!( -// l.read_sql(), -// "SELECT * FROM league WHERE name = $1 ORDER BY id" +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS CHAR) ,'%')" // ) // } +// +// /// Same than the above but with the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_with_mssql() { +// // Find all the players where its ID column value is greater that 50 +// let filtered_find_players = Player::select_query_with(&SQL_SERVER_DS) +// .r#where(PlayerFieldValue::id(&50), Comp::Gt) +// .query() +// .await; +// +// assert!(!filtered_find_players.unwrap().is_empty()); +// } +// +// /// Same than the above but with the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_with_mysql() { +// // Find all the players where its ID column value is greater that 50 +// let filtered_find_players = Player::select_query_with(&MYSQL_DS) +// .r#where(PlayerFieldValue::id(&50), Comp::Gt) +// .query() +// .await; +// +// assert!(!filtered_find_players.unwrap().is_empty()); +// } +// +// // /// Updates the values of the range on entries defined by the constraint parameters +// // /// in the database entity +// // #[cfg(feature = "postgres")] +// // #[canyon_sql::macros::canyon_tokio_test] +// // fn test_crud_update_with_querybuilder() { +// // // Find all the leagues with ID less or equals that 7 +// // // and where it's region column value is equals to 'Korea' +// // let mut q = League::update_query(); +// // q.set(&[ +// // (LeagueField::slug, "Updated with the QueryBuilder"), +// // (LeagueField::name, "Random"), +// // ]) +// // .r#where(LeagueFieldValue::id(&1), Comp::Gt) +// // .and(LeagueFieldValue::id(&8), Comp::Lt); +// +// // /* NOTE: Family of QueryBuilders are clone, useful in case of need to read the generated SQL +// // let qpr = q.clone(); +// // println!("PSQL: {:?}", qpr.read_sql()); +// // */ +// // // We can now back to the original an throw the query +// // q.query() +// // .await +// // .expect("Failed to update records with the querybuilder"); +// +// // let found_updated_values = League::select_query() +// // .r#where(LeagueFieldValue::id(&1), Comp::Gt) +// // .and(LeagueFieldValue::id(&7), Comp::Lt) +// // .query() +// // .await +// // .expect("Failed to retrieve database League entries with the querybuilder"); +// +// // found_updated_values +// // .iter() +// // .for_each(|league| assert_eq!(league.slug, "Updated with the QueryBuilder")); +// // } +// +// // /// Same as above, but with the specified datasource +// // #[cfg(feature = "mssql")] +// // #[canyon_sql::macros::canyon_tokio_test] +// // fn test_crud_update_with_querybuilder_with_mssql() { +// // // Find all the leagues with ID less or equals that 7 +// // // and where it's region column value is equals to 'Korea' +// // let mut q = Player::update_query_with(SQL_SERVER_DS); +// // q.set(&[ +// // (PlayerField::summoner_name, "Random updated player name"), +// // (PlayerField::first_name, "I am an updated first name"), +// // ]) +// // .r#where(PlayerFieldValue::id(&1), Comp::Gt) +// // .and(PlayerFieldValue::id(&8), Comp::Lt) +// // .query() +// // .await +// // .expect("Failed to update records with the querybuilder"); +// +// // let found_updated_values = Player::select_query_with(&SQL_SERVER_DS) +// // .r#where(PlayerFieldValue::id(&1), Comp::Gt) +// // .and(PlayerFieldValue::id(&7), Comp::LtEq) +// // .query() +// // .await +// // .expect("Failed to retrieve database League entries with the querybuilder"); +// +// // found_updated_values.iter().for_each(|player| { +// // assert_eq!(player.summoner_name, "Random updated player name"); +// // assert_eq!(player.first_name, "I am an updated first name"); +// // }); +// // } +// +// // /// Same as above, but with the specified datasource +// // #[cfg(feature = "mysql")] +// // #[canyon_sql::macros::canyon_tokio_test] +// // fn test_crud_update_with_querybuilder_with_mysql() { +// // // Find all the leagues with ID less or equals that 7 +// // // and where it's region column value is equals to 'Korea' +// +// // let mut q = Player::update_query_with(MYSQL_DS); +// // q.set(&[ +// // (PlayerField::summoner_name, "Random updated player name"), +// // (PlayerField::first_name, "I am an updated first name"), +// // ]) +// // .r#where(PlayerFieldValue::id(&1), Comp::Gt) +// // .and(PlayerFieldValue::id(&8), Comp::Lt) +// // .query() +// // .await +// // .expect("Failed to update records with the querybuilder"); +// +// // let found_updated_values = Player::select_query_with(&MYSQL_DS) +// // .r#where(PlayerFieldValue::id(&1), Comp::Gt) +// // .and(PlayerFieldValue::id(&7), Comp::LtEq) +// // .query() +// // .await +// // .expect("Failed to retrieve database League entries with the querybuilder"); +// +// // found_updated_values.iter().for_each(|player| { +// // assert_eq!(player.summoner_name, "Random updated player name"); +// // assert_eq!(player.first_name, "I am an updated first name"); +// // }); +// // } +// +// // /// Deletes entries from the mapped entity `T` that are in the ranges filtered +// // /// with the QueryBuilder +// // /// +// // /// Note if the database is persisted (not created and destroyed on every docker or +// // /// GitHub Action wake up), it won't delete things that already have been deleted, +// // /// but this isn't an error. They just don't exists. +// // #[cfg(feature = "postgres")] +// // #[canyon_sql::macros::canyon_tokio_test] +// // fn test_crud_delete_with_querybuilder() { +// // Tournament::delete_query() +// // .r#where(TournamentFieldValue::id(&14), Comp::Gt) +// // .and(TournamentFieldValue::id(&16), Comp::Lt) +// // .query() +// // .await +// // .expect("Error connecting with the database on the delete operation"); +// +// // assert_eq!(Tournament::find_by_pk(&15).await.unwrap(), None); +// // } +// +// // /// Same as the above delete, but with the specified datasource +// // #[cfg(feature = "mssql")] +// // #[canyon_sql::macros::canyon_tokio_test] +// // fn test_crud_delete_with_querybuilder_with_mssql() { +// // Player::delete_query_with(SQL_SERVER_DS) +// // .r#where(PlayerFieldValue::id(&120), Comp::Gt) +// // .and(PlayerFieldValue::id(&130), Comp::Lt) +// // .query() +// // .await +// // .expect("Error connecting with the database when we are going to delete data! :)"); +// +// // assert!(Player::select_query_with(&SQL_SERVER_DS) +// // .r#where(PlayerFieldValue::id(&122), Comp::Eq) +// // .query() +// // .await +// // .unwrap() +// // .is_empty()); +// // } +// +// // /// Same as the above delete, but with the specified datasource +// // #[cfg(feature = "mysql")] +// // #[canyon_sql::macros::canyon_tokio_test] +// // fn test_crud_delete_with_querybuilder_with_mysql() { +// // Player::delete_query_with(MYSQL_DS) +// // .r#where(PlayerFieldValue::id(&120), Comp::Gt) +// // .and(PlayerFieldValue::id(&130), Comp::Lt) +// // .query() +// // .await +// // .expect("Error connecting with the database when we are going to delete data! :)"); +// +// // assert!(Player::select_query_with(&MYSQL_DS) +// // .r#where(PlayerFieldValue::id(&122), Comp::Eq) +// // .query() +// // .await +// // .unwrap() +// // .is_empty()); +// // } +// +// // /// Tests for the generated SQL query after use the +// // /// WHERE clause +// // #[canyon_sql::macros::canyon_tokio_test] +// // fn test_where_clause() { +// // let mut l = League::select_query(); +// // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq); +// +// // assert_eq!(l.read_sql(), "SELECT * FROM league WHERE name = $1") +// // } +// +// // /// Tests for the generated SQL query after use the +// // /// AND clause +// // #[canyon_sql::macros::canyon_tokio_test] +// // fn test_and_clause() { +// // let mut l = League::select_query(); +// // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) +// // .and(LeagueFieldValue::id(&10), Comp::LtEq); +// +// // assert_eq!( +// // l.read_sql().trim(), +// // "SELECT * FROM league WHERE name = $1 AND id <= $2" +// // ) +// // } +// +// // /// Tests for the generated SQL query after use the +// // /// AND clause +// // #[canyon_sql::macros::canyon_tokio_test] +// // fn test_and_clause_with_in_constraint() { +// // let mut l = League::select_query(); +// // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) +// // .and_values_in(LeagueField::id, &[1, 7, 10]); +// +// // assert_eq!( +// // l.read_sql().trim(), +// // "SELECT * FROM league WHERE name = $1 AND id IN ($1, $2, $3)" +// // ) +// // } +// +// // /// Tests for the generated SQL query after use the +// // /// AND clause +// // #[canyon_sql::macros::canyon_tokio_test] +// // fn test_or_clause() { +// // let mut l = League::select_query(); +// // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) +// // .or(LeagueFieldValue::id(&10), Comp::LtEq); +// +// // assert_eq!( +// // l.read_sql().trim(), +// // "SELECT * FROM league WHERE name = $1 OR id <= $2" +// // ) +// // } +// +// // /// Tests for the generated SQL query after use the +// // /// AND clause +// // #[canyon_sql::macros::canyon_tokio_test] +// // fn test_or_clause_with_in_constraint() { +// // let mut l = League::select_query(); +// // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) +// // .or_values_in(LeagueField::id, &[1, 7, 10]); +// +// // assert_eq!( +// // l.read_sql(), +// // "SELECT * FROM league WHERE name = $1 OR id IN ($1, $2, $3)" +// // ) +// // } +// +// // /// Tests for the generated SQL query after use the +// // /// AND clause +// // #[canyon_sql::macros::canyon_tokio_test] +// // fn test_order_by_clause() { +// // let mut l = League::select_query(); +// // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) +// // .order_by(LeagueField::id, false); +// +// // assert_eq!( +// // l.read_sql(), +// // "SELECT * FROM league WHERE name = $1 ORDER BY id" +// // ) +// // } diff --git a/tests/crud/select_operations.rs b/tests/crud/select_operations.rs index cd345eda..cbd52b22 100644 --- a/tests/crud/select_operations.rs +++ b/tests/crud/select_operations.rs @@ -1,178 +1,178 @@ -#![allow(clippy::nonminimal_bool)] - -#[cfg(feature = "mysql")] -use crate::constants::MYSQL_DS; - -#[cfg(feature = "mssql")] -use crate::constants::SQL_SERVER_DS; -// Integration tests for the CRUD operations available in `Canyon` that -/// generates and executes *SELECT* statements -use crate::Error; -use canyon_sql::crud::CrudOperations; - -use crate::tests_models::league::*; -use crate::tests_models::player::*; - -/// Tests the behaviour of a SELECT * FROM {table_name} within Canyon, through the -/// `::find_all()` associated function derived with the `CanyonCrud` derive proc-macro -/// and using the *default datasource* -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_all() { - let find_all_result: Result, Box> = - League::find_all().await; - - // Connection doesn't return an error - assert!(!find_all_result.is_err()); - assert!(!find_all_result.unwrap().is_empty()); - - let find_all_players: Result, Box> = - Player::find_all().await; - assert!(!find_all_players.unwrap().is_empty()); -} - -/// Same as the `find_all()`, but with the unchecked variant, which directly returns `Vec` not -/// `Result` wrapped -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_all_unchecked() { - let find_all_result: Vec = League::find_all_unchecked().await; - assert!(!find_all_result.is_empty()); -} - -/// Tests the behaviour of a SELECT * FROM {table_name} within Canyon, through the -/// `::find_all()` associated function derived with the `CanyonCrud` derive proc-macro -/// and using the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_all_with_mssql() { - let find_all_result: Result, Box> = - League::find_all_with(SQL_SERVER_DS).await; - // Connection doesn't return an error - assert!(!find_all_result.is_err()); - assert!(!find_all_result.unwrap().is_empty()); -} - -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_all_with_mysql() { - let find_all_result: Result, Box> = - League::find_all_with(MYSQL_DS).await; - - // Connection doesn't return an error - assert!(!find_all_result.is_err()); - assert!(!find_all_result.unwrap().is_empty()); -} - -/// Same as the `find_all_with()`, but with the unchecked variant and the specified dataosource, -/// returning directly `Vec` and not `Result, Err>` -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_all_unchecked_with() { - let find_all_result: Vec = League::find_all_unchecked_with(SQL_SERVER_DS).await; - assert!(!find_all_result.is_empty()); -} - -/// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -/// defined with the #[primary_key] attribute over some field of the type. -/// -/// Uses the *default datasource*. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_by_pk() { - let find_by_pk_result: Result, Box> = - League::find_by_pk(&1).await; - assert!(find_by_pk_result.as_ref().unwrap().is_some()); - - let some_league = find_by_pk_result.unwrap().unwrap(); - assert_eq!(some_league.id, 1); - assert_eq!(some_league.ext_id, 100695891328981122_i64); - assert_eq!(some_league.slug, "european-masters"); - assert_eq!(some_league.name, "European Masters"); - assert_eq!(some_league.region, "EUROPE"); - assert_eq!( - some_league.image_url, - "http://static.lolesports.com/leagues/EM_Bug_Outline1.png" - ); -} - -/// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -/// defined with the #[primary_key] attribute over some field of the type. -/// -/// Uses the *specified datasource mssql* in the second parameter of the function call. -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_by_pk_with_mssql() { - let find_by_pk_result: Result, Box> = - League::find_by_pk_with(&27, SQL_SERVER_DS).await; - assert!(find_by_pk_result.as_ref().unwrap().is_some()); - - let some_league = find_by_pk_result.unwrap().unwrap(); - assert_eq!(some_league.id, 27); - assert_eq!(some_league.ext_id, 107898214974993351_i64); - assert_eq!(some_league.slug, "college_championship"); - assert_eq!(some_league.name, "College Championship"); - assert_eq!(some_league.region, "NORTH AMERICA"); - assert_eq!( - some_league.image_url, - "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" - ); -} - -/// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -/// defined with the #[primary_key] attribute over some field of the type. -/// -/// Uses the *specified datasource mysql* in the second parameter of the function call. -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_by_pk_with_mysql() { - let find_by_pk_result: Result, Box> = - League::find_by_pk_with(&27, MYSQL_DS).await; - assert!(find_by_pk_result.as_ref().unwrap().is_some()); - - let some_league = find_by_pk_result.unwrap().unwrap(); - assert_eq!(some_league.id, 27); - assert_eq!(some_league.ext_id, 107898214974993351_i64); - assert_eq!(some_league.slug, "college_championship"); - assert_eq!(some_league.name, "College Championship"); - assert_eq!(some_league.region, "NORTH AMERICA"); - assert_eq!( - some_league.image_url, - "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" - ); -} - -/// Counts how many rows contains an entity on the target database. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_count_operation() { - assert_eq!( - League::find_all().await.unwrap().len() as i64, - League::count().await.unwrap() - ); -} - -/// Counts how many rows contains an entity on the target database using -/// the specified datasource mssql -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_count_with_operation_mssql() { - assert_eq!( - League::find_all_with(SQL_SERVER_DS) - .await - .unwrap() - .len() as i64, - League::count_with(SQL_SERVER_DS).await.unwrap() - ); -} - -/// Counts how many rows contains an entity on the target database using -/// the specified datasource mysql -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_count_with_operation_mysql() { - assert_eq!( - League::find_all_with(MYSQL_DS).await.unwrap().len() as i64, - League::count_with(MYSQL_DS).await.unwrap() - ); -} +// #![allow(clippy::nonminimal_bool)] +// +// #[cfg(feature = "mysql")] +// use crate::constants::MYSQL_DS; +// +// #[cfg(feature = "mssql")] +// use crate::constants::SQL_SERVER_DS; +// // Integration tests for the CRUD operations available in `Canyon` that +// /// generates and executes *SELECT* statements +// use crate::Error; +// use canyon_sql::crud::CrudOperations; +// +// use crate::tests_models::league::*; +// use crate::tests_models::player::*; +// +// /// Tests the behaviour of a SELECT * FROM {table_name} within Canyon, through the +// /// `::find_all()` associated function derived with the `CanyonCrud` derive proc-macro +// /// and using the *default datasource* +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_all() { +// let find_all_result: Result, Box> = +// League::find_all().await; +// +// // Connection doesn't return an error +// assert!(!find_all_result.is_err()); +// assert!(!find_all_result.unwrap().is_empty()); +// +// let find_all_players: Result, Box> = +// Player::find_all().await; +// assert!(!find_all_players.unwrap().is_empty()); +// } +// +// /// Same as the `find_all()`, but with the unchecked variant, which directly returns `Vec` not +// /// `Result` wrapped +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_all_unchecked() { +// let find_all_result: Vec = League::find_all_unchecked().await; +// assert!(!find_all_result.is_empty()); +// } +// +// /// Tests the behaviour of a SELECT * FROM {table_name} within Canyon, through the +// /// `::find_all()` associated function derived with the `CanyonCrud` derive proc-macro +// /// and using the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_all_with_mssql() { +// let find_all_result: Result, Box> = +// League::find_all_with(&SQL_SERVER_DS).await; +// // Connection doesn't return an error +// assert!(!find_all_result.is_err()); +// assert!(!find_all_result.unwrap().is_empty()); +// } +// +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_all_with_mysql() { +// let find_all_result: Result, Box> = +// League::find_all_with(&MYSQL_DS).await; +// +// // Connection doesn't return an error +// assert!(!find_all_result.is_err()); +// assert!(!find_all_result.unwrap().is_empty()); +// } +// +// /// Same as the `find_all_with()`, but with the unchecked variant and the specified dataosource, +// /// returning directly `Vec` and not `Result, Err>` +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_all_unchecked_with() { +// let find_all_result: Vec = League::find_all_unchecked_with(&SQL_SERVER_DS).await; +// assert!(!find_all_result.is_empty()); +// } +// +// /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +// /// defined with the #[primary_key] attribute over some field of the type. +// /// +// /// Uses the *default datasource*. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_by_pk() { +// let find_by_pk_result: Result, Box> = +// League::find_by_pk(&1).await; +// assert!(find_by_pk_result.as_ref().unwrap().is_some()); +// +// let some_league = find_by_pk_result.unwrap().unwrap(); +// assert_eq!(some_league.id, 1); +// assert_eq!(some_league.ext_id, 100695891328981122_i64); +// assert_eq!(some_league.slug, "european-masters"); +// assert_eq!(some_league.name, "European Masters"); +// assert_eq!(some_league.region, "EUROPE"); +// assert_eq!( +// some_league.image_url, +// "http://static.lolesports.com/leagues/EM_Bug_Outline1.png" +// ); +// } +// +// /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +// /// defined with the #[primary_key] attribute over some field of the type. +// /// +// /// Uses the *specified datasource mssql* in the second parameter of the function call. +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_by_pk_with_mssql() { +// let find_by_pk_result: Result, Box> = +// League::find_by_pk_with(&27, &SQL_SERVER_DS).await; +// assert!(find_by_pk_result.as_ref().unwrap().is_some()); +// +// let some_league = find_by_pk_result.unwrap().unwrap(); +// assert_eq!(some_league.id, 27); +// assert_eq!(some_league.ext_id, 107898214974993351_i64); +// assert_eq!(some_league.slug, "college_championship"); +// assert_eq!(some_league.name, "College Championship"); +// assert_eq!(some_league.region, "NORTH AMERICA"); +// assert_eq!( +// some_league.image_url, +// "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" +// ); +// } +// +// /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +// /// defined with the #[primary_key] attribute over some field of the type. +// /// +// /// Uses the *specified datasource mysql* in the second parameter of the function call. +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_by_pk_with_mysql() { +// let find_by_pk_result: Result, Box> = +// League::find_by_pk_with(&27, &MYSQL_DS).await; +// assert!(find_by_pk_result.as_ref().unwrap().is_some()); +// +// let some_league = find_by_pk_result.unwrap().unwrap(); +// assert_eq!(some_league.id, 27); +// assert_eq!(some_league.ext_id, 107898214974993351_i64); +// assert_eq!(some_league.slug, "college_championship"); +// assert_eq!(some_league.name, "College Championship"); +// assert_eq!(some_league.region, "NORTH AMERICA"); +// assert_eq!( +// some_league.image_url, +// "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" +// ); +// } +// +// /// Counts how many rows contains an entity on the target database. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_count_operation() { +// assert_eq!( +// League::find_all().await.unwrap().len() as i64, +// League::count().await.unwrap() +// ); +// } +// +// /// Counts how many rows contains an entity on the target database using +// /// the specified datasource mssql +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_count_with_operation_mssql() { +// assert_eq!( +// League::find_all_with(&SQL_SERVER_DS) +// .await +// .unwrap() +// .len() as i64, +// League::count_with(&SQL_SERVER_DS).await.unwrap() +// ); +// } +// +// /// Counts how many rows contains an entity on the target database using +// /// the specified datasource mysql +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_count_with_operation_mysql() { +// assert_eq!( +// League::find_all_with(&MYSQL_DS).await.unwrap().len() as i64, +// League::count_with(&MYSQL_DS).await.unwrap() +// ); +// } diff --git a/tests/tests_models/player.rs b/tests/tests_models/player.rs index 59c03daa..c2b3a020 100644 --- a/tests/tests_models/player.rs +++ b/tests/tests_models/player.rs @@ -1,24 +1,24 @@ -use canyon_sql::macros::*; - -#[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] -#[canyon_entity] -/// Data model that represents a database entity for Players. -/// -/// For test the behaviour of Canyon with entities that no declares primary keys, -/// or that is configuration isn't autoincremental, we will use this class. -/// Note that this entity has a primary key declared in the database, but we will -/// omit this in Canyon, so for us, is like if the primary key wasn't set up. -/// -/// Remember that the entities that does not declares at least a field as `#[primary_key]` -/// does not have all the CRUD operations available, only the ones that doesn't -/// requires of a primary key. -pub struct Player { - // #[primary_key] We will omit this to use it as a mock of entities that doesn't declares primary key - id: i32, - ext_id: i64, - first_name: String, - last_name: String, - summoner_name: String, - image_url: Option, - role: String, -} +// use canyon_sql::macros::*; +// +// #[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] +// #[canyon_entity] +// /// Data model that represents a database entity for Players. +// /// +// /// For test the behaviour of Canyon with entities that no declares primary keys, +// /// or that is configuration isn't autoincremental, we will use this class. +// /// Note that this entity has a primary key declared in the database, but we will +// /// omit this in Canyon, so for us, is like if the primary key wasn't set up. +// /// +// /// Remember that the entities that does not declares at least a field as `#[primary_key]` +// /// does not have all the CRUD operations available, only the ones that doesn't +// /// requires of a primary key. +// pub struct Player { +// // #[primary_key] We will omit this to use it as a mock of entities that doesn't declares primary key +// id: i32, +// ext_id: i64, +// first_name: String, +// last_name: String, +// summoner_name: String, +// image_url: Option, +// role: String, +// } diff --git a/tests/tests_models/tournament.rs b/tests/tests_models/tournament.rs index 880076f4..001a87b5 100644 --- a/tests/tests_models/tournament.rs +++ b/tests/tests_models/tournament.rs @@ -1,15 +1,15 @@ -use crate::tests_models::league::League; -use canyon_sql::{date_time::NaiveDate, macros::*}; - -#[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] -#[canyon_entity] -pub struct Tournament { - #[primary_key] - id: i32, - ext_id: i64, - slug: String, - start_date: NaiveDate, - end_date: NaiveDate, - #[foreign_key(table = "league", column = "id")] - league: i32, -} +// use crate::tests_models::league::League; +// use canyon_sql::{date_time::NaiveDate, macros::*}; +// +// #[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] +// #[canyon_entity] +// pub struct Tournament { +// #[primary_key] +// id: i32, +// ext_id: i64, +// slug: String, +// start_date: NaiveDate, +// end_date: NaiveDate, +// #[foreign_key(table = "league", column = "id")] +// league: i32, +// } From d86cb332e246767584c1616481335e61c5b8a071 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 26 Jan 2025 02:07:56 +0100 Subject: [PATCH 043/155] feat: relaxing the querybuilder new bounds on From<&'a I> for TransactionInput --- canyon_core/src/query.rs | 7 +- canyon_crud/src/crud.rs | 28 +- .../src/query_elements/query_builder.rs | 15 +- .../src/query_operations/macro_template.rs | 7 +- canyon_macros/src/query_operations/select.rs | 58 +-- tests/crud/init_mssql.rs | 18 +- tests/crud/querybuilder_operations.rs | 136 +++---- tests/crud/select_operations.rs | 356 +++++++++--------- tests/tests_models/player.rs | 48 +-- 9 files changed, 333 insertions(+), 340 deletions(-) diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index 16c913df..99b4a52d 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -27,16 +27,15 @@ pub trait Transaction { fn query<'a, S, Z, I>( stmt: S, params: Z, - input: &'a I, + input: I, ) -> impl Future>> + Send where S: AsRef + Display + Sync + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, - I: Into> + Sync + Send + 'a, - TransactionInput<'a>: From<&'a I>, + I: Into> + Sync + Send + 'a { async move { - let transaction_input: TransactionInput<'a> = TransactionInput::from(input); + let transaction_input: TransactionInput<'a> = input.into(); let statement = stmt.as_ref(); let query_parameters = params.as_ref(); diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 5c16b32c..62d2ce22 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -29,29 +29,26 @@ where { async fn find_all() -> Result, Box<(dyn std::error::Error + Send + Sync)>>; - async fn find_all_with<'a, I>(input: &'a I) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> - where I: Into> + Sync + Send + 'a, - TransactionInput<'a>: From<&'a I>; + async fn find_all_with<'a, I>(input: I) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + where I: Into> + Sync + Send + 'a; async fn find_all_unchecked() -> Vec; - async fn find_all_unchecked_with<'a, I>(input: &'a I) -> Vec + async fn find_all_unchecked_with<'a, I>(input: I) -> Vec + where I: Into> + Sync + Send + 'a; + + fn select_query<'a>() -> SelectQueryBuilder<'a, T, &'a str>; + + fn select_query_with<'a, I>(input: I) -> SelectQueryBuilder<'a, T, I> where I: Into> + Sync + Send + 'a, TransactionInput<'a>: From<&'a I>; - // fn select_query<'a>() -> SelectQueryBuilder<'a, T, &'a str>; - // - // fn select_query_with<'a, I>(input: &'a I) -> SelectQueryBuilder<'a, T, I> - // where I: Into> + Sync + Send + 'a, - // TransactionInput<'a>: From<&'a I>; - async fn count() -> Result>; async fn count_with<'a, I>( - input: &'a I, + input: I, ) -> Result> - where I: Into> + Sync + Send + 'a, - TransactionInput<'a>: From<&'a I>; + where I: Into> + Sync + Send + 'a; async fn find_by_pk<'a>( value: &'a dyn QueryParameter<'a>, @@ -59,10 +56,9 @@ where async fn find_by_pk_with<'a, I>( value: &'a dyn QueryParameter<'a>, - input: &'a I, + input: I, ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> - where I: Into> + Sync + Send + 'a, - TransactionInput<'a>: From<&'a I>; + where I: Into> + Sync + Send + 'a; // async fn insert<'a>(&mut self) -> Result<(), Box>; diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index 99e1ad93..a75f0807 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -138,11 +138,10 @@ pub mod ops { pub struct QueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: Into> + Send + Sync + 'a, - TransactionInput<'a>: From<&'a I>, + I: Into> + Send + Sync + 'a { query: Query<'a>, - input: &'a I, + input: I, datasource_type: DatabaseType, pd: PhantomData // TODO: provisional while reworking the bounds } @@ -166,7 +165,7 @@ where TransactionInput<'a>: From<&'a I>, { /// Returns a new instance of the [`QueryBuilder`] - pub fn new(query: Query<'a>, input: &I) -> Self { + pub fn new(query: Query<'a>, input: I) -> Self { Self { query, input, @@ -188,7 +187,7 @@ where Ok(T::query( self.query.sql.clone(), self.query.params.to_vec(), - self.input, + &self.input, ) .await? .into_results::()) @@ -317,7 +316,7 @@ where TransactionInput<'a>: From<&'a I>, { /// Generates a new public instance of the [`SelectQueryBuilder`] - pub fn new(table_schema_data: &str, input: &I) -> Self { + pub fn new(table_schema_data: &str, input: I) -> Self { Self { _inner: QueryBuilder::::new( Query::new(format!("SELECT * FROM {table_schema_data}")), @@ -485,7 +484,7 @@ where TransactionInput<'a>: From<&'a I>, { /// Generates a new public instance of the [`UpdateQueryBuilder`] - pub fn new(table_schema_data: &str, input: &I) -> Self { + pub fn new(table_schema_data: &str, input: I) -> Self { Self { _inner: QueryBuilder::::new( Query::new(format!("UPDATE {table_schema_data}")), @@ -633,7 +632,7 @@ where Self { _inner: QueryBuilder::::new( Query::new(format!("DELETE FROM {table_schema_data}")), - &input, + input, ), } } diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index 5fe01647..0658c0cc 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -109,7 +109,7 @@ impl MacroOperationBuilder { let ds_arg0 = input_arg; quote! { #ds_arg0 } } else { - quote! { &"" } + quote! { "" } } } @@ -119,13 +119,12 @@ impl MacroOperationBuilder { } pub fn with_input_param(mut self) -> Self { - self.input_param = Some(quote! { input: &'a I }); + self.input_param = Some(quote! { input: I }); self.input_fwd_arg = Some(quote! { input }); self.lifetime = true; self.where_clause_bounds .push(quote! { - I: Into> + Sync + Send + 'a, - canyon_sql::core::TransactionInput<'a>: From<&'a I> + I: Into> + Sync + Send + 'a }); self } diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index 65ef67ed..59b86186 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -51,34 +51,34 @@ pub fn generate_find_all_query_tokens( let ty = macro_data.ty; quote! { - // Generates a [`canyon_sql::query::SelectQueryBuilder`] - // that allows you to customize the query by adding parameters and constrains dynamically. - // - // It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your - // entity but converted to the corresponding database convention, - // unless concrete values are set on the available parameters of the - // `canyon_macro(table_name = "table_name", schema = "schema")` - // fn select_query<'a>() -> canyon_sql::query::SelectQueryBuilder<'a, #ty, &'a str> { - // canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, "") - // } - - // Generates a [`canyon_sql::query::SelectQueryBuilder`] - // that allows you to customize the query by adding parameters and constrains dynamically. - // - // It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your - // entity but converted to the corresponding database convention, - // unless concrete values are set on the available parameters of the - // `canyon_macro(table_name = "table_name", schema = "schema")` - // - // The query it's made against the database with the configured datasource - // described in the configuration file, and selected with the [`&str`] - // passed as parameter. - // fn select_query_with<'a, I>(input: &'a I) -> canyon_sql::query::SelectQueryBuilder<'a, #ty, I> - // where I: Into> + Sync + Send + 'a, - // canyon_sql::core::TransactionInput<'a>: From<&'a I> - // { - // canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, input) - // } + /// Generates a [`canyon_sql::query::SelectQueryBuilder`] + /// that allows you to customize the query by adding parameters and constrains dynamically. + /// + /// It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your + /// entity but converted to the corresponding database convention, + /// unless concrete values are set on the available parameters of the + /// `canyon_macro(table_name = "table_name", schema = "schema")` + fn select_query<'a>() -> canyon_sql::query::SelectQueryBuilder<'a, #ty, &'a str> { + canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, "") + } + + /// Generates a [`canyon_sql::query::SelectQueryBuilder`] + /// that allows you to customize the query by adding parameters and constrains dynamically. + /// + /// It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your + /// entity but converted to the corresponding database convention, + /// unless concrete values are set on the available parameters of the + /// `canyon_macro(table_name = "table_name", schema = "schema")` + /// + /// The query it's made against the database with the configured datasource + /// described in the configuration file, and selected with the [`&str`] + /// passed as parameter. + fn select_query_with<'a, I>(input: I) -> canyon_sql::query::SelectQueryBuilder<'a, #ty, I> + where I: Into> + Sync + Send + 'a, + canyon_sql::core::TransactionInput<'a>: From<&'a I> + { + canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, input) + } } } @@ -111,7 +111,7 @@ fn generate_find_by_pk_tokens( async fn find_by_pk_with<'a, I>( value: &'a dyn canyon_sql::core::QueryParameter<'a>, - input: &'a I + input: I ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> where I: Into> + Sync + Send + 'a { diff --git a/tests/crud/init_mssql.rs b/tests/crud/init_mssql.rs index c7630478..426fb0d5 100644 --- a/tests/crud/init_mssql.rs +++ b/tests/crud/init_mssql.rs @@ -2,12 +2,12 @@ // use crate::constants::SQL_SERVER_DS; // use crate::constants::SQL_SERVER_FILL_TABLE_VALUES; // use crate::tests_models::league::League; -// +// // use canyon_sql::crud::CrudOperations; // use canyon_sql::db_clients::tiberius::{Client, Config}; // use canyon_sql::runtime::tokio::net::TcpStream; // use canyon_sql::runtime::tokio_util::compat::TokioAsyncWriteCompatExt; -// +// // /// In order to initialize data on `SqlServer`. we must manually insert it // /// when the docker starts. SqlServer official docker from Microsoft does // /// not allow you to run `.sql` files against the database (not at least, without) @@ -24,26 +24,26 @@ // fn initialize_sql_server_docker_instance() { // static CONN_STR: &str = // TODO: change this for the DS when will be in the public API // "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true"; -// +// // canyon_sql::runtime::futures::executor::block_on(async { // let config = Config::from_ado_string(CONN_STR).unwrap(); -// +// // let tcp = TcpStream::connect(config.get_addr()).await.unwrap(); // let tcp2 = TcpStream::connect(config.get_addr()).await.unwrap(); // tcp.set_nodelay(true).ok(); -// +// // let mut client = Client::connect(config.clone(), tcp.compat_write()) // .await // .unwrap(); -// +// // // Create the tables // let query_result = client.query(SQL_SERVER_CREATE_TABLES, &[]).await; // assert!(query_result.is_ok()); -// -// let leagues_sql = League::find_all_with(&SQL_SERVER_DS).await; +// +// let leagues_sql = League::find_all_with(SQL_SERVER_DS).await; // println!("LSQL ERR: {leagues_sql:?}"); // assert!(leagues_sql.is_ok()); -// +// // match leagues_sql { // Ok(ref leagues) => { // let leagues_len = leagues.len(); diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index 62b8e2ab..feb59660 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -2,7 +2,7 @@ // use crate::constants::MYSQL_DS; // #[cfg(feature = "mssql")] // use crate::constants::SQL_SERVER_DS; -// +// // /// Tests for the QueryBuilder available operations within Canyon. // /// // /// QueryBuilder are the way of obtain more flexibility that with @@ -13,11 +13,11 @@ // crud::CrudOperations, // query::{operators::Comp, operators::Like, ops::QueryBuilder}, // }; -// +// // use crate::tests_models::league::*; // use crate::tests_models::player::*; // use crate::tests_models::tournament::*; -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[canyon_sql::macros::canyon_tokio_test] @@ -39,7 +39,7 @@ // "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "postgres")] @@ -52,15 +52,15 @@ // .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) // .query() // .await; -// +// // let filtered_leagues: Vec = filtered_leagues_result.unwrap(); // assert!(!filtered_leagues.is_empty()); -// +// // let league_idx_0 = filtered_leagues.first().unwrap(); // assert_eq!(league_idx_0.id, 34); // assert_eq!(league_idx_0.region, "KOREA"); // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "postgres")] @@ -69,43 +69,43 @@ // // Find all the leagues with "LC" in their name // let mut filtered_leagues_result = League::select_query(); // filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] // fn test_crud_find_with_querybuilder_and_fulllike_with_mssql() { // // Find all the leagues with "LC" in their name -// let mut filtered_leagues_result = League::select_query_with(&SQL_SERVER_DS); +// let mut filtered_leagues_result = League::select_query_with(SQL_SERVER_DS); // filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] // fn test_crud_find_with_querybuilder_and_fulllike_with_mysql() { // // Find all the leagues with "LC" in their name -// let mut filtered_leagues_result = League::select_query_with(&MYSQL_DS); +// let mut filtered_leagues_result = League::select_query_with(MYSQL_DS); // filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR) ,'%')" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "postgres")] @@ -114,13 +114,13 @@ // // Find all the leagues whose name ends with "CK" // let mut filtered_leagues_result = League::select_query(); // filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mssql")] @@ -129,28 +129,28 @@ // // Find all the leagues whose name ends with "CK" // let mut filtered_leagues_result = League::select_query(); // filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] // fn test_crud_find_with_querybuilder_and_leftlike_with_mysql() { // // Find all the leagues whose name ends with "CK" -// let mut filtered_leagues_result = League::select_query_with(&MYSQL_DS); +// let mut filtered_leagues_result = League::select_query_with(MYSQL_DS); // filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR))" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "postgres")] @@ -159,69 +159,69 @@ // // Find all the leagues whose name starts with "LC" // let mut filtered_leagues_result = League::select_query(); // filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] // fn test_crud_find_with_querybuilder_and_rightlike_with_mssql() { // // Find all the leagues whose name starts with "LC" -// let mut filtered_leagues_result = League::select_query_with(&SQL_SERVER_DS); +// let mut filtered_leagues_result = League::select_query_with(SQL_SERVER_DS); // filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] // fn test_crud_find_with_querybuilder_and_rightlike_with_mysql() { // // Find all the leagues whose name starts with "LC" -// let mut filtered_leagues_result = League::select_query_with(&MYSQL_DS); +// let mut filtered_leagues_result = League::select_query_with(MYSQL_DS); // filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS CHAR) ,'%')" // ) // } -// +// // /// Same than the above but with the specified datasource // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] // fn test_crud_find_with_querybuilder_with_mssql() { // // Find all the players where its ID column value is greater that 50 -// let filtered_find_players = Player::select_query_with(&SQL_SERVER_DS) +// let filtered_find_players = Player::select_query_with(SQL_SERVER_DS) // .r#where(PlayerFieldValue::id(&50), Comp::Gt) // .query() // .await; -// +// // assert!(!filtered_find_players.unwrap().is_empty()); // } -// +// // /// Same than the above but with the specified datasource // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] // fn test_crud_find_with_querybuilder_with_mysql() { // // Find all the players where its ID column value is greater that 50 -// let filtered_find_players = Player::select_query_with(&MYSQL_DS) +// let filtered_find_players = Player::select_query_with(MYSQL_DS) // .r#where(PlayerFieldValue::id(&50), Comp::Gt) // .query() // .await; -// +// // assert!(!filtered_find_players.unwrap().is_empty()); // } -// +// // // /// Updates the values of the range on entries defined by the constraint parameters // // /// in the database entity // // #[cfg(feature = "postgres")] @@ -236,7 +236,7 @@ // // ]) // // .r#where(LeagueFieldValue::id(&1), Comp::Gt) // // .and(LeagueFieldValue::id(&8), Comp::Lt); -// +// // // /* NOTE: Family of QueryBuilders are clone, useful in case of need to read the generated SQL // // let qpr = q.clone(); // // println!("PSQL: {:?}", qpr.read_sql()); @@ -245,19 +245,19 @@ // // q.query() // // .await // // .expect("Failed to update records with the querybuilder"); -// +// // // let found_updated_values = League::select_query() // // .r#where(LeagueFieldValue::id(&1), Comp::Gt) // // .and(LeagueFieldValue::id(&7), Comp::Lt) // // .query() // // .await // // .expect("Failed to retrieve database League entries with the querybuilder"); -// +// // // found_updated_values // // .iter() // // .for_each(|league| assert_eq!(league.slug, "Updated with the QueryBuilder")); // // } -// +// // // /// Same as above, but with the specified datasource // // #[cfg(feature = "mssql")] // // #[canyon_sql::macros::canyon_tokio_test] @@ -274,27 +274,27 @@ // // .query() // // .await // // .expect("Failed to update records with the querybuilder"); -// -// // let found_updated_values = Player::select_query_with(&SQL_SERVER_DS) +// +// // let found_updated_values = Player::select_query_with(SQL_SERVER_DS) // // .r#where(PlayerFieldValue::id(&1), Comp::Gt) // // .and(PlayerFieldValue::id(&7), Comp::LtEq) // // .query() // // .await // // .expect("Failed to retrieve database League entries with the querybuilder"); -// +// // // found_updated_values.iter().for_each(|player| { // // assert_eq!(player.summoner_name, "Random updated player name"); // // assert_eq!(player.first_name, "I am an updated first name"); // // }); // // } -// +// // // /// Same as above, but with the specified datasource // // #[cfg(feature = "mysql")] // // #[canyon_sql::macros::canyon_tokio_test] // // fn test_crud_update_with_querybuilder_with_mysql() { // // // Find all the leagues with ID less or equals that 7 // // // and where it's region column value is equals to 'Korea' -// +// // // let mut q = Player::update_query_with(MYSQL_DS); // // q.set(&[ // // (PlayerField::summoner_name, "Random updated player name"), @@ -305,20 +305,20 @@ // // .query() // // .await // // .expect("Failed to update records with the querybuilder"); -// -// // let found_updated_values = Player::select_query_with(&MYSQL_DS) +// +// // let found_updated_values = Player::select_query_with(MYSQL_DS) // // .r#where(PlayerFieldValue::id(&1), Comp::Gt) // // .and(PlayerFieldValue::id(&7), Comp::LtEq) // // .query() // // .await // // .expect("Failed to retrieve database League entries with the querybuilder"); -// +// // // found_updated_values.iter().for_each(|player| { // // assert_eq!(player.summoner_name, "Random updated player name"); // // assert_eq!(player.first_name, "I am an updated first name"); // // }); // // } -// +// // // /// Deletes entries from the mapped entity `T` that are in the ranges filtered // // /// with the QueryBuilder // // /// @@ -334,10 +334,10 @@ // // .query() // // .await // // .expect("Error connecting with the database on the delete operation"); -// +// // // assert_eq!(Tournament::find_by_pk(&15).await.unwrap(), None); // // } -// +// // // /// Same as the above delete, but with the specified datasource // // #[cfg(feature = "mssql")] // // #[canyon_sql::macros::canyon_tokio_test] @@ -348,15 +348,15 @@ // // .query() // // .await // // .expect("Error connecting with the database when we are going to delete data! :)"); -// -// // assert!(Player::select_query_with(&SQL_SERVER_DS) +// +// // assert!(Player::select_query_with(SQL_SERVER_DS) // // .r#where(PlayerFieldValue::id(&122), Comp::Eq) // // .query() // // .await // // .unwrap() // // .is_empty()); // // } -// +// // // /// Same as the above delete, but with the specified datasource // // #[cfg(feature = "mysql")] // // #[canyon_sql::macros::canyon_tokio_test] @@ -367,25 +367,25 @@ // // .query() // // .await // // .expect("Error connecting with the database when we are going to delete data! :)"); -// -// // assert!(Player::select_query_with(&MYSQL_DS) +// +// // assert!(Player::select_query_with(MYSQL_DS) // // .r#where(PlayerFieldValue::id(&122), Comp::Eq) // // .query() // // .await // // .unwrap() // // .is_empty()); // // } -// +// // // /// Tests for the generated SQL query after use the // // /// WHERE clause // // #[canyon_sql::macros::canyon_tokio_test] // // fn test_where_clause() { // // let mut l = League::select_query(); // // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq); -// +// // // assert_eq!(l.read_sql(), "SELECT * FROM league WHERE name = $1") // // } -// +// // // /// Tests for the generated SQL query after use the // // /// AND clause // // #[canyon_sql::macros::canyon_tokio_test] @@ -393,13 +393,13 @@ // // let mut l = League::select_query(); // // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) // // .and(LeagueFieldValue::id(&10), Comp::LtEq); -// +// // // assert_eq!( // // l.read_sql().trim(), // // "SELECT * FROM league WHERE name = $1 AND id <= $2" // // ) // // } -// +// // // /// Tests for the generated SQL query after use the // // /// AND clause // // #[canyon_sql::macros::canyon_tokio_test] @@ -407,13 +407,13 @@ // // let mut l = League::select_query(); // // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) // // .and_values_in(LeagueField::id, &[1, 7, 10]); -// +// // // assert_eq!( // // l.read_sql().trim(), // // "SELECT * FROM league WHERE name = $1 AND id IN ($1, $2, $3)" // // ) // // } -// +// // // /// Tests for the generated SQL query after use the // // /// AND clause // // #[canyon_sql::macros::canyon_tokio_test] @@ -421,13 +421,13 @@ // // let mut l = League::select_query(); // // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) // // .or(LeagueFieldValue::id(&10), Comp::LtEq); -// +// // // assert_eq!( // // l.read_sql().trim(), // // "SELECT * FROM league WHERE name = $1 OR id <= $2" // // ) // // } -// +// // // /// Tests for the generated SQL query after use the // // /// AND clause // // #[canyon_sql::macros::canyon_tokio_test] @@ -435,13 +435,13 @@ // // let mut l = League::select_query(); // // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) // // .or_values_in(LeagueField::id, &[1, 7, 10]); -// +// // // assert_eq!( // // l.read_sql(), // // "SELECT * FROM league WHERE name = $1 OR id IN ($1, $2, $3)" // // ) // // } -// +// // // /// Tests for the generated SQL query after use the // // /// AND clause // // #[canyon_sql::macros::canyon_tokio_test] @@ -449,7 +449,7 @@ // // let mut l = League::select_query(); // // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) // // .order_by(LeagueField::id, false); -// +// // // assert_eq!( // // l.read_sql(), // // "SELECT * FROM league WHERE name = $1 ORDER BY id" diff --git a/tests/crud/select_operations.rs b/tests/crud/select_operations.rs index cbd52b22..cd345eda 100644 --- a/tests/crud/select_operations.rs +++ b/tests/crud/select_operations.rs @@ -1,178 +1,178 @@ -// #![allow(clippy::nonminimal_bool)] -// -// #[cfg(feature = "mysql")] -// use crate::constants::MYSQL_DS; -// -// #[cfg(feature = "mssql")] -// use crate::constants::SQL_SERVER_DS; -// // Integration tests for the CRUD operations available in `Canyon` that -// /// generates and executes *SELECT* statements -// use crate::Error; -// use canyon_sql::crud::CrudOperations; -// -// use crate::tests_models::league::*; -// use crate::tests_models::player::*; -// -// /// Tests the behaviour of a SELECT * FROM {table_name} within Canyon, through the -// /// `::find_all()` associated function derived with the `CanyonCrud` derive proc-macro -// /// and using the *default datasource* -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_all() { -// let find_all_result: Result, Box> = -// League::find_all().await; -// -// // Connection doesn't return an error -// assert!(!find_all_result.is_err()); -// assert!(!find_all_result.unwrap().is_empty()); -// -// let find_all_players: Result, Box> = -// Player::find_all().await; -// assert!(!find_all_players.unwrap().is_empty()); -// } -// -// /// Same as the `find_all()`, but with the unchecked variant, which directly returns `Vec` not -// /// `Result` wrapped -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_all_unchecked() { -// let find_all_result: Vec = League::find_all_unchecked().await; -// assert!(!find_all_result.is_empty()); -// } -// -// /// Tests the behaviour of a SELECT * FROM {table_name} within Canyon, through the -// /// `::find_all()` associated function derived with the `CanyonCrud` derive proc-macro -// /// and using the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_all_with_mssql() { -// let find_all_result: Result, Box> = -// League::find_all_with(&SQL_SERVER_DS).await; -// // Connection doesn't return an error -// assert!(!find_all_result.is_err()); -// assert!(!find_all_result.unwrap().is_empty()); -// } -// -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_all_with_mysql() { -// let find_all_result: Result, Box> = -// League::find_all_with(&MYSQL_DS).await; -// -// // Connection doesn't return an error -// assert!(!find_all_result.is_err()); -// assert!(!find_all_result.unwrap().is_empty()); -// } -// -// /// Same as the `find_all_with()`, but with the unchecked variant and the specified dataosource, -// /// returning directly `Vec` and not `Result, Err>` -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_all_unchecked_with() { -// let find_all_result: Vec = League::find_all_unchecked_with(&SQL_SERVER_DS).await; -// assert!(!find_all_result.is_empty()); -// } -// -// /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -// /// defined with the #[primary_key] attribute over some field of the type. -// /// -// /// Uses the *default datasource*. -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_by_pk() { -// let find_by_pk_result: Result, Box> = -// League::find_by_pk(&1).await; -// assert!(find_by_pk_result.as_ref().unwrap().is_some()); -// -// let some_league = find_by_pk_result.unwrap().unwrap(); -// assert_eq!(some_league.id, 1); -// assert_eq!(some_league.ext_id, 100695891328981122_i64); -// assert_eq!(some_league.slug, "european-masters"); -// assert_eq!(some_league.name, "European Masters"); -// assert_eq!(some_league.region, "EUROPE"); -// assert_eq!( -// some_league.image_url, -// "http://static.lolesports.com/leagues/EM_Bug_Outline1.png" -// ); -// } -// -// /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -// /// defined with the #[primary_key] attribute over some field of the type. -// /// -// /// Uses the *specified datasource mssql* in the second parameter of the function call. -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_by_pk_with_mssql() { -// let find_by_pk_result: Result, Box> = -// League::find_by_pk_with(&27, &SQL_SERVER_DS).await; -// assert!(find_by_pk_result.as_ref().unwrap().is_some()); -// -// let some_league = find_by_pk_result.unwrap().unwrap(); -// assert_eq!(some_league.id, 27); -// assert_eq!(some_league.ext_id, 107898214974993351_i64); -// assert_eq!(some_league.slug, "college_championship"); -// assert_eq!(some_league.name, "College Championship"); -// assert_eq!(some_league.region, "NORTH AMERICA"); -// assert_eq!( -// some_league.image_url, -// "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" -// ); -// } -// -// /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -// /// defined with the #[primary_key] attribute over some field of the type. -// /// -// /// Uses the *specified datasource mysql* in the second parameter of the function call. -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_by_pk_with_mysql() { -// let find_by_pk_result: Result, Box> = -// League::find_by_pk_with(&27, &MYSQL_DS).await; -// assert!(find_by_pk_result.as_ref().unwrap().is_some()); -// -// let some_league = find_by_pk_result.unwrap().unwrap(); -// assert_eq!(some_league.id, 27); -// assert_eq!(some_league.ext_id, 107898214974993351_i64); -// assert_eq!(some_league.slug, "college_championship"); -// assert_eq!(some_league.name, "College Championship"); -// assert_eq!(some_league.region, "NORTH AMERICA"); -// assert_eq!( -// some_league.image_url, -// "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" -// ); -// } -// -// /// Counts how many rows contains an entity on the target database. -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_count_operation() { -// assert_eq!( -// League::find_all().await.unwrap().len() as i64, -// League::count().await.unwrap() -// ); -// } -// -// /// Counts how many rows contains an entity on the target database using -// /// the specified datasource mssql -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_count_with_operation_mssql() { -// assert_eq!( -// League::find_all_with(&SQL_SERVER_DS) -// .await -// .unwrap() -// .len() as i64, -// League::count_with(&SQL_SERVER_DS).await.unwrap() -// ); -// } -// -// /// Counts how many rows contains an entity on the target database using -// /// the specified datasource mysql -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_count_with_operation_mysql() { -// assert_eq!( -// League::find_all_with(&MYSQL_DS).await.unwrap().len() as i64, -// League::count_with(&MYSQL_DS).await.unwrap() -// ); -// } +#![allow(clippy::nonminimal_bool)] + +#[cfg(feature = "mysql")] +use crate::constants::MYSQL_DS; + +#[cfg(feature = "mssql")] +use crate::constants::SQL_SERVER_DS; +// Integration tests for the CRUD operations available in `Canyon` that +/// generates and executes *SELECT* statements +use crate::Error; +use canyon_sql::crud::CrudOperations; + +use crate::tests_models::league::*; +use crate::tests_models::player::*; + +/// Tests the behaviour of a SELECT * FROM {table_name} within Canyon, through the +/// `::find_all()` associated function derived with the `CanyonCrud` derive proc-macro +/// and using the *default datasource* +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_all() { + let find_all_result: Result, Box> = + League::find_all().await; + + // Connection doesn't return an error + assert!(!find_all_result.is_err()); + assert!(!find_all_result.unwrap().is_empty()); + + let find_all_players: Result, Box> = + Player::find_all().await; + assert!(!find_all_players.unwrap().is_empty()); +} + +/// Same as the `find_all()`, but with the unchecked variant, which directly returns `Vec` not +/// `Result` wrapped +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_all_unchecked() { + let find_all_result: Vec = League::find_all_unchecked().await; + assert!(!find_all_result.is_empty()); +} + +/// Tests the behaviour of a SELECT * FROM {table_name} within Canyon, through the +/// `::find_all()` associated function derived with the `CanyonCrud` derive proc-macro +/// and using the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_all_with_mssql() { + let find_all_result: Result, Box> = + League::find_all_with(SQL_SERVER_DS).await; + // Connection doesn't return an error + assert!(!find_all_result.is_err()); + assert!(!find_all_result.unwrap().is_empty()); +} + +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_all_with_mysql() { + let find_all_result: Result, Box> = + League::find_all_with(MYSQL_DS).await; + + // Connection doesn't return an error + assert!(!find_all_result.is_err()); + assert!(!find_all_result.unwrap().is_empty()); +} + +/// Same as the `find_all_with()`, but with the unchecked variant and the specified dataosource, +/// returning directly `Vec` and not `Result, Err>` +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_all_unchecked_with() { + let find_all_result: Vec = League::find_all_unchecked_with(SQL_SERVER_DS).await; + assert!(!find_all_result.is_empty()); +} + +/// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +/// defined with the #[primary_key] attribute over some field of the type. +/// +/// Uses the *default datasource*. +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_by_pk() { + let find_by_pk_result: Result, Box> = + League::find_by_pk(&1).await; + assert!(find_by_pk_result.as_ref().unwrap().is_some()); + + let some_league = find_by_pk_result.unwrap().unwrap(); + assert_eq!(some_league.id, 1); + assert_eq!(some_league.ext_id, 100695891328981122_i64); + assert_eq!(some_league.slug, "european-masters"); + assert_eq!(some_league.name, "European Masters"); + assert_eq!(some_league.region, "EUROPE"); + assert_eq!( + some_league.image_url, + "http://static.lolesports.com/leagues/EM_Bug_Outline1.png" + ); +} + +/// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +/// defined with the #[primary_key] attribute over some field of the type. +/// +/// Uses the *specified datasource mssql* in the second parameter of the function call. +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_by_pk_with_mssql() { + let find_by_pk_result: Result, Box> = + League::find_by_pk_with(&27, SQL_SERVER_DS).await; + assert!(find_by_pk_result.as_ref().unwrap().is_some()); + + let some_league = find_by_pk_result.unwrap().unwrap(); + assert_eq!(some_league.id, 27); + assert_eq!(some_league.ext_id, 107898214974993351_i64); + assert_eq!(some_league.slug, "college_championship"); + assert_eq!(some_league.name, "College Championship"); + assert_eq!(some_league.region, "NORTH AMERICA"); + assert_eq!( + some_league.image_url, + "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" + ); +} + +/// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +/// defined with the #[primary_key] attribute over some field of the type. +/// +/// Uses the *specified datasource mysql* in the second parameter of the function call. +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_by_pk_with_mysql() { + let find_by_pk_result: Result, Box> = + League::find_by_pk_with(&27, MYSQL_DS).await; + assert!(find_by_pk_result.as_ref().unwrap().is_some()); + + let some_league = find_by_pk_result.unwrap().unwrap(); + assert_eq!(some_league.id, 27); + assert_eq!(some_league.ext_id, 107898214974993351_i64); + assert_eq!(some_league.slug, "college_championship"); + assert_eq!(some_league.name, "College Championship"); + assert_eq!(some_league.region, "NORTH AMERICA"); + assert_eq!( + some_league.image_url, + "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" + ); +} + +/// Counts how many rows contains an entity on the target database. +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_count_operation() { + assert_eq!( + League::find_all().await.unwrap().len() as i64, + League::count().await.unwrap() + ); +} + +/// Counts how many rows contains an entity on the target database using +/// the specified datasource mssql +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_count_with_operation_mssql() { + assert_eq!( + League::find_all_with(SQL_SERVER_DS) + .await + .unwrap() + .len() as i64, + League::count_with(SQL_SERVER_DS).await.unwrap() + ); +} + +/// Counts how many rows contains an entity on the target database using +/// the specified datasource mysql +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_count_with_operation_mysql() { + assert_eq!( + League::find_all_with(MYSQL_DS).await.unwrap().len() as i64, + League::count_with(MYSQL_DS).await.unwrap() + ); +} diff --git a/tests/tests_models/player.rs b/tests/tests_models/player.rs index c2b3a020..0cba50ec 100644 --- a/tests/tests_models/player.rs +++ b/tests/tests_models/player.rs @@ -1,24 +1,24 @@ -// use canyon_sql::macros::*; -// -// #[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] -// #[canyon_entity] -// /// Data model that represents a database entity for Players. -// /// -// /// For test the behaviour of Canyon with entities that no declares primary keys, -// /// or that is configuration isn't autoincremental, we will use this class. -// /// Note that this entity has a primary key declared in the database, but we will -// /// omit this in Canyon, so for us, is like if the primary key wasn't set up. -// /// -// /// Remember that the entities that does not declares at least a field as `#[primary_key]` -// /// does not have all the CRUD operations available, only the ones that doesn't -// /// requires of a primary key. -// pub struct Player { -// // #[primary_key] We will omit this to use it as a mock of entities that doesn't declares primary key -// id: i32, -// ext_id: i64, -// first_name: String, -// last_name: String, -// summoner_name: String, -// image_url: Option, -// role: String, -// } +use canyon_sql::macros::*; + +#[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] +#[canyon_entity] +/// Data model that represents a database entity for Players. +/// +/// For test the behaviour of Canyon with entities that no declares primary keys, +/// or that is configuration isn't autoincremental, we will use this class. +/// Note that this entity has a primary key declared in the database, but we will +/// omit this in Canyon, so for us, is like if the primary key wasn't set up. +/// +/// Remember that the entities that does not declare at least a field as `#[primary_key]` +/// does not have all the CRUD operations available, only the ones that doesn't +/// require of a primary key. +pub struct Player { + // #[primary_key] We will omit this to use it as a mock of entities that doesn't declare primary key + id: i32, + ext_id: i64, + first_name: String, + last_name: String, + summoner_name: String, + image_url: Option, + role: String, +} From 1ef28e87e1425131b847d9e3d54d58e0333c11cd Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 26 Jan 2025 10:22:18 +0100 Subject: [PATCH 044/155] feat(wip): making the querybuilder take and return by value in it's fluent interface --- canyon_core/src/query.rs | 2 +- canyon_crud/src/crud.rs | 31 ++- canyon_crud/src/query_elements/query.rs | 5 +- .../src/query_elements/query_builder.rs | 125 +++++----- canyon_macros/src/lib.rs | 2 +- .../src/query_operations/doc_comments.rs | 7 +- .../src/query_operations/foreign_key.rs | 3 +- .../src/query_operations/macro_template.rs | 11 +- canyon_macros/src/query_operations/mod.rs | 4 +- canyon_macros/src/query_operations/select.rs | 6 +- tests/crud/querybuilder_operations.rs | 225 +++++++++--------- tests/crud/select_operations.rs | 5 +- tests/tests_models/tournament.rs | 2 +- 13 files changed, 212 insertions(+), 216 deletions(-) diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index 99b4a52d..a570e100 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -32,7 +32,7 @@ pub trait Transaction { where S: AsRef + Display + Sync + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, - I: Into> + Sync + Send + 'a + I: Into> + Sync + Send + 'a, { async move { let transaction_input: TransactionInput<'a> = input.into(); diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 62d2ce22..f607be75 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -1,11 +1,11 @@ -use async_trait::async_trait; -use canyon_core::query_parameters::QueryParameter; -use canyon_core::{mapper::RowMapper, query::Transaction}; -use canyon_core::connection::db_connector::DatabaseConnection; -use canyon_core::query::TransactionInput; use crate::query_elements::query_builder::{ DeleteQueryBuilder, SelectQueryBuilder, UpdateQueryBuilder, }; +use async_trait::async_trait; +use canyon_core::connection::db_connector::DatabaseConnection; +use canyon_core::query::TransactionInput; +use canyon_core::query_parameters::QueryParameter; +use canyon_core::{mapper::RowMapper, query::Transaction}; /// *CrudOperations* it's the core part of Canyon-SQL. /// @@ -29,26 +29,32 @@ where { async fn find_all() -> Result, Box<(dyn std::error::Error + Send + Sync)>>; - async fn find_all_with<'a, I>(input: I) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> - where I: Into> + Sync + Send + 'a; + async fn find_all_with<'a, I>( + input: I, + ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + where + I: Into> + Sync + Send + 'a; async fn find_all_unchecked() -> Vec; async fn find_all_unchecked_with<'a, I>(input: I) -> Vec - where I: Into> + Sync + Send + 'a; + where + I: Into> + Sync + Send + 'a; fn select_query<'a>() -> SelectQueryBuilder<'a, T, &'a str>; fn select_query_with<'a, I>(input: I) -> SelectQueryBuilder<'a, T, I> - where I: Into> + Sync + Send + 'a, - TransactionInput<'a>: From<&'a I>; + where + I: Into> + Sync + Send + 'a, + TransactionInput<'a>: From<&'a I>; async fn count() -> Result>; async fn count_with<'a, I>( input: I, ) -> Result> - where I: Into> + Sync + Send + 'a; + where + I: Into> + Sync + Send + 'a; async fn find_by_pk<'a>( value: &'a dyn QueryParameter<'a>, @@ -58,7 +64,8 @@ where value: &'a dyn QueryParameter<'a>, input: I, ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> - where I: Into> + Sync + Send + 'a; + where + I: Into> + Sync + Send + 'a; // async fn insert<'a>(&mut self) -> Result<(), Box>; diff --git a/canyon_crud/src/query_elements/query.rs b/canyon_crud/src/query_elements/query.rs index e27cb9b2..2f01638b 100644 --- a/canyon_crud/src/query_elements/query.rs +++ b/canyon_crud/src/query_elements/query.rs @@ -4,8 +4,7 @@ use canyon_core::query_parameters::QueryParameter; /// Holds a sql sentence details #[derive(Debug, Clone)] -pub struct Query<'a> -{ +pub struct Query<'a> { pub sql: String, pub params: Vec<&'a dyn QueryParameter<'a>>, } @@ -14,7 +13,7 @@ impl<'a> Query<'a> { pub fn new(sql: String) -> Query<'a> { Self { sql, - params: vec![] + params: vec![], } } } diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index a75f0807..fc6dd1c3 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -1,14 +1,14 @@ -use std::fmt::Debug; -use std::marker::PhantomData; -use canyon_core::connection::{database_type::DatabaseType, get_database_config, DATASOURCES}; -use canyon_core::{mapper::RowMapper, query::Transaction, query_parameters::QueryParameter}; -use canyon_core::query::TransactionInput; use crate::{ bounds::{FieldIdentifier, FieldValueIdentifier}, crud::CrudOperations, query_elements::query::Query, Operator, }; +use canyon_core::connection::{database_type::DatabaseType, get_database_config, DATASOURCES}; +use canyon_core::query::TransactionInput; +use canyon_core::{mapper::RowMapper, query::Transaction, query_parameters::QueryParameter}; +use std::fmt::Debug; +use std::marker::PhantomData; /// Contains the elements that makes part of the formal declaration /// of the behaviour of the Canyon-SQL QueryBuilder @@ -63,7 +63,7 @@ pub mod ops { /// generated one /// /// * `sql` - The [`&str`] to be wired in the SQL - fn push_sql(&mut self, sql: &str); + fn push_sql(self, sql: &str); /// Generates a `WHERE` SQL clause for constraint the query. /// @@ -72,10 +72,10 @@ pub mod ops { /// * `op` - Any element that implements [`Operator`] for create the comparison /// or equality binary operator fn r#where>( - &mut self, + self, column: Z, op: impl Operator, - ) -> &mut Self + ) -> Self where T: Debug + CrudOperations + Transaction + RowMapper; @@ -86,10 +86,10 @@ pub mod ops { /// * `op` - Any element that implements [`Operator`] for create the comparison /// or equality binary operator fn and>( - &mut self, + self, column: Z, op: impl Operator, - ) -> &mut Self; + ) -> Self; /// Generates an `AND` SQL clause for constraint the query that will create /// the filter in conjunction with an `IN` operator that will ac @@ -99,7 +99,7 @@ pub mod ops { /// the field name that maps the targeted column name /// * `values` - An array of [`QueryParameter`] with the values to filter /// inside the `IN` operator - fn and_values_in(&mut self, column: Z, values: &'a [Q]) -> &mut Self + fn and_values_in(self, column: Z, values: &'a [Q]) -> Self where Z: FieldIdentifier, Q: QueryParameter<'a>; @@ -112,7 +112,7 @@ pub mod ops { /// the field name that maps the targeted column name /// * `values` - An array of [`QueryParameter`] with the values to filter /// inside the `IN` operator - fn or_values_in(&mut self, r#or: Z, values: &'a [Q]) -> &mut Self + fn or_values_in(self, r#or: Z, values: &'a [Q]) -> Self where Z: FieldIdentifier, Q: QueryParameter<'a>; @@ -123,14 +123,14 @@ pub mod ops { /// column name and the value for the filter /// * `op` - Any element that implements [`Operator`] for create the comparison /// or equality binary operator - fn or>(&mut self, column: Z, op: impl Operator) - -> &mut Self; + fn or>(self, column: Z, op: impl Operator) + -> Self; /// Generates a `ORDER BY` SQL clause for constraint the query. /// /// * `order_by` - A [`FieldIdentifier`] that will provide the target column name /// * `desc` - a boolean indicating if the generated `ORDER_BY` must be in ascending or descending order - fn order_by>(&mut self, order_by: Z, desc: bool) -> &mut Self; + fn order_by>(self, order_by: Z, desc: bool) -> Self; } } @@ -138,25 +138,26 @@ pub mod ops { pub struct QueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: Into> + Send + Sync + 'a + I: Into> + Send + Sync + 'a, { query: Query<'a>, input: I, datasource_type: DatabaseType, - pd: PhantomData // TODO: provisional while reworking the bounds + pd: PhantomData, // TODO: provisional while reworking the bounds } unsafe impl<'a, T, I> Send for QueryBuilder<'a, T, I> - where T: CrudOperations + Transaction + RowMapper, +where + T: CrudOperations + Transaction + RowMapper, I: Into> + Send + Sync + 'a, - TransactionInput<'a>: From<&'a I>, { } unsafe impl<'a, T, I> Sync for QueryBuilder<'a, T, I> - where T: CrudOperations + Transaction + RowMapper, - I: Into> + Send + Sync + 'a, - TransactionInput<'a>: From<&'a I> -{} +where + T: CrudOperations + Transaction + RowMapper, + I: Into> + Send + Sync + 'a, +{ +} impl<'a, T, I> QueryBuilder<'a, T, I> where @@ -180,14 +181,14 @@ where /// Launches the generated query against the database targeted /// by the selected datasource pub async fn query( - &'a mut self, + mut self, ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> { self.query.sql.push(';'); Ok(T::query( self.query.sql.clone(), self.query.params.to_vec(), - &self.input, + self.input, ) .await? .into_results::()) @@ -303,7 +304,7 @@ where pub struct SelectQueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: Into> + Send + Sync + 'a + ?Sized, + I: Into> + Send + Sync + 'a, TransactionInput<'a>: From<&'a I>, { _inner: QueryBuilder<'a, T, I>, @@ -316,7 +317,7 @@ where TransactionInput<'a>: From<&'a I>, { /// Generates a new public instance of the [`SelectQueryBuilder`] - pub fn new(table_schema_data: &str, input: I) -> Self { + pub fn new(table_schema_data: &str, input: I) -> Self { Self { _inner: QueryBuilder::::new( Query::new(format!("SELECT * FROM {table_schema_data}")), @@ -328,9 +329,7 @@ where /// Launches the generated query to the database pointed by the /// selected datasource #[inline] - pub async fn query( - &'a mut self, - ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> { + pub async fn query(self) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> { self._inner.query().await } @@ -342,7 +341,7 @@ where /// * `col2` - The right side of the ON operator for the join /// /// > Note: The order on the column parameters is irrelevant - pub fn left_join(&mut self, join_table: &str, col1: &str, col2: &str) -> &mut Self { + pub fn left_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { self._inner .query .sql @@ -358,7 +357,7 @@ where /// * `col2` - The right side of the ON operator for the join /// /// > Note: The order on the column parameters is irrelevant - pub fn inner_join(&mut self, join_table: &str, col1: &str, col2: &str) -> &mut Self { + pub fn inner_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { self._inner .query .sql @@ -374,7 +373,7 @@ where /// * `col2` - The right side of the ON operator for the join /// /// > Note: The order on the column parameters is irrelevant - pub fn right_join(&mut self, join_table: &str, col1: &str, col2: &str) -> &mut Self { + pub fn right_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { self._inner .query .sql @@ -390,7 +389,7 @@ where /// * `col2` - The right side of the ON operator for the join /// /// > Note: The order on the column parameters is irrelevant - pub fn full_join(&mut self, join_table: &str, col1: &str, col2: &str) -> &mut Self { + pub fn full_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { self._inner .query .sql @@ -411,28 +410,28 @@ where } #[inline(always)] - fn push_sql(&mut self, sql: &str) { + fn push_sql(mut self, sql: &str) { self._inner.query.sql.push_str(sql); } #[inline] fn r#where>( - &mut self, + mut self, r#where: Z, op: impl Operator, - ) -> &mut Self { + ) -> Self { self._inner.r#where(r#where, op); self } #[inline] - fn and>(&mut self, column: Z, op: impl Operator) -> &mut Self { + fn and>(mut self, column: Z, op: impl Operator) -> Self { self._inner.and(column, op); self } #[inline] - fn and_values_in(&mut self, r#and: Z, values: &'a [Q]) -> &mut Self + fn and_values_in(mut self, r#and: Z, values: &'a [Q]) -> Self where Z: FieldIdentifier, Q: QueryParameter<'a>, @@ -442,7 +441,7 @@ where } #[inline] - fn or_values_in(&mut self, r#and: Z, values: &'a [Q]) -> &mut Self + fn or_values_in(mut self, r#and: Z, values: &'a [Q]) -> Self where Z: FieldIdentifier, Q: QueryParameter<'a>, @@ -452,13 +451,13 @@ where } #[inline] - fn or>(&mut self, column: Z, op: impl Operator) -> &mut Self { + fn or>(mut self, column: Z, op: impl Operator) -> Self { self._inner.or(column, op); self } #[inline] - fn order_by>(&mut self, order_by: Z, desc: bool) -> &mut Self { + fn order_by>(mut self, order_by: Z, desc: bool) -> Self { self._inner.order_by(order_by, desc); self } @@ -497,13 +496,13 @@ where /// selected datasource #[inline] pub async fn query( - &'a mut self, + self, ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> { self._inner.query().await } - /// Creates an SQL `SET` clause to especify the columns that must be updated in the sentence - pub fn set(&mut self, columns: &'a [(Z, Q)]) -> &mut Self + /// Creates an SQL `SET` clause to specify the columns that must be updated in the sentence + pub fn set(mut self, columns: &'a [(Z, Q)]) -> Self where Z: FieldIdentifier + Clone, Q: QueryParameter<'a>, @@ -554,28 +553,28 @@ where } #[inline(always)] - fn push_sql(&mut self, sql: &str) { + fn push_sql(mut self, sql: &str) { self._inner.query.sql.push_str(sql); } #[inline] fn r#where>( - &mut self, + mut self, r#where: Z, op: impl Operator, - ) -> &mut Self { + ) -> Self { self._inner.r#where(r#where, op); self } #[inline] - fn and>(&mut self, column: Z, op: impl Operator) -> &mut Self { + fn and>(mut self, column: Z, op: impl Operator) -> Self { self._inner.and(column, op); self } #[inline] - fn and_values_in(&mut self, r#and: Z, values: &'a [Q]) -> &mut Self + fn and_values_in(mut self, r#and: Z, values: &'a [Q]) -> Self where Z: FieldIdentifier, Q: QueryParameter<'a>, @@ -585,7 +584,7 @@ where } #[inline] - fn or_values_in(&mut self, r#or: Z, values: &'a [Q]) -> &mut Self + fn or_values_in(mut self, r#or: Z, values: &'a [Q]) -> Self where Z: FieldIdentifier, Q: QueryParameter<'a>, @@ -595,13 +594,13 @@ where } #[inline] - fn or>(&mut self, column: Z, op: impl Operator) -> &mut Self { + fn or>(mut self, column: Z, op: impl Operator) -> Self { self._inner.or(column, op); self } #[inline] - fn order_by>(&mut self, order_by: Z, desc: bool) -> &mut Self { + fn order_by>(mut self, order_by: Z, desc: bool) -> Self { self._inner.order_by(order_by, desc); self } @@ -640,9 +639,7 @@ where /// Launches the generated query to the database pointed by the /// selected datasource #[inline] - pub async fn query( - &'a mut self, - ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> { + pub async fn query(self) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> { self._inner.query().await } } @@ -659,28 +656,28 @@ where } #[inline(always)] - fn push_sql(&mut self, sql: &str) { + fn push_sql(mut self, sql: &str) { self._inner.query.sql.push_str(sql); } #[inline] fn r#where>( - &mut self, + mut self, r#where: Z, op: impl Operator, - ) -> &mut Self { + ) -> Self { self._inner.r#where(r#where, op); self } #[inline] - fn and>(&mut self, column: Z, op: impl Operator) -> &mut Self { + fn and>(mut self, column: Z, op: impl Operator) -> Self { self._inner.and(column, op); self } #[inline] - fn and_values_in(&mut self, r#and: Z, values: &'a [Q]) -> &mut Self + fn and_values_in(mut self, r#and: Z, values: &'a [Q]) -> Self where Z: FieldIdentifier, Q: QueryParameter<'a>, @@ -690,7 +687,7 @@ where } #[inline] - fn or_values_in(&mut self, r#or: Z, values: &'a [Q]) -> &mut Self + fn or_values_in(mut self, r#or: Z, values: &'a [Q]) -> Self where Z: FieldIdentifier, Q: QueryParameter<'a>, @@ -700,13 +697,13 @@ where } #[inline] - fn or>(&mut self, column: Z, op: impl Operator) -> &mut Self { + fn or>(mut self, column: Z, op: impl Operator) -> Self { self._inner.or(column, op); self } #[inline] - fn order_by>(&mut self, order_by: Z, desc: bool) -> &mut Self { + fn order_by>(mut self, order_by: Z, desc: bool) -> Self { self._inner.order_by(order_by, desc); self } diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index d4eb1329..80197667 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -23,10 +23,10 @@ use query_operations::{ delete::{generate_delete_query_tokens, generate_delete_tokens}, insert::{generate_insert_tokens, generate_multiple_insert_tokens}, select::{ - generate_read_operations_tokens, generate_find_all_query_tokens, // generate_find_by_foreign_key_tokens, // generate_find_by_reverse_foreign_key_tokens, + generate_read_operations_tokens, }, update::{generate_update_query_tokens, generate_update_tokens}, }; diff --git a/canyon_macros/src/query_operations/doc_comments.rs b/canyon_macros/src/query_operations/doc_comments.rs index fa88791b..5fadd44c 100644 --- a/canyon_macros/src/query_operations/doc_comments.rs +++ b/canyon_macros/src/query_operations/doc_comments.rs @@ -13,8 +13,7 @@ pub const SELECT_QUERYBUILDER_DOC_COMMENT: &str = /// unless concrete values are set on the available parameters of the \ /// `canyon_macro => table_name = \"table_name\", schema = \"schema\")`"; -pub const FIND_BY_PK: &str = - "/// Finds an element on the queried table that matches the \ +pub const FIND_BY_PK: &str = "/// Finds an element on the queried table that matches the \ /// value of the field annotated with the `primary_key` attribute, \ /// filtering by the column that it's declared as the primary \ /// key on the database. \ @@ -26,8 +25,8 @@ pub const FIND_BY_PK: &str = /// querying the database, or, if no errors happens, a success containing \ /// and Option with the data found wrapped in the Some(T) variant, \ /// or None if the value isn't found on the table."; - + pub const DS_ADVERTISING: &str = "/// The query it's made against the database with the configured datasource \ /// described in the configuration file, and selected with the [`&str`] \ - /// passed as parameter."; \ No newline at end of file + /// passed as parameter."; diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index 1c0b8080..82491636 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -1,4 +1,3 @@ - // /// Generates the TokenStream for build the search by foreign key feature, also as a method instance // /// of a T type of as an associated function of same T type, but wrapped as a Result, representing // /// a possible failure querying the database, a bad or missing FK annotation or a missed ForeignKeyable @@ -177,4 +176,4 @@ // } // rev_fk_quotes -// } \ No newline at end of file +// } diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index 0658c0cc..48becec8 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -80,10 +80,10 @@ impl MacroOperationBuilder { self.user_type = Some(ty.clone()); self } - + fn compose_fn_signature_generics(&self) -> TokenStream { if !&self.lifetime && self.input_param.is_none() { - quote!{} + quote! {} } else if self.lifetime && self.input_param.is_none() { quote! { <'a> } } else { @@ -122,10 +122,9 @@ impl MacroOperationBuilder { self.input_param = Some(quote! { input: I }); self.input_fwd_arg = Some(quote! { input }); self.lifetime = true; - self.where_clause_bounds - .push(quote! { - I: Into> + Sync + Send + 'a - }); + self.where_clause_bounds.push(quote! { + I: Into> + Sync + Send + 'a + }); self } diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index 25d6561e..6030eb4f 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -1,8 +1,8 @@ pub mod delete; +pub mod foreign_key; pub mod insert; pub mod select; -pub mod foreign_key; pub mod update; -mod macro_template; mod doc_comments; +mod macro_template; diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index 59b86186..fe08aff4 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -358,8 +358,9 @@ mod macro_builder_read_ops_tests { const MAPS_TO: &str = "into_results :: < User > ()"; const LT_CONSTRAINT: &str = "< 'a >"; const INPUT_PARAM: &str = "input : & 'a I"; - - const WITH_WHERE_BOUNDS: &str = "where I : Into < canyon_sql::core::TransactionInput < 'a >> + Sync + Send + 'a "; + + const WITH_WHERE_BOUNDS: &str = + "where I : Into < canyon_sql::core::TransactionInput < 'a >> + Sync + Send + 'a "; #[test] fn test_macro_builder_find_all() { @@ -451,4 +452,3 @@ mod macro_builder_read_ops_tests { assert!(find_by_pk_with.contains(OPT_RET_TY_LT)); } } - diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index feb59660..e9a6111a 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -1,66 +1,65 @@ -// #[cfg(feature = "mysql")] -// use crate::constants::MYSQL_DS; -// #[cfg(feature = "mssql")] -// use crate::constants::SQL_SERVER_DS; -// -// /// Tests for the QueryBuilder available operations within Canyon. -// /// -// /// QueryBuilder are the way of obtain more flexibility that with -// /// the default generated queries, essentially for build the queries -// /// with the SQL filters -// /// -// use canyon_sql::{ -// crud::CrudOperations, -// query::{operators::Comp, operators::Like, ops::QueryBuilder}, -// }; -// -// use crate::tests_models::league::*; -// use crate::tests_models::player::*; -// use crate::tests_models::tournament::*; -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_generated_sql_by_the_select_querybuilder() { -// let mut select_with_joins = League::select_query(); -// select_with_joins -// .inner_join("tournament", "league.id", "tournament.league_id") -// .left_join("team", "tournament.id", "player.tournament_id") -// .r#where(LeagueFieldValue::id(&7), Comp::Gt) -// .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) -// .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); -// // .query() -// // .await; -// // NOTE: We don't have in the docker the generated relationships -// // with the joins, so for now, we are just going to check that the -// // generated SQL by the SelectQueryBuilder is the spected -// assert_eq!( -// select_with_joins.read_sql(), -// "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" -// ) -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder() { -// // Find all the leagues with ID less or equals that 7 -// // and where it's region column value is equals to 'Korea' -// let filtered_leagues_result: Result, _> = League::select_query() -// .r#where(LeagueFieldValue::id(&50), Comp::LtEq) -// .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) -// .query() -// .await; -// -// let filtered_leagues: Vec = filtered_leagues_result.unwrap(); -// assert!(!filtered_leagues.is_empty()); -// -// let league_idx_0 = filtered_leagues.first().unwrap(); -// assert_eq!(league_idx_0.id, 34); -// assert_eq!(league_idx_0.region, "KOREA"); -// } -// +#[cfg(feature = "mysql")] +use crate::constants::MYSQL_DS; +#[cfg(feature = "mssql")] +use crate::constants::SQL_SERVER_DS; + +/// Tests for the QueryBuilder available operations within Canyon. +/// +/// QueryBuilder are the way of obtain more flexibility that with +/// the default generated queries, essentially for build the queries +/// with the SQL filters +/// +use canyon_sql::{ + crud::CrudOperations, + query::{operators::Comp, operators::Like, ops::QueryBuilder}, +}; + +use crate::tests_models::league::*; +use crate::tests_models::player::*; +use crate::tests_models::tournament::*; + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[canyon_sql::macros::canyon_tokio_test] +fn test_generated_sql_by_the_select_querybuilder() { + let mut select_with_joins = League::select_query() + .inner_join("tournament", "league.id", "tournament.league_id") + .left_join("team", "tournament.id", "player.tournament_id") + .r#where(LeagueFieldValue::id(&7), Comp::Gt) + .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) + .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); + // .query() + // .await; + // NOTE: We don't have in the docker the generated relationships + // with the joins, so for now, we are just going to check that the + // generated SQL by the SelectQueryBuilder is the spected + assert_eq!( + select_with_joins.read_sql(), + "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder() { + // Find all the leagues with ID less or equals that 7 + // and where it's region column value is equals to 'Korea' + let filtered_leagues_result: Result, _> = League::select_query() + .r#where(LeagueFieldValue::id(&50), Comp::LtEq) + .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) + .query() + .await; + + let filtered_leagues: Vec = filtered_leagues_result.unwrap(); + assert!(!filtered_leagues.is_empty()); + + let league_idx_0 = filtered_leagues.first().unwrap(); + assert_eq!(league_idx_0.id, 34); + assert_eq!(league_idx_0.region, "KOREA"); +} + // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "postgres")] @@ -69,13 +68,13 @@ // // Find all the leagues with "LC" in their name // let mut filtered_leagues_result = League::select_query(); // filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mssql")] @@ -84,13 +83,13 @@ // // Find all the leagues with "LC" in their name // let mut filtered_leagues_result = League::select_query_with(SQL_SERVER_DS); // filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mysql")] @@ -99,13 +98,13 @@ // // Find all the leagues with "LC" in their name // let mut filtered_leagues_result = League::select_query_with(MYSQL_DS); // filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR) ,'%')" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "postgres")] @@ -114,13 +113,13 @@ // // Find all the leagues whose name ends with "CK" // let mut filtered_leagues_result = League::select_query(); // filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mssql")] @@ -129,13 +128,13 @@ // // Find all the leagues whose name ends with "CK" // let mut filtered_leagues_result = League::select_query(); // filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mysql")] @@ -144,13 +143,13 @@ // // Find all the leagues whose name ends with "CK" // let mut filtered_leagues_result = League::select_query_with(MYSQL_DS); // filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR))" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "postgres")] @@ -159,13 +158,13 @@ // // Find all the leagues whose name starts with "LC" // let mut filtered_leagues_result = League::select_query(); // filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mssql")] @@ -174,13 +173,13 @@ // // Find all the leagues whose name starts with "LC" // let mut filtered_leagues_result = League::select_query_with(SQL_SERVER_DS); // filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mysql")] @@ -189,13 +188,13 @@ // // Find all the leagues whose name starts with "LC" // let mut filtered_leagues_result = League::select_query_with(MYSQL_DS); // filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS CHAR) ,'%')" // ) // } -// +// // /// Same than the above but with the specified datasource // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] @@ -205,10 +204,10 @@ // .r#where(PlayerFieldValue::id(&50), Comp::Gt) // .query() // .await; -// +// // assert!(!filtered_find_players.unwrap().is_empty()); // } -// +// // /// Same than the above but with the specified datasource // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] @@ -218,10 +217,10 @@ // .r#where(PlayerFieldValue::id(&50), Comp::Gt) // .query() // .await; -// +// // assert!(!filtered_find_players.unwrap().is_empty()); // } -// +// // // /// Updates the values of the range on entries defined by the constraint parameters // // /// in the database entity // // #[cfg(feature = "postgres")] @@ -236,7 +235,7 @@ // // ]) // // .r#where(LeagueFieldValue::id(&1), Comp::Gt) // // .and(LeagueFieldValue::id(&8), Comp::Lt); -// +// // // /* NOTE: Family of QueryBuilders are clone, useful in case of need to read the generated SQL // // let qpr = q.clone(); // // println!("PSQL: {:?}", qpr.read_sql()); @@ -245,19 +244,19 @@ // // q.query() // // .await // // .expect("Failed to update records with the querybuilder"); -// +// // // let found_updated_values = League::select_query() // // .r#where(LeagueFieldValue::id(&1), Comp::Gt) // // .and(LeagueFieldValue::id(&7), Comp::Lt) // // .query() // // .await // // .expect("Failed to retrieve database League entries with the querybuilder"); -// +// // // found_updated_values // // .iter() // // .for_each(|league| assert_eq!(league.slug, "Updated with the QueryBuilder")); // // } -// +// // // /// Same as above, but with the specified datasource // // #[cfg(feature = "mssql")] // // #[canyon_sql::macros::canyon_tokio_test] @@ -274,27 +273,27 @@ // // .query() // // .await // // .expect("Failed to update records with the querybuilder"); -// +// // // let found_updated_values = Player::select_query_with(SQL_SERVER_DS) // // .r#where(PlayerFieldValue::id(&1), Comp::Gt) // // .and(PlayerFieldValue::id(&7), Comp::LtEq) // // .query() // // .await // // .expect("Failed to retrieve database League entries with the querybuilder"); -// +// // // found_updated_values.iter().for_each(|player| { // // assert_eq!(player.summoner_name, "Random updated player name"); // // assert_eq!(player.first_name, "I am an updated first name"); // // }); // // } -// +// // // /// Same as above, but with the specified datasource // // #[cfg(feature = "mysql")] // // #[canyon_sql::macros::canyon_tokio_test] // // fn test_crud_update_with_querybuilder_with_mysql() { // // // Find all the leagues with ID less or equals that 7 // // // and where it's region column value is equals to 'Korea' -// +// // // let mut q = Player::update_query_with(MYSQL_DS); // // q.set(&[ // // (PlayerField::summoner_name, "Random updated player name"), @@ -305,20 +304,20 @@ // // .query() // // .await // // .expect("Failed to update records with the querybuilder"); -// +// // // let found_updated_values = Player::select_query_with(MYSQL_DS) // // .r#where(PlayerFieldValue::id(&1), Comp::Gt) // // .and(PlayerFieldValue::id(&7), Comp::LtEq) // // .query() // // .await // // .expect("Failed to retrieve database League entries with the querybuilder"); -// +// // // found_updated_values.iter().for_each(|player| { // // assert_eq!(player.summoner_name, "Random updated player name"); // // assert_eq!(player.first_name, "I am an updated first name"); // // }); // // } -// +// // // /// Deletes entries from the mapped entity `T` that are in the ranges filtered // // /// with the QueryBuilder // // /// @@ -334,10 +333,10 @@ // // .query() // // .await // // .expect("Error connecting with the database on the delete operation"); -// +// // // assert_eq!(Tournament::find_by_pk(&15).await.unwrap(), None); // // } -// +// // // /// Same as the above delete, but with the specified datasource // // #[cfg(feature = "mssql")] // // #[canyon_sql::macros::canyon_tokio_test] @@ -348,7 +347,7 @@ // // .query() // // .await // // .expect("Error connecting with the database when we are going to delete data! :)"); -// +// // // assert!(Player::select_query_with(SQL_SERVER_DS) // // .r#where(PlayerFieldValue::id(&122), Comp::Eq) // // .query() @@ -356,7 +355,7 @@ // // .unwrap() // // .is_empty()); // // } -// +// // // /// Same as the above delete, but with the specified datasource // // #[cfg(feature = "mysql")] // // #[canyon_sql::macros::canyon_tokio_test] @@ -367,7 +366,7 @@ // // .query() // // .await // // .expect("Error connecting with the database when we are going to delete data! :)"); -// +// // // assert!(Player::select_query_with(MYSQL_DS) // // .r#where(PlayerFieldValue::id(&122), Comp::Eq) // // .query() @@ -375,17 +374,17 @@ // // .unwrap() // // .is_empty()); // // } -// +// // // /// Tests for the generated SQL query after use the // // /// WHERE clause // // #[canyon_sql::macros::canyon_tokio_test] // // fn test_where_clause() { // // let mut l = League::select_query(); // // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq); -// +// // // assert_eq!(l.read_sql(), "SELECT * FROM league WHERE name = $1") // // } -// +// // // /// Tests for the generated SQL query after use the // // /// AND clause // // #[canyon_sql::macros::canyon_tokio_test] @@ -393,13 +392,13 @@ // // let mut l = League::select_query(); // // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) // // .and(LeagueFieldValue::id(&10), Comp::LtEq); -// +// // // assert_eq!( // // l.read_sql().trim(), // // "SELECT * FROM league WHERE name = $1 AND id <= $2" // // ) // // } -// +// // // /// Tests for the generated SQL query after use the // // /// AND clause // // #[canyon_sql::macros::canyon_tokio_test] @@ -407,13 +406,13 @@ // // let mut l = League::select_query(); // // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) // // .and_values_in(LeagueField::id, &[1, 7, 10]); -// +// // // assert_eq!( // // l.read_sql().trim(), // // "SELECT * FROM league WHERE name = $1 AND id IN ($1, $2, $3)" // // ) // // } -// +// // // /// Tests for the generated SQL query after use the // // /// AND clause // // #[canyon_sql::macros::canyon_tokio_test] @@ -421,13 +420,13 @@ // // let mut l = League::select_query(); // // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) // // .or(LeagueFieldValue::id(&10), Comp::LtEq); -// +// // // assert_eq!( // // l.read_sql().trim(), // // "SELECT * FROM league WHERE name = $1 OR id <= $2" // // ) // // } -// +// // // /// Tests for the generated SQL query after use the // // /// AND clause // // #[canyon_sql::macros::canyon_tokio_test] @@ -435,13 +434,13 @@ // // let mut l = League::select_query(); // // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) // // .or_values_in(LeagueField::id, &[1, 7, 10]); -// +// // // assert_eq!( // // l.read_sql(), // // "SELECT * FROM league WHERE name = $1 OR id IN ($1, $2, $3)" // // ) // // } -// +// // // /// Tests for the generated SQL query after use the // // /// AND clause // // #[canyon_sql::macros::canyon_tokio_test] @@ -449,7 +448,7 @@ // // let mut l = League::select_query(); // // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) // // .order_by(LeagueField::id, false); -// +// // // assert_eq!( // // l.read_sql(), // // "SELECT * FROM league WHERE name = $1 ORDER BY id" diff --git a/tests/crud/select_operations.rs b/tests/crud/select_operations.rs index cd345eda..fed05601 100644 --- a/tests/crud/select_operations.rs +++ b/tests/crud/select_operations.rs @@ -158,10 +158,7 @@ fn test_crud_count_operation() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_count_with_operation_mssql() { assert_eq!( - League::find_all_with(SQL_SERVER_DS) - .await - .unwrap() - .len() as i64, + League::find_all_with(SQL_SERVER_DS).await.unwrap().len() as i64, League::count_with(SQL_SERVER_DS).await.unwrap() ); } diff --git a/tests/tests_models/tournament.rs b/tests/tests_models/tournament.rs index 001a87b5..e6fab352 100644 --- a/tests/tests_models/tournament.rs +++ b/tests/tests_models/tournament.rs @@ -1,6 +1,6 @@ // use crate::tests_models::league::League; // use canyon_sql::{date_time::NaiveDate, macros::*}; -// +// // #[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] // #[canyon_entity] // pub struct Tournament { From c122380bab6514abb52492d8643affc22d151b87 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 26 Jan 2025 11:35:15 +0100 Subject: [PATCH 045/155] feat: getting rid of the implementation indirections of Transaction input, avoiding storing intermediate state --- canyon_core/src/connection/db_connector.rs | 34 ++++++ canyon_core/src/query.rs | 111 +++++------------- canyon_crud/src/crud.rs | 14 +-- .../src/query_elements/query_builder.rs | 43 +++---- .../src/query_operations/macro_template.rs | 2 +- canyon_macros/src/query_operations/select.rs | 5 +- src/lib.rs | 1 - 7 files changed, 91 insertions(+), 119 deletions(-) diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index 32c22555..a3b9ff85 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -45,6 +45,29 @@ impl DbConnection for DatabaseConnection { } } +impl DbConnection for &mut DatabaseConnection { + fn launch<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> impl std::future::Future< + Output = Result>, + > + Send { + async move { + match self { + #[cfg(feature = "postgres")] + DatabaseConnection::Postgres(client) => client.launch(stmt, params).await, + + #[cfg(feature = "mssql")] + DatabaseConnection::SqlServer(client) => client.launch(stmt, params).await, + + #[cfg(feature = "mysql")] + DatabaseConnection::MySQL(client) => client.launch(stmt, params).await, + } + } + } +} + unsafe impl Send for DatabaseConnection {} unsafe impl Sync for DatabaseConnection {} @@ -67,6 +90,17 @@ impl DatabaseConnection { DatabaseType::MySQL => connection_helpers::create_mysql_connection(datasource).await, } } + + pub fn get_db_type(&self) -> DatabaseType { + match self { + #[cfg(feature = "postgres")] + DatabaseConnection::Postgres(conn) => DatabaseType::PostgreSql, + #[cfg(feature = "postgres")] + DatabaseConnection::SqlServer(conn) => DatabaseType::SqlServer, + #[cfg(feature = "postgres")] + DatabaseConnection::MySQL(conn) => DatabaseType::MySQL, + } + } #[cfg(feature = "postgres")] pub fn postgres_connection(&self) -> &PostgreSqlConnection { diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index a570e100..b53bfdc8 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -7,7 +7,8 @@ use crate::{ rows::CanyonRows, }; use std::{fmt::Display, future::Future}; - +use std::error::Error; +use crate::connection::database_type::DatabaseType; // TODO: in order to avoid the tiberius transmute, we should define other method that takes the db_conn as a mut ref pub trait DbConnection { @@ -16,7 +17,30 @@ pub trait DbConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send; + ) -> impl Future>> + Send; + + // TODO: the querybuilder needs to know the underlying db type associated with self, so provide + // a method to obtain it +} + +/// This impl of [` DbConnection` ] for [`&str`] allows the client to use the exposed input types +/// on the public API that works with a generic parameter to refer to a database connection +/// directly with an [`&str`] that must match one of the datasources defined +/// within the user config file +impl DbConnection for &str { + fn launch<'a>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) + -> impl Future>> + Send + { + async move { + let sane_ds_name = if !self.is_empty() { + Some(*self) + } else { + None + }; + let conn = get_database_connection_by_ds(sane_ds_name).await?; + conn.launch(stmt, params).await + } + } } pub trait Transaction { @@ -24,90 +48,17 @@ pub trait Transaction { /// Performs a query against the targeted database by the selected or /// the defaulted datasource, wrapping the resultant collection of entities /// in [`super::rows::CanyonRows`] - fn query<'a, S, Z, I>( + fn query<'a, S, Z>( stmt: S, params: Z, - input: I, - ) -> impl Future>> + Send + input: impl DbConnection + Send + 'a, + ) -> impl Future>> + Send where S: AsRef + Display + Sync + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, - I: Into> + Sync + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a { async move { - let transaction_input: TransactionInput<'a> = input.into(); - let statement = stmt.as_ref(); - let query_parameters = params.as_ref(); - - match transaction_input { - TransactionInput::DbConnection(conn) => { - conn.launch(statement, query_parameters).await - } - TransactionInput::DbConnectionRef(conn) => { - conn.launch(statement, query_parameters).await - } - TransactionInput::DbConnectionRefMut(/* TODO: mut*/ conn) => { - conn.launch(statement, query_parameters).await - } - TransactionInput::DatasourceConfig(ds) => { - // TODO: add a new from_ds_config_mut for mssql - let conn = DatabaseConnection::new(ds).await?; - conn.launch(statement, query_parameters).await - } - TransactionInput::DatasourceName(ds_name) => { - let sane_ds_name = if !ds_name.is_empty() { - Some(ds_name) - } else { - None - }; - let conn = get_database_connection_by_ds(sane_ds_name).await?; - conn.launch(statement, query_parameters).await - } - } + input.launch(stmt.as_ref(), params.as_ref()).await } } } - -pub enum TransactionInput<'a> { - DbConnection(DatabaseConnection), - DbConnectionRef(&'a DatabaseConnection), - DbConnectionRefMut(&'a mut DatabaseConnection), - DatasourceConfig(&'a DatasourceConfig), - DatasourceName(&'a str), -} - -impl From for TransactionInput<'_> { - fn from(conn: DatabaseConnection) -> Self { - TransactionInput::DbConnection(conn) - } -} - -impl<'a> From<&'a DatabaseConnection> for TransactionInput<'a> { - fn from(conn: &'a DatabaseConnection) -> Self { - TransactionInput::DbConnectionRef(conn) - } -} - -impl<'a> From<&'a mut DatabaseConnection> for TransactionInput<'a> { - fn from(conn: &'a mut DatabaseConnection) -> Self { - TransactionInput::DbConnectionRefMut(conn) - } -} - -impl<'a> From<&'a DatasourceConfig> for TransactionInput<'a> { - fn from(ds: &'a DatasourceConfig) -> Self { - TransactionInput::DatasourceConfig(ds) - } -} - -impl<'a> From<&'a str> for TransactionInput<'a> { - fn from(ds_name: &'a str) -> Self { - TransactionInput::DatasourceName(ds_name) - } -} - -impl<'a> From<&'a &'a str> for TransactionInput<'a> { - fn from(ds_name: &'a &'a str) -> Self { - TransactionInput::DatasourceName(ds_name) - } -} diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index f607be75..a476cb31 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -2,8 +2,7 @@ use crate::query_elements::query_builder::{ DeleteQueryBuilder, SelectQueryBuilder, UpdateQueryBuilder, }; use async_trait::async_trait; -use canyon_core::connection::db_connector::DatabaseConnection; -use canyon_core::query::TransactionInput; +use canyon_core::query::DbConnection; use canyon_core::query_parameters::QueryParameter; use canyon_core::{mapper::RowMapper, query::Transaction}; @@ -33,20 +32,19 @@ where input: I, ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> where - I: Into> + Sync + Send + 'a; + I: DbConnection + Send + 'a; async fn find_all_unchecked() -> Vec; async fn find_all_unchecked_with<'a, I>(input: I) -> Vec where - I: Into> + Sync + Send + 'a; + I: DbConnection + Send + 'a; fn select_query<'a>() -> SelectQueryBuilder<'a, T, &'a str>; fn select_query_with<'a, I>(input: I) -> SelectQueryBuilder<'a, T, I> where - I: Into> + Sync + Send + 'a, - TransactionInput<'a>: From<&'a I>; + I: DbConnection + Send + 'a; async fn count() -> Result>; @@ -54,7 +52,7 @@ where input: I, ) -> Result> where - I: Into> + Sync + Send + 'a; + I: DbConnection + Send + 'a; async fn find_by_pk<'a>( value: &'a dyn QueryParameter<'a>, @@ -65,7 +63,7 @@ where input: I, ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> where - I: Into> + Sync + Send + 'a; + I: DbConnection + Send + 'a; // async fn insert<'a>(&mut self) -> Result<(), Box>; diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index fc6dd1c3..19e8f06b 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -5,7 +5,7 @@ use crate::{ Operator, }; use canyon_core::connection::{database_type::DatabaseType, get_database_config, DATASOURCES}; -use canyon_core::query::TransactionInput; +use canyon_core::query::DbConnection; use canyon_core::{mapper::RowMapper, query::Transaction, query_parameters::QueryParameter}; use std::fmt::Debug; use std::marker::PhantomData; @@ -46,7 +46,7 @@ pub mod ops { /// specific operations, like, for example, join operations /// on the [`super::SelectQueryBuilder`], and the usage /// of the `SET` clause on a [`super::UpdateQueryBuilder`], - /// without mixing types or convoluting everything into + /// without mixing types or polluting everything into /// just one type. pub trait QueryBuilder<'a, T> where @@ -138,7 +138,7 @@ pub mod ops { pub struct QueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: Into> + Send + Sync + 'a, + I: DbConnection, { query: Query<'a>, input: I, @@ -149,28 +149,28 @@ where unsafe impl<'a, T, I> Send for QueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: Into> + Send + Sync + 'a, + I: DbConnection + Send + 'a { } unsafe impl<'a, T, I> Sync for QueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: Into> + Send + Sync + 'a, + I: DbConnection + Send + 'a { } impl<'a, T, I> QueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: Into> + Send + Sync + 'a, - TransactionInput<'a>: From<&'a I>, + I: DbConnection + Send + 'a { /// Returns a new instance of the [`QueryBuilder`] pub fn new(query: Query<'a>, input: I) -> Self { + // let ti = input.into(); Self { query, input, - datasource_type: todo!("The from type on the querybuilder"), + datasource_type: todo!(), // DatabaseType::from( // &get_database_config(input, &DATASOURCES).auth, // ), @@ -304,8 +304,7 @@ where pub struct SelectQueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: Into> + Send + Sync + 'a, - TransactionInput<'a>: From<&'a I>, + I: DbConnection + Send + 'a { _inner: QueryBuilder<'a, T, I>, } @@ -313,8 +312,7 @@ where impl<'a, T, I> SelectQueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: Into> + Send + Sync + 'a, - TransactionInput<'a>: From<&'a I>, + I: DbConnection + Send + 'a { /// Generates a new public instance of the [`SelectQueryBuilder`] pub fn new(table_schema_data: &str, input: I) -> Self { @@ -401,8 +399,7 @@ where impl<'a, T, I> ops::QueryBuilder<'a, T> for SelectQueryBuilder<'a, T, I> where T: Debug + CrudOperations + Transaction + RowMapper + Send, - I: Into> + Send + Sync + 'a, - TransactionInput<'a>: From<&'a I>, + I: DbConnection + Send + 'a { #[inline] fn read_sql(&'a self) -> &'a str { @@ -470,8 +467,7 @@ where pub struct UpdateQueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: Into> + Send + Sync + 'a, - TransactionInput<'a>: From<&'a I>, + I: DbConnection + Send + 'a { _inner: QueryBuilder<'a, T, I>, } @@ -479,8 +475,7 @@ where impl<'a, T, I> UpdateQueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: Into> + Send + Sync + 'a, - TransactionInput<'a>: From<&'a I>, + I: DbConnection + Send + 'a { /// Generates a new public instance of the [`UpdateQueryBuilder`] pub fn new(table_schema_data: &str, input: I) -> Self { @@ -544,8 +539,7 @@ where impl<'a, T, I> ops::QueryBuilder<'a, T> for UpdateQueryBuilder<'a, T, I> where T: Debug + CrudOperations + Transaction + RowMapper + Send, - I: Into> + Send + Sync + 'a, - TransactionInput<'a>: From<&'a I>, + I: DbConnection + Send + 'a { #[inline] fn read_sql(&'a self) -> &'a str { @@ -614,8 +608,7 @@ where pub struct DeleteQueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: Into> + Send + Sync + 'a, - TransactionInput<'a>: From<&'a I>, + I: DbConnection + Send + 'a { _inner: QueryBuilder<'a, T, I>, } @@ -623,8 +616,7 @@ where impl<'a, T, I> DeleteQueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: Into> + Send + Sync + 'a, - TransactionInput<'a>: From<&'a I>, + I: DbConnection + Send + 'a { /// Generates a new public instance of the [`DeleteQueryBuilder`] pub fn new(table_schema_data: &str, input: I) -> Self { @@ -647,8 +639,7 @@ where impl<'a, T, I> ops::QueryBuilder<'a, T> for DeleteQueryBuilder<'a, T, I> where T: Debug + CrudOperations + Transaction + RowMapper + Send, - I: Into> + Send + Sync + 'a, - TransactionInput<'a>: From<&'a I>, + I: DbConnection + Send + 'a { #[inline] fn read_sql(&'a self) -> &'a str { diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index 48becec8..c9071ee2 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -123,7 +123,7 @@ impl MacroOperationBuilder { self.input_fwd_arg = Some(quote! { input }); self.lifetime = true; self.where_clause_bounds.push(quote! { - I: Into> + Sync + Send + 'a + I: canyon_sql::core::DbConnection + Send + 'a, }); self } diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index fe08aff4..38b919a3 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -74,8 +74,7 @@ pub fn generate_find_all_query_tokens( /// described in the configuration file, and selected with the [`&str`] /// passed as parameter. fn select_query_with<'a, I>(input: I) -> canyon_sql::query::SelectQueryBuilder<'a, #ty, I> - where I: Into> + Sync + Send + 'a, - canyon_sql::core::TransactionInput<'a>: From<&'a I> + where I: canyon_sql::core::DbConnection + Send + 'a, { canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, input) } @@ -113,7 +112,7 @@ fn generate_find_by_pk_tokens( value: &'a dyn canyon_sql::core::QueryParameter<'a>, input: I ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> - where I: Into> + Sync + Send + 'a + where I: canyon_sql::core::DbConnection + Send + 'a, { Err( std::io::Error::new( diff --git a/src/lib.rs b/src/lib.rs index cb0d2725..6c085d9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,7 +44,6 @@ pub mod core { pub use canyon_core::mapper::*; pub use canyon_core::query::DbConnection; pub use canyon_core::query::Transaction; - pub use canyon_core::query::TransactionInput; pub use canyon_core::query_parameters::QueryParameter; pub use canyon_core::rows::CanyonRows; } From dfe226ecd772375430745eea7a3b6124de586c20 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 26 Jan 2025 11:54:02 +0100 Subject: [PATCH 046/155] feat: The DbConnection trait now is able to tell the client which is the underlying db type for the connection --- .../src/connection/db_clients/mssql.rs | 6 ++++++ .../src/connection/db_clients/mysql.rs | 6 ++++++ .../src/connection/db_clients/postgresql.rs | 6 ++++++ canyon_core/src/connection/db_connector.rs | 21 +++++++++++++------ canyon_core/src/connection/mod.rs | 12 +++++++++-- canyon_core/src/query.rs | 10 +++++++-- tests/crud/querybuilder_operations.rs | 4 ++-- 7 files changed, 53 insertions(+), 12 deletions(-) diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs index 30012fa3..4ff727bb 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -1,8 +1,10 @@ +use std::error::Error; #[cfg(feature = "mssql")] use async_std::net::TcpStream; use crate::{query::DbConnection, query_parameters::QueryParameter, rows::CanyonRows}; use tiberius::Query; +use crate::connection::database_type::DatabaseType; /// A connection with a `SqlServer` database #[cfg(feature = "mssql")] @@ -20,6 +22,10 @@ impl DbConnection for SqlServerConnection { > + Send { sqlserver_query_launcher::launch(stmt, params, self) } + + fn get_database_type(&self) -> Result> { + Ok(DatabaseType::SqlServer) + } } #[cfg(feature = "mssql")] diff --git a/canyon_core/src/connection/db_clients/mysql.rs b/canyon_core/src/connection/db_clients/mysql.rs index eb0d5fbb..404071a6 100644 --- a/canyon_core/src/connection/db_clients/mysql.rs +++ b/canyon_core/src/connection/db_clients/mysql.rs @@ -1,3 +1,4 @@ +use std::error::Error; #[cfg(feature = "mysql")] use mysql_async::Pool; @@ -5,6 +6,7 @@ use crate::{query::DbConnection, query_parameters::QueryParameter, rows::CanyonR use mysql_async::Row; use mysql_common::constants::ColumnType; use mysql_common::row; +use crate::connection::database_type::DatabaseType; /// A connection with a `Mysql` database #[cfg(feature = "mysql")] @@ -22,6 +24,10 @@ impl DbConnection for MysqlConnection { > + Send { mysql_query_launcher::launch(stmt, params, self) } + + fn get_database_type(&self) -> Result> { + Ok(DatabaseType::MySQL) + } } #[cfg(feature = "mysql")] diff --git a/canyon_core/src/connection/db_clients/postgresql.rs b/canyon_core/src/connection/db_clients/postgresql.rs index 7522afbe..7eb17a97 100644 --- a/canyon_core/src/connection/db_clients/postgresql.rs +++ b/canyon_core/src/connection/db_clients/postgresql.rs @@ -1,7 +1,9 @@ +use std::error::Error; use crate::{query::DbConnection, query_parameters::QueryParameter, rows::CanyonRows}; #[cfg(feature = "postgres")] use tokio_postgres::Client; +use crate::connection::database_type::DatabaseType; /// A connection with a `PostgreSQL` database #[cfg(feature = "postgres")] @@ -20,6 +22,10 @@ impl DbConnection for PostgreSqlConnection { > + Send { postgres_query_launcher::launch(stmt, params, self) } + + fn get_database_type(&self) -> Result> { + Ok(DatabaseType::PostgreSql) + } } #[cfg(feature = "postgres")] diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index a3b9ff85..a5a1d147 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -1,3 +1,4 @@ +use std::error::Error; use crate::connection::database_type::DatabaseType; use crate::connection::datasources::DatasourceConfig; use crate::connection::db_clients::mssql::SqlServerConnection; @@ -43,6 +44,10 @@ impl DbConnection for DatabaseConnection { } } } + + fn get_database_type(&self) -> Result> { + Ok(self.get_db_type()) + } } impl DbConnection for &mut DatabaseConnection { @@ -51,7 +56,7 @@ impl DbConnection for &mut DatabaseConnection { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> impl std::future::Future< - Output = Result>, + Output = Result>, > + Send { async move { match self { @@ -66,6 +71,10 @@ impl DbConnection for &mut DatabaseConnection { } } } + + fn get_database_type(&self) -> Result> { + Ok(self.get_db_type()) + } } unsafe impl Send for DatabaseConnection {} @@ -74,7 +83,7 @@ unsafe impl Sync for DatabaseConnection {} impl DatabaseConnection { pub async fn new( datasource: &DatasourceConfig, - ) -> Result> { + ) -> Result> { match datasource.get_db_type() { #[cfg(feature = "postgres")] DatabaseType::PostgreSql => { @@ -90,15 +99,15 @@ impl DatabaseConnection { DatabaseType::MySQL => connection_helpers::create_mysql_connection(datasource).await, } } - + pub fn get_db_type(&self) -> DatabaseType { match self { #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(conn) => DatabaseType::PostgreSql, + DatabaseConnection::Postgres(_) => DatabaseType::PostgreSql, #[cfg(feature = "postgres")] - DatabaseConnection::SqlServer(conn) => DatabaseType::SqlServer, + DatabaseConnection::SqlServer(_) => DatabaseType::SqlServer, #[cfg(feature = "postgres")] - DatabaseConnection::MySQL(conn) => DatabaseType::MySQL, + DatabaseConnection::MySQL(_) => DatabaseType::MySQL, } } diff --git a/canyon_core/src/connection/mod.rs b/canyon_core/src/connection/mod.rs index 194b40b1..24170915 100644 --- a/canyon_core/src/connection/mod.rs +++ b/canyon_core/src/connection/mod.rs @@ -105,9 +105,17 @@ pub async fn get_database_connection_by_ds<'a>( DatabaseConnection::new(ds).await } -fn find_datasource_by_name_or_try_default<'a>( - datasource_name: Option<&str>, +pub fn find_datasource_by_name_or_try_default<'a>( + datasource_name: Option<&str>, // TODO: with the new inputs, we don't want anymore this as Option ) -> Result<&DatasourceConfig, DatasourceNotFound> { + let datasource_name = if let Some(ds_name) = datasource_name { + if !ds_name.is_empty() { + Some(ds_name) + } else { None } + } else { + None + }; + datasource_name .map_or_else( || DATASOURCES.first(), diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs index b53bfdc8..1097d3e9 100644 --- a/canyon_core/src/query.rs +++ b/canyon_core/src/query.rs @@ -9,6 +9,7 @@ use crate::{ use std::{fmt::Display, future::Future}; use std::error::Error; use crate::connection::database_type::DatabaseType; +use crate::connection::find_datasource_by_name_or_try_default; // TODO: in order to avoid the tiberius transmute, we should define other method that takes the db_conn as a mut ref pub trait DbConnection { @@ -18,9 +19,10 @@ pub trait DbConnection { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> impl Future>> + Send; - + // TODO: the querybuilder needs to know the underlying db type associated with self, so provide // a method to obtain it + fn get_database_type(&self) -> Result>; } /// This impl of [` DbConnection` ] for [`&str`] allows the client to use the exposed input types @@ -28,7 +30,7 @@ pub trait DbConnection { /// directly with an [`&str`] that must match one of the datasources defined /// within the user config file impl DbConnection for &str { - fn launch<'a>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) + fn launch<'a>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) -> impl Future>> + Send { async move { @@ -41,6 +43,10 @@ impl DbConnection for &str { conn.launch(stmt, params).await } } + + fn get_database_type(&self) -> Result> { + Ok(find_datasource_by_name_or_try_default(Some(*self))?.get_db_type()) + } } pub trait Transaction { diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index e9a6111a..1ad941aa 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -22,7 +22,7 @@ use crate::tests_models::tournament::*; /// with the parameters that modifies the base SQL to SELECT * FROM #[canyon_sql::macros::canyon_tokio_test] fn test_generated_sql_by_the_select_querybuilder() { - let mut select_with_joins = League::select_query() + let select_with_joins = League::select_query() .inner_join("tournament", "league.id", "tournament.league_id") .left_join("team", "tournament.id", "player.tournament_id") .r#where(LeagueFieldValue::id(&7), Comp::Gt) @@ -32,7 +32,7 @@ fn test_generated_sql_by_the_select_querybuilder() { // .await; // NOTE: We don't have in the docker the generated relationships // with the joins, so for now, we are just going to check that the - // generated SQL by the SelectQueryBuilder is the spected + // generated SQL by the SelectQueryBuilder is the expected assert_eq!( select_with_joins.read_sql(), "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" From 11e8bf5fa9e7dcafa7a1b14f76bff14a5452481a Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 26 Jan 2025 12:02:27 +0100 Subject: [PATCH 047/155] test: re-enabled the integration tests of the SelectQuerybuilder --- canyon_core/src/connection/mod.rs | 9 +- .../src/query_elements/query_builder.rs | 9 +- tests/crud/querybuilder_operations.rs | 720 +++++++++--------- 3 files changed, 366 insertions(+), 372 deletions(-) diff --git a/canyon_core/src/connection/mod.rs b/canyon_core/src/connection/mod.rs index 24170915..b378dfb5 100644 --- a/canyon_core/src/connection/mod.rs +++ b/canyon_core/src/connection/mod.rs @@ -108,13 +108,8 @@ pub async fn get_database_connection_by_ds<'a>( pub fn find_datasource_by_name_or_try_default<'a>( datasource_name: Option<&str>, // TODO: with the new inputs, we don't want anymore this as Option ) -> Result<&DatasourceConfig, DatasourceNotFound> { - let datasource_name = if let Some(ds_name) = datasource_name { - if !ds_name.is_empty() { - Some(ds_name) - } else { None } - } else { - None - }; + let datasource_name = datasource_name + .filter(|&ds_name| !ds_name.is_empty()); datasource_name .map_or_else( diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index 19e8f06b..b3bbf284 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -166,14 +166,13 @@ where { /// Returns a new instance of the [`QueryBuilder`] pub fn new(query: Query<'a>, input: I) -> Self { - // let ti = input.into(); + let db_type = input + .get_database_type() + .expect("QueryBuilder::::get_database_type(). Querybuilder new must return Result on it's public API, refactor it"); // TODO: Self { query, input, - datasource_type: todo!(), - // DatabaseType::from( - // &get_database_config(input, &DATASOURCES).auth, - // ), + datasource_type: db_type, pd: Default::default(), } } diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index 1ad941aa..eca6592e 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -60,397 +60,397 @@ fn test_crud_find_with_querybuilder() { assert_eq!(league_idx_0.region, "KOREA"); } -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_fulllike() { + // Find all the leagues with "LC" in their name + let filtered_leagues_result = League::select_query() + .r#where(LeagueFieldValue::name(&"LC"), Like::Full); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_fulllike_with_mssql() { + // Find all the leagues with "LC" in their name + let filtered_leagues_result = League::select_query_with(SQL_SERVER_DS) + .r#where(LeagueFieldValue::name(&"LC"), Like::Full); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_fulllike_with_mysql() { + // Find all the leagues with "LC" in their name + let filtered_leagues_result = League::select_query_with(MYSQL_DS) + .r#where(LeagueFieldValue::name(&"LC"), Like::Full); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_leftlike() { + // Find all the leagues whose name ends with "CK" + let filtered_leagues_result = League::select_query() + .r#where(LeagueFieldValue::name(&"CK"), Like::Left); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_leftlike_with_mssql() { + // Find all the leagues whose name ends with "CK" + let mut filtered_leagues_result = League::select_query() + .r#where(LeagueFieldValue::name(&"CK"), Like::Left); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_leftlike_with_mysql() { + // Find all the leagues whose name ends with "CK" + let filtered_leagues_result = League::select_query_with(MYSQL_DS) + .r#where(LeagueFieldValue::name(&"CK"), Like::Left); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR))" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_rightlike() { + // Find all the leagues whose name starts with "LC" + let filtered_leagues_result = League::select_query() + .r#where(LeagueFieldValue::name(&"LC"), Like::Right); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_rightlike_with_mssql() { + // Find all the leagues whose name starts with "LC" + let filtered_leagues_result = League::select_query_with(SQL_SERVER_DS) + .r#where(LeagueFieldValue::name(&"LC"), Like::Right); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_rightlike_with_mysql() { + // Find all the leagues whose name starts with "LC" + let filtered_leagues_result = League::select_query_with(MYSQL_DS) + .r#where(LeagueFieldValue::name(&"LC"), Like::Right); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS CHAR) ,'%')" + ) +} + +/// Same than the above but with the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_with_mssql() { + // Find all the players where its ID column value is greater than 50 + let filtered_find_players = Player::select_query_with(SQL_SERVER_DS) + .r#where(PlayerFieldValue::id(&50), Comp::Gt) + .query() + .await; + + assert!(!filtered_find_players.unwrap().is_empty()); +} + +/// Same than the above but with the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_with_mysql() { + // Find all the players where its ID column value is greater than 50 + let filtered_find_players = Player::select_query_with(MYSQL_DS) + .r#where(PlayerFieldValue::id(&50), Comp::Gt) + .query() + .await; + + assert!(!filtered_find_players.unwrap().is_empty()); +} +// +// /// Updates the values of the range on entries defined by the constraint parameters +// /// in the database entity // #[cfg(feature = "postgres")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_fulllike() { -// // Find all the leagues with "LC" in their name -// let mut filtered_leagues_result = League::select_query(); -// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" -// ) +// fn test_crud_update_with_querybuilder() { +// // Find all the leagues with ID less or equals that 7 +// // and where it's region column value is equals to 'Korea' +// let mut q = League::update_query() +// .set(&[ +// (LeagueField::slug, "Updated with the QueryBuilder"), +// (LeagueField::name, "Random"), +// ]) +// .r#where(LeagueFieldValue::id(&1), Comp::Gt) +// .and(LeagueFieldValue::id(&8), Comp::Lt); +// +// /* NOTE: Family of QueryBuilders are clone, useful in case of need to read the generated SQL +// let qpr = q.clone(); +// println!("PSQL: {:?}", qpr.read_sql()); +// */ +// // We can now back to the original an throw the query +// q.query() +// .await +// .expect("Failed to update records with the querybuilder"); +// +// let found_updated_values = League::select_query() +// .r#where(LeagueFieldValue::id(&1), Comp::Gt) +// .and(LeagueFieldValue::id(&7), Comp::Lt) +// .query() +// .await +// .expect("Failed to retrieve database League entries with the querybuilder"); +// +// found_updated_values +// .iter() +// .for_each(|league| assert_eq!(league.slug, "Updated with the QueryBuilder")); // } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM +// +// /// Same as above, but with the specified datasource // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_fulllike_with_mssql() { -// // Find all the leagues with "LC" in their name -// let mut filtered_leagues_result = League::select_query_with(SQL_SERVER_DS); -// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" -// ) +// fn test_crud_update_with_querybuilder_with_mssql() { +// // Find all the leagues with ID less or equals that 7 +// // and where it's region column value is equals to 'Korea' +// let mut q = Player::update_query_with(SQL_SERVER_DS); +// q.set(&[ +// (PlayerField::summoner_name, "Random updated player name"), +// (PlayerField::first_name, "I am an updated first name"), +// ]) +// .r#where(PlayerFieldValue::id(&1), Comp::Gt) +// .and(PlayerFieldValue::id(&8), Comp::Lt) +// .query() +// .await +// .expect("Failed to update records with the querybuilder"); +// +// let found_updated_values = Player::select_query_with(SQL_SERVER_DS) +// .r#where(PlayerFieldValue::id(&1), Comp::Gt) +// .and(PlayerFieldValue::id(&7), Comp::LtEq) +// .query() +// .await +// .expect("Failed to retrieve database League entries with the querybuilder"); +// +// found_updated_values.iter().for_each(|player| { +// assert_eq!(player.summoner_name, "Random updated player name"); +// assert_eq!(player.first_name, "I am an updated first name"); +// }); // } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM +// +// /// Same as above, but with the specified datasource // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_fulllike_with_mysql() { -// // Find all the leagues with "LC" in their name -// let mut filtered_leagues_result = League::select_query_with(MYSQL_DS); -// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR) ,'%')" -// ) +// fn test_crud_update_with_querybuilder_with_mysql() { +// // Find all the leagues with ID less or equals that 7 +// // and where it's region column value is equals to 'Korea' +// +// let mut q = Player::update_query_with(MYSQL_DS); +// q.set(&[ +// (PlayerField::summoner_name, "Random updated player name"), +// (PlayerField::first_name, "I am an updated first name"), +// ]) +// .r#where(PlayerFieldValue::id(&1), Comp::Gt) +// .and(PlayerFieldValue::id(&8), Comp::Lt) +// .query() +// .await +// .expect("Failed to update records with the querybuilder"); +// +// let found_updated_values = Player::select_query_with(MYSQL_DS) +// .r#where(PlayerFieldValue::id(&1), Comp::Gt) +// .and(PlayerFieldValue::id(&7), Comp::LtEq) +// .query() +// .await +// .expect("Failed to retrieve database League entries with the querybuilder"); +// +// found_updated_values.iter().for_each(|player| { +// assert_eq!(player.summoner_name, "Random updated player name"); +// assert_eq!(player.first_name, "I am an updated first name"); +// }); // } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM +// +// /// Deletes entries from the mapped entity `T` that are in the ranges filtered +// /// with the QueryBuilder +// /// +// /// Note if the database is persisted (not created and destroyed on every docker or +// /// GitHub Action wake up), it won't delete things that already have been deleted, +// /// but this isn't an error. They just don't exists. // #[cfg(feature = "postgres")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_leftlike() { -// // Find all the leagues whose name ends with "CK" -// let mut filtered_leagues_result = League::select_query(); -// filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" -// ) +// fn test_crud_delete_with_querybuilder() { +// Tournament::delete_query() +// .r#where(TournamentFieldValue::id(&14), Comp::Gt) +// .and(TournamentFieldValue::id(&16), Comp::Lt) +// .query() +// .await +// .expect("Error connecting with the database on the delete operation"); +// +// assert_eq!(Tournament::find_by_pk(&15).await.unwrap(), None); // } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM +// +// /// Same as the above delete, but with the specified datasource // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_leftlike_with_mssql() { -// // Find all the leagues whose name ends with "CK" -// let mut filtered_leagues_result = League::select_query(); -// filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" -// ) +// fn test_crud_delete_with_querybuilder_with_mssql() { +// Player::delete_query_with(SQL_SERVER_DS) +// .r#where(PlayerFieldValue::id(&120), Comp::Gt) +// .and(PlayerFieldValue::id(&130), Comp::Lt) +// .query() +// .await +// .expect("Error connecting with the database when we are going to delete data! :)"); +// +// assert!(Player::select_query_with(SQL_SERVER_DS) +// .r#where(PlayerFieldValue::id(&122), Comp::Eq) +// .query() +// .await +// .unwrap() +// .is_empty()); // } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM +// +// /// Same as the above delete, but with the specified datasource // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_leftlike_with_mysql() { -// // Find all the leagues whose name ends with "CK" -// let mut filtered_leagues_result = League::select_query_with(MYSQL_DS); -// filtered_leagues_result.r#where(LeagueFieldValue::name(&"CK"), Like::Left); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR))" -// ) +// fn test_crud_delete_with_querybuilder_with_mysql() { +// Player::delete_query_with(MYSQL_DS) +// .r#where(PlayerFieldValue::id(&120), Comp::Gt) +// .and(PlayerFieldValue::id(&130), Comp::Lt) +// .query() +// .await +// .expect("Error connecting with the database when we are going to delete data! :)"); +// +// assert!(Player::select_query_with(MYSQL_DS) +// .r#where(PlayerFieldValue::id(&122), Comp::Eq) +// .query() +// .await +// .unwrap() +// .is_empty()); // } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "postgres")] +// +// /// Tests for the generated SQL query after use the +// /// WHERE clause +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_where_clause() { +// let mut l = League::select_query(); +// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq); +// +// assert_eq!(l.read_sql(), "SELECT * FROM league WHERE name = $1") +// } +// +// /// Tests for the generated SQL query after use the +// /// AND clause // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_rightlike() { -// // Find all the leagues whose name starts with "LC" -// let mut filtered_leagues_result = League::select_query(); -// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); -// +// fn test_and_clause() { +// let mut l = League::select_query(); +// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) +// .and(LeagueFieldValue::id(&10), Comp::LtEq); +// // assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" +// l.read_sql().trim(), +// "SELECT * FROM league WHERE name = $1 AND id <= $2" // ) // } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "mssql")] +// +// /// Tests for the generated SQL query after use the +// /// AND clause // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_rightlike_with_mssql() { -// // Find all the leagues whose name starts with "LC" -// let mut filtered_leagues_result = League::select_query_with(SQL_SERVER_DS); -// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); -// +// fn test_and_clause_with_in_constraint() { +// let mut l = League::select_query(); +// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) +// .and_values_in(LeagueField::id, &[1, 7, 10]); +// // assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" +// l.read_sql().trim(), +// "SELECT * FROM league WHERE name = $1 AND id IN ($1, $2, $3)" // ) // } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "mysql")] +// +// /// Tests for the generated SQL query after use the +// /// AND clause // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_rightlike_with_mysql() { -// // Find all the leagues whose name starts with "LC" -// let mut filtered_leagues_result = League::select_query_with(MYSQL_DS); -// filtered_leagues_result.r#where(LeagueFieldValue::name(&"LC"), Like::Right); -// +// fn test_or_clause() { +// let mut l = League::select_query(); +// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) +// .or(LeagueFieldValue::id(&10), Comp::LtEq); +// // assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS CHAR) ,'%')" +// l.read_sql().trim(), +// "SELECT * FROM league WHERE name = $1 OR id <= $2" // ) // } -// -// /// Same than the above but with the specified datasource -// #[cfg(feature = "mssql")] +// +// /// Tests for the generated SQL query after use the +// /// AND clause // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_with_mssql() { -// // Find all the players where its ID column value is greater that 50 -// let filtered_find_players = Player::select_query_with(SQL_SERVER_DS) -// .r#where(PlayerFieldValue::id(&50), Comp::Gt) -// .query() -// .await; -// -// assert!(!filtered_find_players.unwrap().is_empty()); +// fn test_or_clause_with_in_constraint() { +// let mut l = League::select_query(); +// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) +// .or_values_in(LeagueField::id, &[1, 7, 10]); +// +// assert_eq!( +// l.read_sql(), +// "SELECT * FROM league WHERE name = $1 OR id IN ($1, $2, $3)" +// ) // } -// -// /// Same than the above but with the specified datasource -// #[cfg(feature = "mysql")] +// +// /// Tests for the generated SQL query after use the +// /// AND clause // #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_with_mysql() { -// // Find all the players where its ID column value is greater that 50 -// let filtered_find_players = Player::select_query_with(MYSQL_DS) -// .r#where(PlayerFieldValue::id(&50), Comp::Gt) -// .query() -// .await; -// -// assert!(!filtered_find_players.unwrap().is_empty()); +// fn test_order_by_clause() { +// let mut l = League::select_query(); +// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) +// .order_by(LeagueField::id, false); +// +// assert_eq!( +// l.read_sql(), +// "SELECT * FROM league WHERE name = $1 ORDER BY id" +// ) // } -// -// // /// Updates the values of the range on entries defined by the constraint parameters -// // /// in the database entity -// // #[cfg(feature = "postgres")] -// // #[canyon_sql::macros::canyon_tokio_test] -// // fn test_crud_update_with_querybuilder() { -// // // Find all the leagues with ID less or equals that 7 -// // // and where it's region column value is equals to 'Korea' -// // let mut q = League::update_query(); -// // q.set(&[ -// // (LeagueField::slug, "Updated with the QueryBuilder"), -// // (LeagueField::name, "Random"), -// // ]) -// // .r#where(LeagueFieldValue::id(&1), Comp::Gt) -// // .and(LeagueFieldValue::id(&8), Comp::Lt); -// -// // /* NOTE: Family of QueryBuilders are clone, useful in case of need to read the generated SQL -// // let qpr = q.clone(); -// // println!("PSQL: {:?}", qpr.read_sql()); -// // */ -// // // We can now back to the original an throw the query -// // q.query() -// // .await -// // .expect("Failed to update records with the querybuilder"); -// -// // let found_updated_values = League::select_query() -// // .r#where(LeagueFieldValue::id(&1), Comp::Gt) -// // .and(LeagueFieldValue::id(&7), Comp::Lt) -// // .query() -// // .await -// // .expect("Failed to retrieve database League entries with the querybuilder"); -// -// // found_updated_values -// // .iter() -// // .for_each(|league| assert_eq!(league.slug, "Updated with the QueryBuilder")); -// // } -// -// // /// Same as above, but with the specified datasource -// // #[cfg(feature = "mssql")] -// // #[canyon_sql::macros::canyon_tokio_test] -// // fn test_crud_update_with_querybuilder_with_mssql() { -// // // Find all the leagues with ID less or equals that 7 -// // // and where it's region column value is equals to 'Korea' -// // let mut q = Player::update_query_with(SQL_SERVER_DS); -// // q.set(&[ -// // (PlayerField::summoner_name, "Random updated player name"), -// // (PlayerField::first_name, "I am an updated first name"), -// // ]) -// // .r#where(PlayerFieldValue::id(&1), Comp::Gt) -// // .and(PlayerFieldValue::id(&8), Comp::Lt) -// // .query() -// // .await -// // .expect("Failed to update records with the querybuilder"); -// -// // let found_updated_values = Player::select_query_with(SQL_SERVER_DS) -// // .r#where(PlayerFieldValue::id(&1), Comp::Gt) -// // .and(PlayerFieldValue::id(&7), Comp::LtEq) -// // .query() -// // .await -// // .expect("Failed to retrieve database League entries with the querybuilder"); -// -// // found_updated_values.iter().for_each(|player| { -// // assert_eq!(player.summoner_name, "Random updated player name"); -// // assert_eq!(player.first_name, "I am an updated first name"); -// // }); -// // } -// -// // /// Same as above, but with the specified datasource -// // #[cfg(feature = "mysql")] -// // #[canyon_sql::macros::canyon_tokio_test] -// // fn test_crud_update_with_querybuilder_with_mysql() { -// // // Find all the leagues with ID less or equals that 7 -// // // and where it's region column value is equals to 'Korea' -// -// // let mut q = Player::update_query_with(MYSQL_DS); -// // q.set(&[ -// // (PlayerField::summoner_name, "Random updated player name"), -// // (PlayerField::first_name, "I am an updated first name"), -// // ]) -// // .r#where(PlayerFieldValue::id(&1), Comp::Gt) -// // .and(PlayerFieldValue::id(&8), Comp::Lt) -// // .query() -// // .await -// // .expect("Failed to update records with the querybuilder"); -// -// // let found_updated_values = Player::select_query_with(MYSQL_DS) -// // .r#where(PlayerFieldValue::id(&1), Comp::Gt) -// // .and(PlayerFieldValue::id(&7), Comp::LtEq) -// // .query() -// // .await -// // .expect("Failed to retrieve database League entries with the querybuilder"); -// -// // found_updated_values.iter().for_each(|player| { -// // assert_eq!(player.summoner_name, "Random updated player name"); -// // assert_eq!(player.first_name, "I am an updated first name"); -// // }); -// // } -// -// // /// Deletes entries from the mapped entity `T` that are in the ranges filtered -// // /// with the QueryBuilder -// // /// -// // /// Note if the database is persisted (not created and destroyed on every docker or -// // /// GitHub Action wake up), it won't delete things that already have been deleted, -// // /// but this isn't an error. They just don't exists. -// // #[cfg(feature = "postgres")] -// // #[canyon_sql::macros::canyon_tokio_test] -// // fn test_crud_delete_with_querybuilder() { -// // Tournament::delete_query() -// // .r#where(TournamentFieldValue::id(&14), Comp::Gt) -// // .and(TournamentFieldValue::id(&16), Comp::Lt) -// // .query() -// // .await -// // .expect("Error connecting with the database on the delete operation"); -// -// // assert_eq!(Tournament::find_by_pk(&15).await.unwrap(), None); -// // } -// -// // /// Same as the above delete, but with the specified datasource -// // #[cfg(feature = "mssql")] -// // #[canyon_sql::macros::canyon_tokio_test] -// // fn test_crud_delete_with_querybuilder_with_mssql() { -// // Player::delete_query_with(SQL_SERVER_DS) -// // .r#where(PlayerFieldValue::id(&120), Comp::Gt) -// // .and(PlayerFieldValue::id(&130), Comp::Lt) -// // .query() -// // .await -// // .expect("Error connecting with the database when we are going to delete data! :)"); -// -// // assert!(Player::select_query_with(SQL_SERVER_DS) -// // .r#where(PlayerFieldValue::id(&122), Comp::Eq) -// // .query() -// // .await -// // .unwrap() -// // .is_empty()); -// // } -// -// // /// Same as the above delete, but with the specified datasource -// // #[cfg(feature = "mysql")] -// // #[canyon_sql::macros::canyon_tokio_test] -// // fn test_crud_delete_with_querybuilder_with_mysql() { -// // Player::delete_query_with(MYSQL_DS) -// // .r#where(PlayerFieldValue::id(&120), Comp::Gt) -// // .and(PlayerFieldValue::id(&130), Comp::Lt) -// // .query() -// // .await -// // .expect("Error connecting with the database when we are going to delete data! :)"); -// -// // assert!(Player::select_query_with(MYSQL_DS) -// // .r#where(PlayerFieldValue::id(&122), Comp::Eq) -// // .query() -// // .await -// // .unwrap() -// // .is_empty()); -// // } -// -// // /// Tests for the generated SQL query after use the -// // /// WHERE clause -// // #[canyon_sql::macros::canyon_tokio_test] -// // fn test_where_clause() { -// // let mut l = League::select_query(); -// // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq); -// -// // assert_eq!(l.read_sql(), "SELECT * FROM league WHERE name = $1") -// // } -// -// // /// Tests for the generated SQL query after use the -// // /// AND clause -// // #[canyon_sql::macros::canyon_tokio_test] -// // fn test_and_clause() { -// // let mut l = League::select_query(); -// // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) -// // .and(LeagueFieldValue::id(&10), Comp::LtEq); -// -// // assert_eq!( -// // l.read_sql().trim(), -// // "SELECT * FROM league WHERE name = $1 AND id <= $2" -// // ) -// // } -// -// // /// Tests for the generated SQL query after use the -// // /// AND clause -// // #[canyon_sql::macros::canyon_tokio_test] -// // fn test_and_clause_with_in_constraint() { -// // let mut l = League::select_query(); -// // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) -// // .and_values_in(LeagueField::id, &[1, 7, 10]); -// -// // assert_eq!( -// // l.read_sql().trim(), -// // "SELECT * FROM league WHERE name = $1 AND id IN ($1, $2, $3)" -// // ) -// // } -// -// // /// Tests for the generated SQL query after use the -// // /// AND clause -// // #[canyon_sql::macros::canyon_tokio_test] -// // fn test_or_clause() { -// // let mut l = League::select_query(); -// // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) -// // .or(LeagueFieldValue::id(&10), Comp::LtEq); -// -// // assert_eq!( -// // l.read_sql().trim(), -// // "SELECT * FROM league WHERE name = $1 OR id <= $2" -// // ) -// // } -// -// // /// Tests for the generated SQL query after use the -// // /// AND clause -// // #[canyon_sql::macros::canyon_tokio_test] -// // fn test_or_clause_with_in_constraint() { -// // let mut l = League::select_query(); -// // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) -// // .or_values_in(LeagueField::id, &[1, 7, 10]); -// -// // assert_eq!( -// // l.read_sql(), -// // "SELECT * FROM league WHERE name = $1 OR id IN ($1, $2, $3)" -// // ) -// // } -// -// // /// Tests for the generated SQL query after use the -// // /// AND clause -// // #[canyon_sql::macros::canyon_tokio_test] -// // fn test_order_by_clause() { -// // let mut l = League::select_query(); -// // l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) -// // .order_by(LeagueField::id, false); -// -// // assert_eq!( -// // l.read_sql(), -// // "SELECT * FROM league WHERE name = $1 ORDER BY id" -// // ) -// // } From b9c1046783d58fec627509895cb50099f5e6763d Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 26 Jan 2025 19:28:49 +0100 Subject: [PATCH 048/155] feat: re-enabled the insert and delelte operations --- canyon_crud/src/crud.rs | 24 +- canyon_macros/src/lib.rs | 20 +- canyon_macros/src/query_operations/delete.rs | 134 +-- .../src/query_operations/doc_comments.rs | 9 + canyon_macros/src/query_operations/insert.rs | 816 +++++++++--------- .../src/query_operations/macro_template.rs | 80 +- tests/crud/delete_operations.rs | 116 +-- tests/crud/insert_operations.rs | 582 ++++++------- 8 files changed, 950 insertions(+), 831 deletions(-) diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index a476cb31..44e923ec 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -65,12 +65,14 @@ where where I: DbConnection + Send + 'a; - // async fn insert<'a>(&mut self) -> Result<(), Box>; + async fn insert(&mut self) -> Result<(), Box>; - // async fn insert_with<'a>( - // &mut self, - // datasource_name: &'a str, - // ) -> Result<(), Box>; + async fn insert_with<'a, I>( + &mut self, + input: I, + ) -> Result<(), Box> + where + I: DbConnection + Send + 'a; // async fn multi_insert<'a>( // instances: &'a mut [&'a mut T], @@ -92,12 +94,14 @@ where // fn update_query_with(datasource_name: &str) -> UpdateQueryBuilder<'_, T>; - // async fn delete(&self) -> Result<(), Box>; + async fn delete(&self) -> Result<(), Box>; - // async fn delete_with<'a>( - // &self, - // datasource_name: &'a str, - // ) -> Result<(), Box>; + async fn delete_with<'a, I>( + &self, + input: I, + ) -> Result<(), Box> + where + I: DbConnection + Send + 'a; // fn delete_query<'a>() -> DeleteQueryBuilder<'a, T>; diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index 80197667..afe61f67 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -20,15 +20,17 @@ use quote::quote; use syn::{DeriveInput, Fields, Type, Visibility}; use query_operations::{ - delete::{generate_delete_query_tokens, generate_delete_tokens}, - insert::{generate_insert_tokens, generate_multiple_insert_tokens}, select::{ + generate_read_operations_tokens, generate_find_all_query_tokens, // generate_find_by_foreign_key_tokens, // generate_find_by_reverse_foreign_key_tokens, - generate_read_operations_tokens, + }, + insert::{generate_insert_tokens, + // generate_multiple_insert_tokens }, update::{generate_update_query_tokens, generate_update_tokens}, + delete::{generate_delete_query_tokens, generate_delete_tokens}, }; use utils::{function_parser::FunctionParser, helpers, macro_tokens::MacroTokens}; @@ -253,9 +255,9 @@ fn impl_crud_operations_trait_for_struct( let find_all_query_tokens = generate_find_all_query_tokens(macro_data, &table_schema_data); // Builds the insert() query - let _insert_tokens = generate_insert_tokens(macro_data, &table_schema_data); + let insert_tokens = generate_insert_tokens(macro_data, &table_schema_data); // Builds the insert_multi() query - let _insert_multi_tokens = generate_multiple_insert_tokens(macro_data, &table_schema_data); + // let _insert_multi_tokens = generate_multiple_insert_tokens(macro_data, &table_schema_data); // Builds the update() queries let _update_tokens = generate_update_tokens(macro_data, &table_schema_data); @@ -263,7 +265,7 @@ fn impl_crud_operations_trait_for_struct( let _update_query_tokens = generate_update_query_tokens(macro_data, &table_schema_data); // Builds the delete() queries - let _delete_tokens = generate_delete_tokens(macro_data, &table_schema_data); + let delete_tokens = generate_delete_tokens(macro_data, &table_schema_data); // Builds the delete() query as a QueryBuilder let _delete_query_tokens = generate_delete_query_tokens(macro_data, &table_schema_data); @@ -298,8 +300,8 @@ fn impl_crud_operations_trait_for_struct( // // The find_by_pk impl // #_find_by_pk_tokens - // // The insert impl - // #_insert_tokens + // The insert impl + #insert_tokens // // The insert of multiple entities impl // #_insert_multi_tokens @@ -311,7 +313,7 @@ fn impl_crud_operations_trait_for_struct( // #_update_query_tokens // // The delete impl - // #_delete_tokens + #delete_tokens // // The delete as querybuilder impl // #_delete_query_tokens diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index 509a4d9e..9026caea 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -1,6 +1,7 @@ use proc_macro2::TokenStream; use quote::quote; - +use syn::Type; +use crate::query_operations::delete::__details::{create_delete_err_macro, create_delete_err_with_macro, create_delete_macro, create_delete_with_macro}; use crate::utils::macro_tokens::MacroTokens; /// Generates the TokenStream for the __delete() CRUD operation @@ -11,6 +12,9 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let fields = macro_data.get_struct_fields(); let pk = macro_data.get_primary_key_annotation(); + let ret_ty: Type = syn::parse_str("()").expect("Failed to parse unit type"); + let q_ret_ty: TokenStream = quote!{#ret_ty}; + if let Some(primary_key) = pk { let pk_field = fields .iter() @@ -21,60 +25,22 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let pk_field_value = quote! { &self.#pk_field as &dyn canyon_sql::core::QueryParameter<'_> }; + let stmt = format!("DELETE FROM {} WHERE {:?} = $1", table_schema_data, primary_key); + + let delete_tokens = create_delete_macro(ty, &stmt, &pk_field_value, &q_ret_ty); + let delete_with_tokens = create_delete_with_macro(ty, &stmt, &pk_field_value, &q_ret_ty); + quote! { - // /// Deletes from a database entity the row that matches - // /// the current instance of a T type, returning a result - // /// indicating a possible failure querying the database. - // async fn delete(&self) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'static)>> { - // <#ty as canyon_sql::core::Transaction<#ty>>::query( - // format!("DELETE FROM {} WHERE {:?} = $1", #table_schema_data, #primary_key), - // &[#pk_field_value], - // "" - // ).await?; - - // Ok(()) - // } - - // /// Deletes from a database entity the row that matches - // /// the current instance of a T type, returning a result - // /// indicating a possible failure querying the database with the specified datasource. - // async fn delete_with<'a, I>(&self, input: I) - // -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'static)>> - // { - // <#ty as canyon_sql::core::Transaction<#ty>>::query( - // format!("DELETE FROM {} WHERE {:?} = $1", #table_schema_data, #primary_key), - // &[#pk_field_value], - // datasource_name - // ).await?; - - // Ok(()) - // } + #delete_tokens + #delete_with_tokens } } else { - // Delete operation over an instance isn't available without declaring a primary key. - // The delete querybuilder variant must be used for the case when there's no pk declared + let delete_err_tokens = create_delete_err_macro(ty, &q_ret_ty); + let delete_err_with_tokens = create_delete_err_with_macro(ty, &q_ret_ty); + quote! { - // async fn delete(&self) - // -> Result<(), Box> - // { - // Err(std::io::Error::new( - // std::io::ErrorKind::Unsupported, - // "You can't use the 'delete' method on a \ - // CanyonEntity that does not have a #[primary_key] annotation. \ - // If you need to perform an specific search, use the Querybuilder instead." - // ).into_inner().unwrap()) - // } - - // async fn delete_with<'a, I>(&self, input: I) - // -> Result<(), Box> - // { - // Err(std::io::Error::new( - // std::io::ErrorKind::Unsupported, - // "You can't use the 'delete_with' method on a \ - // CanyonEntity that does not have a #[primary_key] annotation. \ - // If you need to perform an specific search, use the Querybuilder instead." - // ).into_inner().unwrap()) - // } + #delete_err_tokens + #delete_err_with_tokens } } } @@ -83,7 +49,7 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri /// [`query_elements::query_builder::QueryBuilder<'a, #ty>`] pub fn generate_delete_query_tokens( macro_data: &MacroTokens, - table_schema_data: &String, + table_schema_data: &str, ) -> TokenStream { let ty = macro_data.ty; @@ -115,3 +81,67 @@ pub fn generate_delete_query_tokens( // } } } + +mod __details { + use proc_macro2::Span; + use crate::query_operations::doc_comments; + use crate::query_operations::macro_template::MacroOperationBuilder; + use super::*; + + pub fn create_delete_macro(ty: &syn::Ident, stmt: &str, pk_field_value: &TokenStream, ret_ty: &TokenStream) -> MacroOperationBuilder { + MacroOperationBuilder::new() + .fn_name("delete") + .with_self_as_ref() + .user_type(ty) + .return_type_ts(ret_ty) + .raw_return() + .add_doc_comment(doc_comments::DELETE) + .query_string(stmt) + .forwarded_parameters(quote!{&[#pk_field_value]}) + .propagate_transaction_result() + .disable_mapping() + .raw_return() + .with_no_result_value() + } + + pub fn create_delete_with_macro(ty: &syn::Ident, stmt: &str, pk_field_value: &TokenStream, ret_ty: &TokenStream) -> MacroOperationBuilder { + MacroOperationBuilder::new() + .fn_name("delete_with") + .with_self_as_ref() + .with_input_param() + .user_type(ty) + .return_type_ts(ret_ty) + .raw_return() + .add_doc_comment(doc_comments::DELETE) + .add_doc_comment(doc_comments::DS_ADVERTISING) + .query_string(stmt) + .forwarded_parameters(quote!{&[#pk_field_value]}) + .propagate_transaction_result() + .disable_mapping() + .raw_return() + .with_no_result_value() + } + + pub fn create_delete_err_macro(ty: &syn::Ident, ret_ty: &TokenStream) -> MacroOperationBuilder { + MacroOperationBuilder::new() + .fn_name("delete") + .with_self_as_ref() + .user_type(ty) + .return_type_ts(ret_ty) + .raw_return() + .add_doc_comment(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) + .with_direct_error_return(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) + } + + pub fn create_delete_err_with_macro(ty: &syn::Ident, ret_ty: &TokenStream) -> MacroOperationBuilder { + MacroOperationBuilder::new() + .fn_name("delete_with") + .with_self_as_ref() + .with_input_param() + .user_type(ty) + .return_type_ts(ret_ty) + .raw_return() + .add_doc_comment(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) + .with_direct_error_return(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) + } +} diff --git a/canyon_macros/src/query_operations/doc_comments.rs b/canyon_macros/src/query_operations/doc_comments.rs index 5fadd44c..322417a4 100644 --- a/canyon_macros/src/query_operations/doc_comments.rs +++ b/canyon_macros/src/query_operations/doc_comments.rs @@ -30,3 +30,12 @@ pub const DS_ADVERTISING: &str = "/// The query it's made against the database with the configured datasource \ /// described in the configuration file, and selected with the [`&str`] \ /// passed as parameter."; + +pub const DELETE: &str = "Deletes from a database entity the row that matches + the current instance of a T type based on the actual value of the primary + key field, returning a result + indicating a possible failure querying the database."; + +pub const UNAVAILABLE_CRUD_OP_ON_INSTANCE: &str = "Operation is unavailable. T doesn't contain a #[primary_key]\ + annotation. You must construct the query with the QueryBuilder type\ + (_query method for the CrudOperations implementors"; \ No newline at end of file diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 9fe3ee13..214d1232 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -52,7 +52,7 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let rows = <#ty as canyon_sql::core::Transaction<#ty>>::query( stmt, values, - datasource_name + input ).await?; match rows { @@ -98,7 +98,7 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri <#ty as canyon_sql::core::Transaction<#ty>>::query( stmt, values, - datasource_name + input ).await?; Ok(()) @@ -106,414 +106,416 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri }; quote! { - // / Inserts into a database entity the current data in `self`, generating a new - // / entry (row), returning the `PRIMARY KEY` = `self.` with the specified - // / datasource by it's `datasouce name`, defined in the configuration file. - // / - // / This `insert` operation needs a `&mut` reference. That's because typically, - // / an insert operation represents *new* data stored in the database, so, when - // / inserted, the database will generate a unique new value for the - // / `pk` field, having a unique identifier for every record, and it will - // / automatically assign that returned pk to `self.`. So, after the `insert` - // / operation, you instance will have the correct value that is the *PRIMARY KEY* - // / of the database row that represents. - // / - // / This operation returns a result type, indicating a possible failure querying the database. - // / - // / ## *Examples* - // /``` - // / let mut lec: League = League { - // / id: Default::default(), - // / ext_id: 1, - // / slug: "LEC".to_string(), - // / name: "League Europe Champions".to_string(), - // / region: "EU West".to_string(), - // / image_url: "https://lec.eu".to_string(), - // / }; - // / - // / println!("LEC before: {:?}", &lec); - // / - // / let ins_result = lec.insert_result().await; - // / - // / Now, we can handle the result returned, because it can contains a - // / critical error that may leads your program to panic - // / if let Ok(_) = ins_result { - // / println!("LEC after: {:?}", &lec); - // / } else { - // / eprintln!("{:?}", ins_result.err()) - // / } - // / ``` - // / - // async fn insert<'a>(&mut self) - // -> Result<(), Box> - // { - // let datasource_name = ""; - // let mut values: Vec<&dyn canyon_sql::core::QueryParameter<'_>> = vec![#(#insert_values),*]; - // #insert_transaction - // } - - // / Inserts into a database entity the current data in `self`, generating a new - // / entry (row), returning the `PRIMARY KEY` = `self.` with the specified - // / datasource by it's `datasouce name`, defined in the configuration file. - // / - // / This `insert` operation needs a `&mut` reference. That's because typically, - // / an insert operation represents *new* data stored in the database, so, when - // / inserted, the database will generate a unique new value for the - // / `pk` field, having a unique identifier for every record, and it will - // / automatically assign that returned pk to `self.`. So, after the `insert` - // / operation, you instance will have the correct value that is the *PRIMARY KEY* - // / of the database row that represents. - // / - // / This operation returns a result type, indicating a possible failure querying the database. - // / - // / ## *Examples* - // /``` - // / let mut lec: League = League { - // / id: Default::default(), - // / ext_id: 1, - // / slug: "LEC".to_string(), - // / name: "League Europe Champions".to_string(), - // / region: "EU West".to_string(), - // / image_url: "https://lec.eu".to_string(), - // / }; - // / - // / println!("LEC before: {:?}", &lec); - // / - // / let ins_result = lec.insert_result().await; - // / - // / Now, we can handle the result returned, because it can contains a - // / critical error that may leads your program to panic - // / if let Ok(_) = ins_result { - // / println!("LEC after: {:?}", &lec); - // / } else { - // / eprintln!("{:?}", ins_result.err()) - // / } - // / ``` - // / - // async fn insert_with<'a, I>(&mut self, input: I) - // -> Result<(), Box> - // { - // let mut values: Vec<&dyn canyon_sql::core::QueryParameter<'_>> = vec![#(#insert_values_cloned),*]; - // #insert_transaction - // } - - } -} - -/// Generates the TokenStream for the __insert() CRUD operation, but being available -/// as a [`QueryBuilder`] object, and instead of being a method over some [`T`] type, -/// as an associated function for [`T`] -/// -/// This, also lets the user to have the option to be able to insert multiple -/// [`T`] objects in only one query -pub fn generate_multiple_insert_tokens( - macro_data: &MacroTokens, - table_schema_data: &String, -) -> TokenStream { - let ty = macro_data.ty; - - // Retrieves the fields of the Struct as continuous String - let column_names = macro_data.get_struct_fields_as_strings(); - - // Retrieves the fields of the Struct - let fields = macro_data.get_struct_fields(); - - let macro_fields = fields.iter().map(|field| quote! { &instance.#field }); - let macro_fields_cloned = macro_fields.clone(); - - let pk = macro_data.get_primary_key_annotation().unwrap_or_default(); - - let pk_ident_type = macro_data - ._fields_with_types() - .into_iter() - .find(|(i, _t)| *i == pk); - - let multi_insert_transaction = if let Some(pk_data) = &pk_ident_type { - let pk_ident = &pk_data.0; - let pk_type = &pk_data.1; - - quote! { - mapped_fields = #column_names - .split(", ") - .map( |column_name| format!("\"{}\"", column_name)) - .collect::>() - .join(", "); - - let mut split = mapped_fields.split(", ") - .collect::>(); - - let pk_value_index = split.iter() - .position(|pk| *pk == format!("\"{}\"", #pk).as_str()) - .expect("Error. No primary key found when should be there"); - split.retain(|pk| *pk != format!("\"{}\"", #pk).as_str()); - mapped_fields = split.join(", ").to_string(); - - let mut fields_placeholders = String::new(); - - let mut elements_counter = 0; - let mut values_counter = 1; - let values_arr_len = final_values.len(); - - for vector in final_values.iter_mut() { - let mut inner_counter = 0; - fields_placeholders.push('('); - vector.remove(pk_value_index); - - for _value in vector.iter() { - if inner_counter < vector.len() - 1 { - fields_placeholders.push_str(&("$".to_owned() + &values_counter.to_string() + ",")); - } else { - fields_placeholders.push_str(&("$".to_owned() + &values_counter.to_string())); - } - - inner_counter += 1; - values_counter += 1; - } - - elements_counter += 1; - - if elements_counter < values_arr_len { - fields_placeholders.push_str("), "); - } else { - fields_placeholders.push(')'); - } - } - - let stmt = format!( - "INSERT INTO {} ({}) VALUES {} RETURNING {}", - #table_schema_data, - mapped_fields, - fields_placeholders, - #pk - ); - - let mut v_arr = Vec::new(); - for arr in final_values.iter() { - for value in arr { - v_arr.push(*value) - } - } - - let multi_insert_result = <#ty as canyon_sql::core::Transaction<#ty>>::query( - stmt, - v_arr, - datasource_name - ).await?; - - match multi_insert_result { - #[cfg(feature="postgres")] - canyon_sql::core::CanyonRows::Postgres(mut v) => { - for (idx, instance) in instances.iter_mut().enumerate() { - instance.#pk_ident = v - .get(idx) - .expect("Failed getting the returned IDs for a multi insert") - .get::<&str, #pk_type>(#pk); - } - - Ok(()) - }, - #[cfg(feature="mssql")] - canyon_sql::core::CanyonRows::Tiberius(mut v) => { - for (idx, instance) in instances.iter_mut().enumerate() { - instance.#pk_ident = v - .get(idx) - .expect("Failed getting the returned IDs for a multi insert") - .get::<#pk_type, &str>(#pk) - .expect("SQL Server primary key type failed to be set as value"); - } - - Ok(()) - }, - #[cfg(feature="mysql")] - canyon_sql::core::CanyonRows::MySQL(mut v) => { - for (idx, instance) in instances.iter_mut().enumerate() { - instance.#pk_ident = v - .get(idx) - .expect("Failed getting the returned IDs for a multi insert") - .get::<#pk_type,usize>(0) - .expect("MYSQL primary key type failed to be set as value"); - } - Ok(()) - }, - _ => panic!() // TODO remove when the generics will be refactored - } + /// Inserts into a database entity the current data in `self`, generating a new + /// entry (row), returning the `PRIMARY KEY` = `self.` with the specified + /// datasource by its `datasource name`, defined in the configuration file. + /// + /// This `insert` operation needs a `&mut` reference. That's because typically, + /// an insert operation represents *new* data stored in the database, so, when + /// inserted, the database will generate a unique new value for the + /// `pk` field, having a unique identifier for every record, and it will + /// automatically assign that returned pk to `self.`. So, after the `insert` + /// operation, you instance will have the correct value that is the *PRIMARY KEY* + /// of the database row that represents. + /// + /// This operation returns a result type, indicating a possible failure querying the database. + /// + /// ## *Examples* + ///``` + /// let mut lec: League = League { + /// id: Default::default(), + /// ext_id: 1, + /// slug: "LEC".to_string(), + /// name: "League Europe Champions".to_string(), + /// region: "EU West".to_string(), + /// image_url: "https://lec.eu".to_string(), + /// }; + /// + /// println!("LEC before: {:?}", &lec); + /// + /// let ins_result = lec.insert_result().await; + /// + /// // Now, we can handle the result returned, because it can contain a + /// // critical error that may lead your program to panic + /// if let Ok(_) = ins_result { + /// println!("LEC after: {:?}", &lec); + /// } else { + /// eprintln!("{:?}", ins_result.err()) + /// } + /// ``` + /// + async fn insert(&mut self) + -> Result<(), Box> + { + let input = ""; + let mut values: Vec<&dyn canyon_sql::core::QueryParameter<'_>> = vec![#(#insert_values),*]; + #insert_transaction } - } else { - quote! { - mapped_fields = #column_names - .split(", ") - .map( |column_name| format!("\"{}\"", column_name)) - .collect::>() - .join(", "); - - let mut split = mapped_fields.split(", ") - .collect::>(); - - let mut fields_placeholders = String::new(); - - let mut elements_counter = 0; - let mut values_counter = 1; - let values_arr_len = final_values.len(); - - for vector in final_values.iter_mut() { - let mut inner_counter = 0; - fields_placeholders.push('('); - - for _value in vector.iter() { - if inner_counter < vector.len() - 1 { - fields_placeholders.push_str(&("$".to_owned() + &values_counter.to_string() + ",")); - } else { - fields_placeholders.push_str(&("$".to_owned() + &values_counter.to_string())); - } - - inner_counter += 1; - values_counter += 1; - } - - elements_counter += 1; - - if elements_counter < values_arr_len { - fields_placeholders.push_str("), "); - } else { - fields_placeholders.push(')'); - } - } - - let stmt = format!( - "INSERT INTO {} ({}) VALUES {}", - #table_schema_data, - mapped_fields, - fields_placeholders - ); - - let mut v_arr = Vec::new(); - for arr in final_values.iter() { - for value in arr { - v_arr.push(*value) - } - } - <#ty as canyon_sql::core::Transaction<#ty>>::query( - stmt, - v_arr, - datasource_name - ).await?; - - Ok(()) + /// Inserts into a database entity the current data in `self`, generating a new + /// entry (row), returning the `PRIMARY KEY` = `self.` with the specified + /// datasource by its `datasource name`, defined in the configuration file. + /// + /// This `insert` operation needs a `&mut` reference. That's because typically, + /// an insert operation represents *new* data stored in the database, so, when + /// inserted, the database will generate a unique new value for the + /// `pk` field, having a unique identifier for every record, and it will + /// automatically assign that returned pk to `self.`. So, after the `insert` + /// operation, your instance will have the correct value that is the *PRIMARY KEY* + /// of the database row that represents. + /// + /// This operation returns a result type, indicating a possible failure querying the database. + /// + /// ## *Examples* + ///``` + /// let mut lec: League = League { + /// id: Default::default(), + /// ext_id: 1, + /// slug: "LEC".to_string(), + /// name: "League Europe Champions".to_string(), + /// region: "EU West".to_string(), + /// image_url: "https://lec.eu".to_string(), + /// }; + /// + /// println!("LEC before: {:?}", &lec); + /// + /// let ins_result = lec.insert_result().await; + /// + /// // Now, we can handle the result returned, because it can contains a + /// // critical error that may leads your program to panic + /// if let Ok(_) = ins_result { + /// println!("LEC after: {:?}", &lec); + /// } else { + /// eprintln!("{:?}", ins_result.err()) + /// } + /// ``` + /// + async fn insert_with<'a, I>(&mut self, input: I) + -> Result<(), Box> + where + I: canyon_sql::core::DbConnection + Send + 'a + { + let mut values: Vec<&dyn canyon_sql::core::QueryParameter<'_>> = vec![#(#insert_values_cloned),*]; + #insert_transaction } - }; - - quote! { - // /// Inserts multiple instances of some type `T` into its related table. - // /// - // /// ``` - // /// let mut new_league = League { - // /// id: Default::default(), - // /// ext_id: 392489032, - // /// slug: "League10".to_owned(), - // /// name: "League10also".to_owned(), - // /// region: "Turkey".to_owned(), - // /// image_url: "https://www.sdklafjsd.com".to_owned() - // /// }; - // /// let mut new_league2 = League { - // /// id: Default::default(), - // /// ext_id: 392489032, - // /// slug: "League11".to_owned(), - // /// name: "League11also".to_owned(), - // /// region: "LDASKJF".to_owned(), - // /// image_url: "https://www.sdklafjsd.com".to_owned() - // /// }; - // /// let mut new_league3 = League { - // /// id: Default::default(), - // /// ext_id: 9687392489032, - // /// slug: "League3".to_owned(), - // /// name: "3League".to_owned(), - // /// region: "EU".to_owned(), - // /// image_url: "https://www.lag.com".to_owned() - // /// }; - // /// - // /// League::insert_multiple( - // /// &mut [&mut new_league, &mut new_league2, &mut new_league3] - // /// ).await - // /// .ok(); - // /// ``` - // // async fn multi_insert<'a>(instances: &'a mut [&'a mut #ty]) -> ( - // // Result<(), Box> - // // ) { - // // use canyon_sql::core::QueryParameter; - // // let datasource_name = ""; - - // // let mut final_values: Vec>> = Vec::new(); - // // for instance in instances.iter() { - // // let intermediate: &[&dyn QueryParameter<'_>] = &[#(#macro_fields),*]; - - // // let mut longer_lived: Vec<&dyn QueryParameter<'_>> = Vec::new(); - // // for value in intermediate.into_iter() { - // // longer_lived.push(*value) - // // } - - // // final_values.push(longer_lived) - // // } - - // // let mut mapped_fields: String = String::new(); - - // // #multi_insert_transaction - // // } - - // /// Inserts multiple instances of some type `T` into its related table with the specified - // /// datasource by it's `datasouce name`, defined in the configuration file. - // /// - // /// ``` - // /// let mut new_league = League { - // /// id: Default::default(), - // /// ext_id: 392489032, - // /// slug: "League10".to_owned(), - // /// name: "League10also".to_owned(), - // /// region: "Turkey".to_owned(), - // /// image_url: "https://www.sdklafjsd.com".to_owned() - // /// }; - // /// let mut new_league2 = League { - // /// id: Default::default(), - // /// ext_id: 392489032, - // /// slug: "League11".to_owned(), - // /// name: "League11also".to_owned(), - // /// region: "LDASKJF".to_owned(), - // /// image_url: "https://www.sdklafjsd.com".to_owned() - // /// }; - // /// let mut new_league3 = League { - // /// id: Default::default(), - // /// ext_id: 9687392489032, - // /// slug: "League3".to_owned(), - // /// name: "3League".to_owned(), - // /// region: "EU".to_owned(), - // /// image_url: "https://www.lag.com".to_owned() - // /// }; - // /// - // /// League::insert_multiple( - // /// &mut [&mut new_league, &mut new_league2, &mut new_league3] - // /// ).await - // /// .ok(); - // /// ``` - // async fn multi_insert_datasource<'a>(instances: &'a mut [&'a mut #ty], datasource_name: &'a str) -> ( - // Result<(), Box> - // ) { - // use canyon_sql::core::QueryParameter; - - // let mut final_values: Vec>> = Vec::new(); - // for instance in instances.iter() { - // let intermediate: &[&dyn QueryParameter<'_>] = &[#(#macro_fields_cloned),*]; - - // let mut longer_lived: Vec<&dyn QueryParameter<'_>> = Vec::new(); - // for value in intermediate.into_iter() { - // longer_lived.push(*value) - // } - - // final_values.push(longer_lived) - // } - - // let mut mapped_fields: String = String::new(); - // #multi_insert_transaction - // } } } +// +// /// Generates the TokenStream for the __insert() CRUD operation, but being available +// /// as a [`QueryBuilder`] object, and instead of being a method over some [`T`] type, +// /// as an associated function for [`T`] +// /// +// /// This, also lets the user to have the option to be able to insert multiple +// /// [`T`] objects in only one query +// pub fn generate_multiple_insert_tokens( +// macro_data: &MacroTokens, +// table_schema_data: &String, +// ) -> TokenStream { +// let ty = macro_data.ty; +// +// // Retrieves the fields of the Struct as continuous String +// let column_names = macro_data.get_struct_fields_as_strings(); +// +// // Retrieves the fields of the Struct +// let fields = macro_data.get_struct_fields(); +// +// let macro_fields = fields.iter().map(|field| quote! { &instance.#field }); +// let macro_fields_cloned = macro_fields.clone(); +// +// let pk = macro_data.get_primary_key_annotation().unwrap_or_default(); +// +// let pk_ident_type = macro_data +// ._fields_with_types() +// .into_iter() +// .find(|(i, _t)| *i == pk); +// +// let multi_insert_transaction = if let Some(pk_data) = &pk_ident_type { +// let pk_ident = &pk_data.0; +// let pk_type = &pk_data.1; +// +// quote! { +// mapped_fields = #column_names +// .split(", ") +// .map( |column_name| format!("\"{}\"", column_name)) +// .collect::>() +// .join(", "); +// +// let mut split = mapped_fields.split(", ") +// .collect::>(); +// +// let pk_value_index = split.iter() +// .position(|pk| *pk == format!("\"{}\"", #pk).as_str()) +// .expect("Error. No primary key found when should be there"); +// split.retain(|pk| *pk != format!("\"{}\"", #pk).as_str()); +// mapped_fields = split.join(", ").to_string(); +// +// let mut fields_placeholders = String::new(); +// +// let mut elements_counter = 0; +// let mut values_counter = 1; +// let values_arr_len = final_values.len(); +// +// for vector in final_values.iter_mut() { +// let mut inner_counter = 0; +// fields_placeholders.push('('); +// vector.remove(pk_value_index); +// +// for _value in vector.iter() { +// if inner_counter < vector.len() - 1 { +// fields_placeholders.push_str(&("$".to_owned() + &values_counter.to_string() + ",")); +// } else { +// fields_placeholders.push_str(&("$".to_owned() + &values_counter.to_string())); +// } +// +// inner_counter += 1; +// values_counter += 1; +// } +// +// elements_counter += 1; +// +// if elements_counter < values_arr_len { +// fields_placeholders.push_str("), "); +// } else { +// fields_placeholders.push(')'); +// } +// } +// +// let stmt = format!( +// "INSERT INTO {} ({}) VALUES {} RETURNING {}", +// #table_schema_data, +// mapped_fields, +// fields_placeholders, +// #pk +// ); +// +// let mut v_arr = Vec::new(); +// for arr in final_values.iter() { +// for value in arr { +// v_arr.push(*value) +// } +// } +// +// let multi_insert_result = <#ty as canyon_sql::core::Transaction<#ty>>::query( +// stmt, +// v_arr, +// datasource_name +// ).await?; +// +// match multi_insert_result { +// #[cfg(feature="postgres")] +// canyon_sql::core::CanyonRows::Postgres(mut v) => { +// for (idx, instance) in instances.iter_mut().enumerate() { +// instance.#pk_ident = v +// .get(idx) +// .expect("Failed getting the returned IDs for a multi insert") +// .get::<&str, #pk_type>(#pk); +// } +// +// Ok(()) +// }, +// #[cfg(feature="mssql")] +// canyon_sql::core::CanyonRows::Tiberius(mut v) => { +// for (idx, instance) in instances.iter_mut().enumerate() { +// instance.#pk_ident = v +// .get(idx) +// .expect("Failed getting the returned IDs for a multi insert") +// .get::<#pk_type, &str>(#pk) +// .expect("SQL Server primary key type failed to be set as value"); +// } +// +// Ok(()) +// }, +// #[cfg(feature="mysql")] +// canyon_sql::core::CanyonRows::MySQL(mut v) => { +// for (idx, instance) in instances.iter_mut().enumerate() { +// instance.#pk_ident = v +// .get(idx) +// .expect("Failed getting the returned IDs for a multi insert") +// .get::<#pk_type,usize>(0) +// .expect("MYSQL primary key type failed to be set as value"); +// } +// Ok(()) +// }, +// _ => panic!() // TODO remove when the generics will be refactored +// } +// } +// } else { +// quote! { +// mapped_fields = #column_names +// .split(", ") +// .map( |column_name| format!("\"{}\"", column_name)) +// .collect::>() +// .join(", "); +// +// let mut split = mapped_fields.split(", ") +// .collect::>(); +// +// let mut fields_placeholders = String::new(); +// +// let mut elements_counter = 0; +// let mut values_counter = 1; +// let values_arr_len = final_values.len(); +// +// for vector in final_values.iter_mut() { +// let mut inner_counter = 0; +// fields_placeholders.push('('); +// +// for _value in vector.iter() { +// if inner_counter < vector.len() - 1 { +// fields_placeholders.push_str(&("$".to_owned() + &values_counter.to_string() + ",")); +// } else { +// fields_placeholders.push_str(&("$".to_owned() + &values_counter.to_string())); +// } +// +// inner_counter += 1; +// values_counter += 1; +// } +// +// elements_counter += 1; +// +// if elements_counter < values_arr_len { +// fields_placeholders.push_str("), "); +// } else { +// fields_placeholders.push(')'); +// } +// } +// +// let stmt = format!( +// "INSERT INTO {} ({}) VALUES {}", +// #table_schema_data, +// mapped_fields, +// fields_placeholders +// ); +// +// let mut v_arr = Vec::new(); +// for arr in final_values.iter() { +// for value in arr { +// v_arr.push(*value) +// } +// } +// +// <#ty as canyon_sql::core::Transaction<#ty>>::query( +// stmt, +// v_arr, +// datasource_name +// ).await?; +// +// Ok(()) +// } +// }; +// +// quote! { +// ///// Inserts multiple instances of some type `T` into its related table. +// ///// +// ///// ``` +// ///// let mut new_league = League { +// ///// id: Default::default(), +// ///// ext_id: 392489032, +// ///// slug: "League10".to_owned(), +// ///// name: "League10also".to_owned(), +// ///// region: "Turkey".to_owned(), +// ///// image_url: "https://www.sdklafjsd.com".to_owned() +// ///// }; +// ///// let mut new_league2 = League { +// ///// id: Default::default(), +// ///// ext_id: 392489032, +// ///// slug: "League11".to_owned(), +// ///// name: "League11also".to_owned(), +// ///// region: "LDASKJF".to_owned(), +// ///// image_url: "https://www.sdklafjsd.com".to_owned() +// ///// }; +// ///// let mut new_league3 = League { +// ///// id: Default::default(), +// ///// ext_id: 9687392489032, +// ///// slug: "League3".to_owned(), +// ///// name: "3League".to_owned(), +// ///// region: "EU".to_owned(), +// ///// image_url: "https://www.lag.com".to_owned() +// ///// }; +// ///// +// ///// League::insert_multiple( +// ///// &mut [&mut new_league, &mut new_league2, &mut new_league3] +// ///// ).await +// ///// .ok(); +// ///// ``` +// //// async fn multi_insert<'a>(instances: &'a mut [&'a mut #ty]) -> ( +// //// Result<(), Box> +// //// ) { +// //// use canyon_sql::core::QueryParameter; +// //// let datasource_name = ""; +// +// //// let mut final_values: Vec>> = Vec::new(); +// //// for instance in instances.iter() { +// //// let intermediate: &[&dyn QueryParameter<'_>] = &[#(#macro_fields),*]; +// +// //// let mut longer_lived: Vec<&dyn QueryParameter<'_>> = Vec::new(); +// //// for value in intermediate.into_iter() { +// //// longer_lived.push(*value) +// //// } +// +// //// final_values.push(longer_lived) +// //// } +// +// //// let mut mapped_fields: String = String::new(); +// +// //// #multi_insert_transaction +// //// } +// +// ///// Inserts multiple instances of some type `T` into its related table with the specified +// ///// datasource by it's `datasouce name`, defined in the configuration file. +// ///// +// ///// ``` +// ///// let mut new_league = League { +// ///// id: Default::default(), +// ///// ext_id: 392489032, +// ///// slug: "League10".to_owned(), +// ///// name: "League10also".to_owned(), +// ///// region: "Turkey".to_owned(), +// ///// image_url: "https://www.sdklafjsd.com".to_owned() +// ///// }; +// ///// let mut new_league2 = League { +// ///// id: Default::default(), +// ///// ext_id: 392489032, +// ///// slug: "League11".to_owned(), +// ///// name: "League11also".to_owned(), +// ///// region: "LDASKJF".to_owned(), +// ///// image_url: "https://www.sdklafjsd.com".to_owned() +// ///// }; +// ///// let mut new_league3 = League { +// ///// id: Default::default(), +// ///// ext_id: 9687392489032, +// ///// slug: "League3".to_owned(), +// ///// name: "3League".to_owned(), +// ///// region: "EU".to_owned(), +// ///// image_url: "https://www.lag.com".to_owned() +// ///// }; +// ///// +// ///// League::insert_multiple( +// ///// &mut [&mut new_league, &mut new_league2, &mut new_league3] +// ///// ).await +// ///// .ok(); +// ///// ``` +// // async fn multi_insert_datasource<'a>(instances: &'a mut [&'a mut #ty], datasource_name: &'a str) -> ( +// // Result<(), Box> +// // ) { +// // use canyon_sql::core::QueryParameter; +// +// // let mut final_values: Vec>> = Vec::new(); +// // for instance in instances.iter() { +// // let intermediate: &[&dyn QueryParameter<'_>] = &[#(#macro_fields_cloned),*]; +// +// // let mut longer_lived: Vec<&dyn QueryParameter<'_>> = Vec::new(); +// // for value in intermediate.into_iter() { +// // longer_lived.push(*value) +// // } +// +// // final_values.push(longer_lived) +// // } +// +// // let mut mapped_fields: String = String::new(); +// +// // #multi_insert_transaction +// // } +// } +// } diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index c9071ee2..662ed04f 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -6,9 +6,11 @@ pub struct MacroOperationBuilder { fn_name: Option, user_type: Option, lifetime: bool, // bool true always will generate <'a> + self_as_ref: bool, input_param: Option, input_fwd_arg: Option, return_type: Option, + return_type_ts: Option, where_clause_bounds: Vec, doc_comments: Vec, body_tokens: Option, @@ -17,7 +19,9 @@ pub struct MacroOperationBuilder { forwarded_parameters: Option, single_result: bool, with_unwrap: bool, + with_no_result_value: bool, // Ok(()) transaction_as_variable: bool, + direct_error_return: Option, disable_mapping: bool, raw_return: bool, propagate_transaction_result: bool, @@ -36,9 +40,11 @@ impl MacroOperationBuilder { fn_name: None, user_type: None, lifetime: false, + self_as_ref: false, input_param: None, input_fwd_arg: None, return_type: None, + return_type_ts: None, where_clause_bounds: Vec::new(), doc_comments: Vec::new(), body_tokens: None, @@ -47,7 +53,9 @@ impl MacroOperationBuilder { forwarded_parameters: None, single_result: false, with_unwrap: false, + with_no_result_value: false, transaction_as_variable: false, + direct_error_return: None, disable_mapping: false, raw_return: false, propagate_transaction_result: false, @@ -91,6 +99,15 @@ impl MacroOperationBuilder { } } + fn compose_self_params_separator(&self) -> TokenStream { + // TODO: missing combinations + if self.self_as_ref && self.input_param.is_some() { + quote! {, } + } else { + quote! {} + } + } + fn compose_params_separator(&self) -> TokenStream { if self.input_parameters.is_some() && self.input_param.is_some() { quote! {, } @@ -99,6 +116,13 @@ impl MacroOperationBuilder { } } + fn get_as_method(&self) -> TokenStream { + if self.self_as_ref { + let self_ident = Ident::new("self", Span::call_site()); + quote! { &#self_ident, } + } else { quote!{} } + } + fn get_input_param(&self) -> TokenStream { let input_param = &self.input_param; quote! { #input_param } @@ -113,6 +137,11 @@ impl MacroOperationBuilder { } } + pub fn with_self_as_ref(mut self) -> Self { + self.self_as_ref = true; + self + } + pub fn with_lifetime(mut self) -> Self { self.lifetime = true; self @@ -129,7 +158,14 @@ impl MacroOperationBuilder { } fn get_return_type(&self) -> TokenStream { - let organic_ret_type = &self.return_type; + let organic_ret_type = if let Some(return_ty_ts) = &self.return_type_ts { + let rt_ts = return_ty_ts; + quote! { #rt_ts } + } else { + let rt = &self.return_type; + quote!{ #rt } + }; + let container_ret_type = if self.single_result { quote! { Option } } else { @@ -173,6 +209,11 @@ impl MacroOperationBuilder { self } + pub fn return_type_ts(mut self, return_type: &TokenStream) -> Self { + self.return_type_ts = Some(return_type.clone()); + self + } + pub fn single_result(mut self) -> Self { self.single_result = true; self @@ -226,6 +267,16 @@ impl MacroOperationBuilder { self } + pub fn with_no_result_value(mut self) -> Self { + self.with_no_result_value = true; + self + } + + pub fn with_direct_error_return>(mut self, err: E) -> Self { + self.direct_error_return = Some(err.as_ref().to_string()); + self + } + pub fn transaction_as_variable(mut self, result_handling: TokenStream) -> Self { self.transaction_as_variable = true; self.post_body = Some(result_handling); @@ -259,6 +310,7 @@ impl MacroOperationBuilder { let fn_name = self.get_fn_name(); let generics = self.compose_fn_signature_generics(); + let as_method = self.get_as_method(); let input_param = self.get_input_param(); let input_fwd_arg = self.get_input_arg(); // TODO: replace let fn_parameters = self.get_fn_parameters(); @@ -283,8 +335,22 @@ impl MacroOperationBuilder { if !self.disable_mapping { base_body_tokens.extend(quote! { .into_results::<#ty>() }) }; - - let body_tokens = if self.transaction_as_variable { + if self.with_no_result_value { // TODO: should we validate some combiantions? in the future, some of them can be hard to reason about + // like transaction_as_variable and with_no_result_value, they can't coexist + base_body_tokens.extend(quote! {; Ok(()) }) + } + + let body_tokens = if let Some(direct_err_return) = &self.direct_error_return { + let err = &self.direct_error_return; + quote! { + Err( + std::io::Error::new( + std::io::ErrorKind::Unsupported, + #err + ).into_inner().unwrap() + ) // TODO: waiting for creating our custom error types + } + } else if self.transaction_as_variable { let result_handling = &self.post_body; quote! { let transaction_result = #base_body_tokens; @@ -295,10 +361,16 @@ impl MacroOperationBuilder { }; let separate_params = self.compose_params_separator(); + let separate_self_params = self.compose_self_params_separator(); quote! { #(#doc_comments)* - async fn #fn_name #generics(#fn_parameters #separate_params #input_param) -> #return_type + async fn #fn_name #generics( + #as_method + #fn_parameters + #separate_params + #input_param + ) -> #return_type #where_clause { #body_tokens diff --git a/tests/crud/delete_operations.rs b/tests/crud/delete_operations.rs index 8f1c7eec..f3953732 100644 --- a/tests/crud/delete_operations.rs +++ b/tests/crud/delete_operations.rs @@ -1,68 +1,68 @@ -// //! Integration tests for the CRUD operations available in `Canyon` that -// //! generates and executes *INSERT* statements -// use canyon_sql::crud::CrudOperations; +//! Integration tests for the CRUD operations available in `Canyon` that +//! generates and executes *INSERT* statements +use canyon_sql::crud::CrudOperations; -// #[cfg(feature = "mysql")] -// use crate::constants::MYSQL_DS; -// #[cfg(feature = "postgres")] -// use crate::constants::PSQL_DS; -// #[cfg(feature = "mssql")] -// use crate::constants::SQL_SERVER_DS; +#[cfg(feature = "mysql")] +use crate::constants::MYSQL_DS; +#[cfg(feature = "postgres")] +use crate::constants::PSQL_DS; +#[cfg(feature = "mssql")] +use crate::constants::SQL_SERVER_DS; -// use crate::tests_models::league::*; +use crate::tests_models::league::*; -// /// Deletes a row from the database that is mapped into some instance of a `T` entity. -// /// -// /// The `t.delete(&self)` operation is only enabled for types that -// /// has, at least, one of it's fields annotated with a `#[primary_key]` -// /// operation, because we use that concrete field to construct the clause that targets -// /// that entity. -// /// -// /// Attempt of usage the `t.delete(&self)` method on an entity without `#[primary_key]` -// /// will raise a runtime error. -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_method_operation() { -// // For test the delete, we will insert a new instance of the database, and then, -// // after inspect it, we will proceed to delete it -// let mut new_league: League = League { -// id: Default::default(), -// ext_id: 7892635306594_i64, -// slug: "some-new-league".to_string(), -// name: "Some New League".to_string(), -// region: "Bahía de cochinos".to_string(), -// image_url: "https://nobodyspectsandimage.io".to_string(), -// }; +/// Deletes a row from the database that is mapped into some instance of a `T` entity. +/// +/// The `t.delete(&self)` operation is only enabled for types that +/// has, at least, one of it's fields annotated with a `#[primary_key]` +/// operation, because we use that concrete field to construct the clause that targets +/// that entity. +/// +/// Attempt of usage the `t.delete(&self)` method on an entity without `#[primary_key]` +/// will raise a runtime error. +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_delete_method_operation() { + // For test the delete operation, we will insert a new instance of the database, and then, + // after inspect it, we will proceed to delete it + let mut new_league: League = League { + id: Default::default(), + ext_id: 7892635306594_i64, + slug: "some-new-league".to_string(), + name: "Some New League".to_string(), + region: "Bahía de cochinos".to_string(), + image_url: "https://nobodyspectsandimage.io".to_string(), + }; -// // We insert the instance on the database, on the `League` entity -// new_league.insert().await.expect("Failed insert operation"); + // We insert the instance on the database, on the `League` entity + new_league.insert().await.expect("Failed insert operation"); -// assert_eq!( -// new_league.id, -// League::find_by_pk_with(&new_league.id, PSQL_DS) -// .await -// .expect("Request error") -// .expect("None value") -// .id -// ); + assert_eq!( + new_league.id, + League::find_by_pk_with(&new_league.id, PSQL_DS) + .await + .expect("Request error") + .expect("None value") + .id + ); -// // Now that we have an instance mapped to some entity by a primary key, we can now -// // remove that entry from the database with the delete operation -// new_league -// .delete() -// .await -// .expect("Failed to delete the operation"); + // Now that we have an instance mapped to some entity by a primary key, we can now + // remove that entry from the database with the delete operation + new_league + .delete() + .await + .expect("Failed to delete the operation"); -// // To check the success, we can query by the primary key value and check if, after unwrap() -// // the result of the operation, the find by primary key contains Some(v) or None -// // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> -// assert_eq!( -// League::find_by_pk(&new_league.id) -// .await -// .expect("Unwrapping the result, letting the Option"), -// None -// ); -// } + // To check the success, we can query by the primary key value and check if, after unwrap() + // the result of the operation, the find by primary key contains Some(v) or None + // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> + assert_eq!( + League::find_by_pk(&new_league.id) + .await + .expect("Unwrapping the result, letting the Option"), + None + ); +} // /// Same as the delete test, but performing the operations with the specified datasource // #[cfg(feature = "mssql")] diff --git a/tests/crud/insert_operations.rs b/tests/crud/insert_operations.rs index 00758f0c..4fb742ca 100644 --- a/tests/crud/insert_operations.rs +++ b/tests/crud/insert_operations.rs @@ -1,317 +1,317 @@ -// //! Integration tests for the CRUD operations available in `Canyon` that -// //! generates and executes *INSERT* statements -// use canyon_sql::crud::CrudOperations; +//! Integration tests for the CRUD operations available in `Canyon` that +//! generates and executes *INSERT* statements +use canyon_sql::crud::CrudOperations; -// #[cfg(feature = "mysql")] -// use crate::constants::MYSQL_DS; -// #[cfg(feature = "mssql")] -// use crate::constants::SQL_SERVER_DS; +#[cfg(feature = "mysql")] +use crate::constants::MYSQL_DS; +#[cfg(feature = "mssql")] +use crate::constants::SQL_SERVER_DS; -// use crate::tests_models::league::*; +use crate::tests_models::league::*; -// /// Inserts a new record on the database, given an entity that is -// /// annotated with `#[canyon_entity]` macro over a *T* type. -// /// -// /// For insert a new record on a database, the *insert* operation needs -// /// some special requirements: -// /// > - We need a mutable instance of `T`. If the operation completes -// /// successfully, the insert operation will automatically set the autogenerated -// /// value for the `primary_key` annotated field in it. -// /// -// /// > - It's considered a good practice to initialize that concrete field with -// /// the `Default` trait, because the value on the primary key field will be -// /// ignored at the execution time of the insert, and updated with the autogenerated -// /// value by the database. -// /// -// /// By default, the `#[primary_key]` annotation means autogenerated and autoincremental. -// /// You can configure not autoincremental via macro annotation parameters (please, -// /// refer to the docs [here]() for more info.) -// /// -// /// If the type hasn't a `#[primary_key]` annotation, or the annotation contains -// /// an argument specifying not autoincremental behaviour, all the fields will be -// /// inserted on the database and no returning value will be placed in any field. -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_insert_operation() { -// let mut new_league: League = League { -// id: Default::default(), -// ext_id: 7892635306594_i64, -// slug: "some-new-league".to_string(), -// name: "Some New League".to_string(), -// region: "Bahía de cochinos".to_string(), -// image_url: "https://nobodyspectsandimage.io".to_string(), -// }; +/// Inserts a new record on the database, given an entity that is +/// annotated with `#[canyon_entity]` macro over a *T* type. +/// +/// For insert a new record on a database, the *insert* operation needs +/// some special requirements: +/// > - We need a mutable instance of `T`. If the operation completes +/// successfully, the insert operation will automatically set the autogenerated +/// value for the `primary_key` annotated field in it. +/// +/// > - It's considered a good practice to initialize that concrete field with +/// the `Default` trait, because the value on the primary key field will be +/// ignored at the execution time of the insert, and updated with the autogenerated +/// value by the database. +/// +/// By default, the `#[primary_key]` annotation means autogenerated and autoincremental. +/// You can configure not autoincremental via macro annotation parameters (please, +/// refer to the docs [here]() for more info.) +/// +/// If the type hasn't a `#[primary_key]` annotation, or the annotation contains +/// an argument specifying not autoincremental behaviour, all the fields will be +/// inserted on the database and no returning value will be placed in any field. +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_insert_operation() { + let mut new_league: League = League { + id: Default::default(), + ext_id: 7892635306594_i64, + slug: "some-new-league".to_string(), + name: "Some New League".to_string(), + region: "Bahía de cochinos".to_string(), + image_url: "https://nobodyspectsandimage.io".to_string(), + }; -// // We insert the instance on the database, on the `League` entity -// new_league.insert().await.expect("Failed insert operation"); + // We insert the instance on the database, on the `League` entity + new_league.insert().await.expect("Failed insert operation"); -// // Now, in the `id` field of the instance, we have the autogenerated -// // value for the primary key field, which is id. So, we can query the -// // database again with the find by primary key operation to check if -// // the value was really inserted -// let inserted_league = League::find_by_pk(&new_league.id) -// .await -// .expect("Failed the query to the database") -// .expect("No entity found for the primary key value passed in"); + // Now, in the `id` field of the instance, we have the autogenerated + // value for the primary key field, which is id. So, we can query the + // database again with the find by primary key operation to check if + // the value was really inserted + let inserted_league = League::find_by_pk(&new_league.id) + .await + .expect("Failed the query to the database") + .expect("No entity found for the primary key value passed in"); -// assert_eq!(new_league.id, inserted_league.id); -// } + assert_eq!(new_league.id, inserted_league.id); +} -// /// Same as the insert operation above, but targeting the database defined in -// /// the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_insert_with_mssql_operation() { -// let mut new_league: League = League { -// id: Default::default(), -// ext_id: 7892635306594_i64, -// slug: "some-new-league".to_string(), -// name: "Some New League".to_string(), -// region: "Bahía de cochinos".to_string(), -// image_url: "https://nobodyspectsandimage.io".to_string(), -// }; +/// Same as the insert operation above, but targeting the database defined in +/// the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_insert_with_mssql_operation() { + let mut new_league: League = League { + id: Default::default(), + ext_id: 7892635306594_i64, + slug: "some-new-league".to_string(), + name: "Some New League".to_string(), + region: "Bahía de cochinos".to_string(), + image_url: "https://nobodyspectsandimage.io".to_string(), + }; -// // We insert the instance on the database, on the `League` entity -// new_league -// .insert_with(SQL_SERVER_DS) -// .await -// .expect("Failed insert datasource operation"); + // We insert the instance on the database, on the `League` entity + new_league + .insert_with(SQL_SERVER_DS) + .await + .expect("Failed insert datasource operation"); -// // Now, in the `id` field of the instance, we have the autogenerated -// // value for the primary key field, which is id. So, we can query the -// // database again with the find by primary key operation to check if -// // the value was really inserted -// let inserted_league = League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) -// .await -// .expect("Failed the query to the database") -// .expect("No entity found for the primary key value passed in"); + // Now, in the `id` field of the instance, we have the autogenerated + // value for the primary key field, which is id. So, we can query the + // database again with the find by primary key operation to check if + // the value was really inserted + let inserted_league = League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) + .await + .expect("Failed the query to the database") + .expect("No entity found for the primary key value passed in"); -// assert_eq!(new_league.id, inserted_league.id); -// } + assert_eq!(new_league.id, inserted_league.id); +} -// /// Same as the insert operation above, but targeting the database defined in -// /// the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_insert_with_mysql_operation() { -// let mut new_league: League = League { -// id: Default::default(), -// ext_id: 7892635306594_i64, -// slug: "some-new-league".to_string(), -// name: "Some New League".to_string(), -// region: "Bahía de cochinos".to_string(), -// image_url: "https://nobodyspectsandimage.io".to_string(), -// }; +/// Same as the insert operation above, but targeting the database defined in +/// the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_insert_with_mysql_operation() { + let mut new_league: League = League { + id: Default::default(), + ext_id: 7892635306594_i64, + slug: "some-new-league".to_string(), + name: "Some New League".to_string(), + region: "Bahía de cochinos".to_string(), + image_url: "https://nobodyspectsandimage.io".to_string(), + }; -// // We insert the instance on the database, on the `League` entity -// new_league -// .insert_with(MYSQL_DS) -// .await -// .expect("Failed insert datasource operation"); + // We insert the instance on the database, on the `League` entity + new_league + .insert_with(MYSQL_DS) + .await + .expect("Failed insert datasource operation"); -// // Now, in the `id` field of the instance, we have the autogenerated -// // value for the primary key field, which is id. So, we can query the -// // database again with the find by primary key operation to check if -// // the value was really inserted -// let inserted_league = League::find_by_pk_with(&new_league.id, MYSQL_DS) -// .await -// .expect("Failed the query to the database") -// .expect("No entity found for the primary key value passed in"); + // Now, in the `id` field of the instance, we have the autogenerated + // value for the primary key field, which is id. So, we can query the + // database again with the find by primary key operation to check if + // the value was really inserted + let inserted_league = League::find_by_pk_with(&new_league.id, MYSQL_DS) + .await + .expect("Failed the query to the database") + .expect("No entity found for the primary key value passed in"); -// assert_eq!(new_league.id, inserted_league.id); -// } + assert_eq!(new_league.id, inserted_league.id); +} -// /// The multi insert operation is a shorthand for insert multiple instances of *T* -// /// in the database at once. -// /// -// /// It works pretty much the same that the insert operation, with the same behaviour -// /// of the `#[primary_key]` annotation over some field. It will auto set the primary -// /// key field with the autogenerated value on the database on the insert operation, but -// /// for every entity passed in as an array of mutable instances of `T`. -// /// -// /// The instances without `#[primary_key]` inserts all the values on the instaqce fields -// /// on the database. -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_multi_insert_operation() { -// let mut new_league_mi: League = League { -// id: Default::default(), -// ext_id: 54376478_i64, -// slug: "some-new-random-league".to_string(), -// name: "Some New Random League".to_string(), -// region: "Unknown".to_string(), -// image_url: "https://what-a-league.io".to_string(), -// }; -// let mut new_league_mi_2: League = League { -// id: Default::default(), -// ext_id: 3475689769678906_i64, -// slug: "new-league-2".to_string(), -// name: "New League 2".to_string(), -// region: "Really unknown".to_string(), -// image_url: "https://what-an-unknown-league.io".to_string(), -// }; -// let mut new_league_mi_3: League = League { -// id: Default::default(), -// ext_id: 46756867_i64, -// slug: "a-new-multinsert".to_string(), -// name: "New League 3".to_string(), -// region: "The dark side of the moon".to_string(), -// image_url: "https://interplanetary-league.io".to_string(), -// }; +/// The multi insert operation is a shorthand for insert multiple instances of *T* +/// in the database at once. +/// +/// It works pretty much the same that the insert operation, with the same behaviour +/// of the `#[primary_key]` annotation over some field. It will auto set the primary +/// key field with the autogenerated value on the database on the insert operation, but +/// for every entity passed in as an array of mutable instances of `T`. +/// +/// The instances without `#[primary_key]` inserts all the values on the instaqce fields +/// on the database. +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_multi_insert_operation() { + let mut new_league_mi: League = League { + id: Default::default(), + ext_id: 54376478_i64, + slug: "some-new-random-league".to_string(), + name: "Some New Random League".to_string(), + region: "Unknown".to_string(), + image_url: "https://what-a-league.io".to_string(), + }; + let mut new_league_mi_2: League = League { + id: Default::default(), + ext_id: 3475689769678906_i64, + slug: "new-league-2".to_string(), + name: "New League 2".to_string(), + region: "Really unknown".to_string(), + image_url: "https://what-an-unknown-league.io".to_string(), + }; + let mut new_league_mi_3: League = League { + id: Default::default(), + ext_id: 46756867_i64, + slug: "a-new-multinsert".to_string(), + name: "New League 3".to_string(), + region: "The dark side of the moon".to_string(), + image_url: "https://interplanetary-league.io".to_string(), + }; -// // Insert the instance as database entities -// new_league_mi -// .insert() -// .await -// .expect("Failed insert datasource operation"); -// new_league_mi_2 -// .insert() -// .await -// .expect("Failed insert datasource operation"); -// new_league_mi_3 -// .insert() -// .await -// .expect("Failed insert datasource operation"); + // Insert the instance as database entities + new_league_mi + .insert() + .await + .expect("Failed insert datasource operation"); + new_league_mi_2 + .insert() + .await + .expect("Failed insert datasource operation"); + new_league_mi_3 + .insert() + .await + .expect("Failed insert datasource operation"); -// // Recover the inserted data by primary key -// let inserted_league = League::find_by_pk(&new_league_mi.id) -// .await -// .expect("[1] - Failed the query to the database") -// .expect("[1] - No entity found for the primary key value passed in"); -// let inserted_league_2 = League::find_by_pk(&new_league_mi_2.id) -// .await -// .expect("[2] - Failed the query to the database") -// .expect("[2] - No entity found for the primary key value passed in"); -// let inserted_league_3 = League::find_by_pk(&new_league_mi_3.id) -// .await -// .expect("[3] - Failed the query to the database") -// .expect("[3] - No entity found for the primary key value passed in"); + // Recover the inserted data by primary key + let inserted_league = League::find_by_pk(&new_league_mi.id) + .await + .expect("[1] - Failed the query to the database") + .expect("[1] - No entity found for the primary key value passed in"); + let inserted_league_2 = League::find_by_pk(&new_league_mi_2.id) + .await + .expect("[2] - Failed the query to the database") + .expect("[2] - No entity found for the primary key value passed in"); + let inserted_league_3 = League::find_by_pk(&new_league_mi_3.id) + .await + .expect("[3] - Failed the query to the database") + .expect("[3] - No entity found for the primary key value passed in"); -// assert_eq!(new_league_mi.id, inserted_league.id); -// assert_eq!(new_league_mi_2.id, inserted_league_2.id); -// assert_eq!(new_league_mi_3.id, inserted_league_3.id); -// } + assert_eq!(new_league_mi.id, inserted_league.id); + assert_eq!(new_league_mi_2.id, inserted_league_2.id); + assert_eq!(new_league_mi_3.id, inserted_league_3.id); +} -// /// Same as the multi insert above, but with the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_multi_insert_with_mssql_operation() { -// let mut new_league_mi: League = League { -// id: Default::default(), -// ext_id: 54376478_i64, -// slug: "some-new-random-league".to_string(), -// name: "Some New Random League".to_string(), -// region: "Unknown".to_string(), -// image_url: "https://what-a-league.io".to_string(), -// }; -// let mut new_league_mi_2: League = League { -// id: Default::default(), -// ext_id: 3475689769678906_i64, -// slug: "new-league-2".to_string(), -// name: "New League 2".to_string(), -// region: "Really unknown".to_string(), -// image_url: "https://what-an-unknown-league.io".to_string(), -// }; -// let mut new_league_mi_3: League = League { -// id: Default::default(), -// ext_id: 46756867_i64, -// slug: "a-new-multinsert".to_string(), -// name: "New League 3".to_string(), -// region: "The dark side of the moon".to_string(), -// image_url: "https://interplanetary-league.io".to_string(), -// }; +/// Same as the multi insert above, but with the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_multi_insert_with_mssql_operation() { + let mut new_league_mi: League = League { + id: Default::default(), + ext_id: 54376478_i64, + slug: "some-new-random-league".to_string(), + name: "Some New Random League".to_string(), + region: "Unknown".to_string(), + image_url: "https://what-a-league.io".to_string(), + }; + let mut new_league_mi_2: League = League { + id: Default::default(), + ext_id: 3475689769678906_i64, + slug: "new-league-2".to_string(), + name: "New League 2".to_string(), + region: "Really unknown".to_string(), + image_url: "https://what-an-unknown-league.io".to_string(), + }; + let mut new_league_mi_3: League = League { + id: Default::default(), + ext_id: 46756867_i64, + slug: "a-new-multinsert".to_string(), + name: "New League 3".to_string(), + region: "The dark side of the moon".to_string(), + image_url: "https://interplanetary-league.io".to_string(), + }; -// // Insert the instance as database entities -// new_league_mi -// .insert_with(SQL_SERVER_DS) -// .await -// .expect("Failed insert datasource operation"); -// new_league_mi_2 -// .insert_with(SQL_SERVER_DS) -// .await -// .expect("Failed insert datasource operation"); -// new_league_mi_3 -// .insert_with(SQL_SERVER_DS) -// .await -// .expect("Failed insert datasource operation"); + // Insert the instance as database entities + new_league_mi + .insert_with(SQL_SERVER_DS) + .await + .expect("Failed insert datasource operation"); + new_league_mi_2 + .insert_with(SQL_SERVER_DS) + .await + .expect("Failed insert datasource operation"); + new_league_mi_3 + .insert_with(SQL_SERVER_DS) + .await + .expect("Failed insert datasource operation"); -// // Recover the inserted data by primary key -// let inserted_league = League::find_by_pk_with(&new_league_mi.id, SQL_SERVER_DS) -// .await -// .expect("[1] - Failed the query to the database") -// .expect("[1] - No entity found for the primary key value passed in"); -// let inserted_league_2 = League::find_by_pk_with(&new_league_mi_2.id, SQL_SERVER_DS) -// .await -// .expect("[2] - Failed the query to the database") -// .expect("[2] - No entity found for the primary key value passed in"); -// let inserted_league_3 = League::find_by_pk_with(&new_league_mi_3.id, SQL_SERVER_DS) -// .await -// .expect("[3] - Failed the query to the database") -// .expect("[3] - No entity found for the primary key value passed in"); + // Recover the inserted data by primary key + let inserted_league = League::find_by_pk_with(&new_league_mi.id, SQL_SERVER_DS) + .await + .expect("[1] - Failed the query to the database") + .expect("[1] - No entity found for the primary key value passed in"); + let inserted_league_2 = League::find_by_pk_with(&new_league_mi_2.id, SQL_SERVER_DS) + .await + .expect("[2] - Failed the query to the database") + .expect("[2] - No entity found for the primary key value passed in"); + let inserted_league_3 = League::find_by_pk_with(&new_league_mi_3.id, SQL_SERVER_DS) + .await + .expect("[3] - Failed the query to the database") + .expect("[3] - No entity found for the primary key value passed in"); -// assert_eq!(new_league_mi.id, inserted_league.id); -// assert_eq!(new_league_mi_2.id, inserted_league_2.id); -// assert_eq!(new_league_mi_3.id, inserted_league_3.id); -// } + assert_eq!(new_league_mi.id, inserted_league.id); + assert_eq!(new_league_mi_2.id, inserted_league_2.id); + assert_eq!(new_league_mi_3.id, inserted_league_3.id); +} -// /// Same as the multi insert above, but with the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_multi_insert_with_mysql_operation() { -// let mut new_league_mi: League = League { -// id: Default::default(), -// ext_id: 54376478_i64, -// slug: "some-new-random-league".to_string(), -// name: "Some New Random League".to_string(), -// region: "Unknown".to_string(), -// image_url: "https://what-a-league.io".to_string(), -// }; -// let mut new_league_mi_2: League = League { -// id: Default::default(), -// ext_id: 3475689769678906_i64, -// slug: "new-league-2".to_string(), -// name: "New League 2".to_string(), -// region: "Really unknown".to_string(), -// image_url: "https://what-an-unknown-league.io".to_string(), -// }; -// let mut new_league_mi_3: League = League { -// id: Default::default(), -// ext_id: 46756867_i64, -// slug: "a-new-multinsert".to_string(), -// name: "New League 3".to_string(), -// region: "The dark side of the moon".to_string(), -// image_url: "https://interplanetary-league.io".to_string(), -// }; +/// Same as the multi insert above, but with the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_multi_insert_with_mysql_operation() { + let mut new_league_mi: League = League { + id: Default::default(), + ext_id: 54376478_i64, + slug: "some-new-random-league".to_string(), + name: "Some New Random League".to_string(), + region: "Unknown".to_string(), + image_url: "https://what-a-league.io".to_string(), + }; + let mut new_league_mi_2: League = League { + id: Default::default(), + ext_id: 3475689769678906_i64, + slug: "new-league-2".to_string(), + name: "New League 2".to_string(), + region: "Really unknown".to_string(), + image_url: "https://what-an-unknown-league.io".to_string(), + }; + let mut new_league_mi_3: League = League { + id: Default::default(), + ext_id: 46756867_i64, + slug: "a-new-multinsert".to_string(), + name: "New League 3".to_string(), + region: "The dark side of the moon".to_string(), + image_url: "https://interplanetary-league.io".to_string(), + }; -// // Insert the instance as database entities -// new_league_mi -// .insert_with(MYSQL_DS) -// .await -// .expect("Failed insert datasource operation"); -// new_league_mi_2 -// .insert_with(MYSQL_DS) -// .await -// .expect("Failed insert datasource operation"); -// new_league_mi_3 -// .insert_with(MYSQL_DS) -// .await -// .expect("Failed insert datasource operation"); + // Insert the instance as database entities + new_league_mi + .insert_with(MYSQL_DS) + .await + .expect("Failed insert datasource operation"); + new_league_mi_2 + .insert_with(MYSQL_DS) + .await + .expect("Failed insert datasource operation"); + new_league_mi_3 + .insert_with(MYSQL_DS) + .await + .expect("Failed insert datasource operation"); -// // Recover the inserted data by primary key -// let inserted_league = League::find_by_pk_with(&new_league_mi.id, MYSQL_DS) -// .await -// .expect("[1] - Failed the query to the database") -// .expect("[1] - No entity found for the primary key value passed in"); -// let inserted_league_2 = League::find_by_pk_with(&new_league_mi_2.id, MYSQL_DS) -// .await -// .expect("[2] - Failed the query to the database") -// .expect("[2] - No entity found for the primary key value passed in"); -// let inserted_league_3 = League::find_by_pk_with(&new_league_mi_3.id, MYSQL_DS) -// .await -// .expect("[3] - Failed the query to the database") -// .expect("[3] - No entity found for the primary key value passed in"); + // Recover the inserted data by primary key + let inserted_league = League::find_by_pk_with(&new_league_mi.id, MYSQL_DS) + .await + .expect("[1] - Failed the query to the database") + .expect("[1] - No entity found for the primary key value passed in"); + let inserted_league_2 = League::find_by_pk_with(&new_league_mi_2.id, MYSQL_DS) + .await + .expect("[2] - Failed the query to the database") + .expect("[2] - No entity found for the primary key value passed in"); + let inserted_league_3 = League::find_by_pk_with(&new_league_mi_3.id, MYSQL_DS) + .await + .expect("[3] - Failed the query to the database") + .expect("[3] - No entity found for the primary key value passed in"); -// assert_eq!(new_league_mi.id, inserted_league.id); -// assert_eq!(new_league_mi_2.id, inserted_league_2.id); -// assert_eq!(new_league_mi_3.id, inserted_league_3.id); -// } + assert_eq!(new_league_mi.id, inserted_league.id); + assert_eq!(new_league_mi_2.id, inserted_league_2.id); + assert_eq!(new_league_mi_3.id, inserted_league_3.id); +} From 0edef47c2b1d67a23171da1fbcb8933d3ca4e0cd Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 27 Jan 2025 10:59:36 +0100 Subject: [PATCH 049/155] test: unit testing the generated tokens for the delete operations macros --- canyon_macros/src/query_operations/delete.rs | 67 +++++++++++++++++++ canyon_macros/src/query_operations/mod.rs | 1 + canyon_macros/src/query_operations/select.rs | 67 ++++++++++--------- .../src/query_operations/tests_consts.rs | 35 ++++++++++ 4 files changed, 138 insertions(+), 32 deletions(-) create mode 100644 canyon_macros/src/query_operations/tests_consts.rs diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index 9026caea..c51040da 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -145,3 +145,70 @@ mod __details { .with_direct_error_return(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) } } + +#[cfg(test)] +mod delete_tests { + use crate::query_operations::tests_consts::*; + use super::__details::*; + use proc_macro2::Span; + use quote::quote; + use syn::Ident; + + const DELETE_MOCK_STMT: &str = "DELETE FROM public.user WHERE user.id = 1"; + + #[test] + fn test_macro_builder_delete() { + let delete_builder = create_delete_macro( + &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + DELETE_MOCK_STMT, + &PK_MOCK_FIELD_VALUE.with(|pk_field_mock_value| pk_field_mock_value.borrow().clone()), + &VOID_RET_TY.with(|void_ret_ty| void_ret_ty.borrow().clone()) + ); + let delete = delete_builder.generate_tokens().to_string(); + + assert!(delete.contains("async fn delete")); + assert!(delete.contains(RES_VOID_RET_TY)); + } + + #[test] + fn test_macro_builder_delete_with() { + let delete_builder = create_delete_with_macro( + &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + DELETE_MOCK_STMT, + &PK_MOCK_FIELD_VALUE.with(|pk_field_mock_value| pk_field_mock_value.borrow().clone()), + &VOID_RET_TY.with(|void_ret_ty| void_ret_ty.borrow().clone()) + ); + let delete_with = delete_builder.generate_tokens().to_string(); + + assert!(delete_with.contains("async fn delete_with")); + assert!(delete_with.contains(RES_VOID_RET_TY_LT)); + assert!(delete_with.contains(LT_CONSTRAINT)); + assert!(delete_with.contains(INPUT_PARAM)); + } + + #[test] + fn test_macro_builder_delete_err() { + let delete_err_builder = create_delete_err_macro( + &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + &VOID_RET_TY.with(|void_ret_ty| void_ret_ty.borrow().clone()) + ); + let delete_err = delete_err_builder.generate_tokens().to_string(); + + assert!(delete_err.contains("async fn delete")); + assert!(delete_err.contains(RES_VOID_RET_TY)); + } + + #[test] + fn test_macro_builder_delete_err_with() { + let delete_err_with_builder = create_delete_err_with_macro( + &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + &VOID_RET_TY.with(|void_ret_ty| void_ret_ty.borrow().clone()) + ); + let delete_err_with = delete_err_with_builder.generate_tokens().to_string(); + + assert!(delete_err_with.contains("async fn delete_with")); + assert!(delete_err_with.contains(RES_VOID_RET_TY_LT)); + assert!(delete_err_with.contains(LT_CONSTRAINT)); + assert!(delete_err_with.contains(INPUT_PARAM)); + } +} \ No newline at end of file diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index 6030eb4f..a487171e 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -6,3 +6,4 @@ pub mod update; mod doc_comments; mod macro_template; +mod tests_consts; diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index 38b919a3..331b908d 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -338,6 +338,7 @@ mod __details { #[cfg(test)] mod macro_builder_read_ops_tests { use super::__details::{count_generators::*, find_all_generators::*, pk_generators::*}; + use crate::query_operations::tests_consts::*; use proc_macro2::Span; use quote::quote; use syn::Ident; @@ -346,25 +347,12 @@ mod macro_builder_read_ops_tests { const COUNT_STMT: &str = "SELECT COUNT(*) FROM public.user"; const FIND_BY_PK_STMT: &str = "SELECT * FROM public.user WHERE id = $1"; - const RAW_RET_TY: &str = "Vec < User >"; - const RES_RET_TY: &str = - "Result < Vec < User > , Box < (dyn std :: error :: Error + Send + Sync) > >"; - const RES_RET_TY_LT: &str = - "Result < Vec < User > , Box < (dyn std :: error :: Error + Send + Sync + 'a) > >"; - const OPT_RET_TY_LT: &str = - "Result < Option < User > , Box < (dyn std :: error :: Error + Send + Sync + 'a) > >"; - - const MAPS_TO: &str = "into_results :: < User > ()"; - const LT_CONSTRAINT: &str = "< 'a >"; - const INPUT_PARAM: &str = "input : & 'a I"; - - const WITH_WHERE_BOUNDS: &str = - "where I : Into < canyon_sql::core::TransactionInput < 'a >> + Sync + Send + 'a "; - #[test] fn test_macro_builder_find_all() { - let ty: Ident = Ident::new("User", Span::call_site()); - let find_all_builder = create_find_all_macro(&ty, SELECT_ALL_STMT); + let find_all_builder = create_find_all_macro( + &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + SELECT_ALL_STMT + ); let find_all = find_all_builder.generate_tokens().to_string(); assert!(find_all.contains("async fn find_all")); @@ -373,8 +361,10 @@ mod macro_builder_read_ops_tests { #[test] fn test_macro_builder_find_all_with() { - let ty: Ident = Ident::new("User", Span::call_site()); - let find_all_builder = create_find_all_with_macro(&ty, SELECT_ALL_STMT); + let find_all_builder = create_find_all_with_macro( + &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + SELECT_ALL_STMT + ); let find_all_with = find_all_builder.generate_tokens().to_string(); assert!(find_all_with.contains("async fn find_all_with")); @@ -385,8 +375,10 @@ mod macro_builder_read_ops_tests { #[test] fn test_macro_builder_find_all_unchecked() { - let ty: Ident = Ident::new("User", Span::call_site()); - let find_all_unc_builder = create_find_all_unchecked_macro(&ty, SELECT_ALL_STMT); + let find_all_unc_builder = create_find_all_unchecked_macro( + &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + SELECT_ALL_STMT + ); let find_all_unc = find_all_unc_builder.generate_tokens().to_string(); assert!(find_all_unc.contains("async fn find_all_unchecked")); @@ -395,8 +387,10 @@ mod macro_builder_read_ops_tests { #[test] fn test_macro_builder_find_all_unchecked_with() { - let ty: Ident = Ident::new("User", Span::call_site()); - let find_all_unc_with_builder = create_find_all_unchecked_with_macro(&ty, SELECT_ALL_STMT); + let find_all_unc_with_builder = create_find_all_unchecked_with_macro( + &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + SELECT_ALL_STMT + ); let find_all_unc_with = find_all_unc_with_builder.generate_tokens().to_string(); assert!(find_all_unc_with.contains("async fn find_all_unchecked_with")); @@ -407,8 +401,10 @@ mod macro_builder_read_ops_tests { #[test] fn test_macro_builder_count() { - let ty: Ident = Ident::new("User", Span::call_site()); - let count_builder = create_count_macro(&ty, COUNT_STMT); + let count_builder = create_count_macro( + &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + COUNT_STMT + ); let count = count_builder.generate_tokens().to_string(); assert!(count.contains("async fn count")); @@ -417,8 +413,10 @@ mod macro_builder_read_ops_tests { #[test] fn test_macro_builder_count_with() { - let ty: Ident = Ident::new("User", Span::call_site()); - let count_with_builder = create_count_with_macro(&ty, COUNT_STMT); + let count_with_builder = create_count_with_macro( + &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + COUNT_STMT + ); let count_with = count_with_builder.generate_tokens().to_string(); assert!(count_with.contains("async fn count_with")); @@ -429,8 +427,11 @@ mod macro_builder_read_ops_tests { #[test] fn test_macro_builder_find_by_pk() { - let ty: Ident = Ident::new("User", Span::call_site()); - let find_by_pk_builder = create_find_by_pk_macro(&ty, FIND_BY_PK_STMT, "e! {}); + let find_by_pk_builder = create_find_by_pk_macro( + &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + FIND_BY_PK_STMT, + "e! {} + ); let find_by_pk = find_by_pk_builder.generate_tokens().to_string(); assert!(find_by_pk.contains("async fn find_by_pk")); @@ -440,10 +441,12 @@ mod macro_builder_read_ops_tests { #[test] fn test_macro_builder_find_by_pk_with() { - let ty: Ident = Ident::new("User", Span::call_site()); - let find_by_pk_with_builder = create_find_by_pk_with(&ty, FIND_BY_PK_STMT, "e! {}); + let find_by_pk_with_builder = create_find_by_pk_with( + &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + FIND_BY_PK_STMT, + "e! {} + ); let find_by_pk_with = find_by_pk_with_builder.generate_tokens().to_string(); - println!("{:?}", find_by_pk_with.split("\n").collect::>()); assert!(find_by_pk_with.contains("async fn find_by_pk_with")); assert!(find_by_pk_with.contains(LT_CONSTRAINT)); diff --git a/canyon_macros/src/query_operations/tests_consts.rs b/canyon_macros/src/query_operations/tests_consts.rs new file mode 100644 index 00000000..83616a53 --- /dev/null +++ b/canyon_macros/src/query_operations/tests_consts.rs @@ -0,0 +1,35 @@ +use std::cell::RefCell; + +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{Ident, Type}; + +thread_local! { + pub static USER_MOCK_TY: RefCell = RefCell::new(Ident::new("User", Span::call_site())); + pub static VOID_RET_TY: RefCell = RefCell::new({ + let ret_ty: Type = syn::parse_str("()").expect("Failed to parse unit type"); + quote! { #ret_ty } + }); + pub static PK_MOCK_FIELD_VALUE: RefCell = RefCell::new({ + quote! { 1 } + }); +} + +pub const RAW_RET_TY: &str = "Vec < User >"; +pub const RES_RET_TY: &str = + "Result < Vec < User > , Box < (dyn std :: error :: Error + Send + Sync) > >"; +pub const RES_VOID_RET_TY: &str = + "Result < () , Box < (dyn std :: error :: Error + Send + Sync) > >"; +pub const RES_RET_TY_LT: &str = + "Result < Vec < User > , Box < (dyn std :: error :: Error + Send + Sync + 'a) > >"; +pub const RES_VOID_RET_TY_LT: &str = + "Result < () , Box < (dyn std :: error :: Error + Send + Sync + 'a) > >"; +pub const OPT_RET_TY_LT: &str = + "Result < Option < User > , Box < (dyn std :: error :: Error + Send + Sync + 'a) > >"; + +pub const MAPS_TO: &str = "into_results :: < User > ()"; +pub const LT_CONSTRAINT: &str = "< 'a "; +pub const INPUT_PARAM: &str = "input : I"; + +pub const WITH_WHERE_BOUNDS: &str = + "where I : canyon_sql :: core :: DbConnection + Send + 'a "; \ No newline at end of file From e33046223dd4c66daf13bb86ec40cd7ac36609db Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 27 Jan 2025 11:40:57 +0100 Subject: [PATCH 050/155] test: unit testing the generated tokens for the delete with the querybuilder operations macros --- canyon_crud/src/crud.rs | 6 +- canyon_macros/src/lib.rs | 8 +-- canyon_macros/src/query_operations/delete.rs | 74 +++++++++++--------- canyon_macros/src/query_operations/select.rs | 2 +- 4 files changed, 46 insertions(+), 44 deletions(-) diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 44e923ec..ae61b0d6 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -103,7 +103,9 @@ where where I: DbConnection + Send + 'a; - // fn delete_query<'a>() -> DeleteQueryBuilder<'a, T>; + fn delete_query<'a>() -> DeleteQueryBuilder<'a, T, &'a str>; - // fn delete_query_with(datasource_name: &str) -> DeleteQueryBuilder<'_, T>; + fn delete_query_with<'a, I>(input: I) -> DeleteQueryBuilder<'a, T, I> + where + I: DbConnection + Send + 'a; } diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index afe61f67..0f672955 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -30,7 +30,7 @@ use query_operations::{ // generate_multiple_insert_tokens }, update::{generate_update_query_tokens, generate_update_tokens}, - delete::{generate_delete_query_tokens, generate_delete_tokens}, + delete::generate_delete_tokens, }; use utils::{function_parser::FunctionParser, helpers, macro_tokens::MacroTokens}; @@ -267,9 +267,6 @@ fn impl_crud_operations_trait_for_struct( // Builds the delete() queries let delete_tokens = generate_delete_tokens(macro_data, &table_schema_data); - // Builds the delete() query as a QueryBuilder - let _delete_query_tokens = generate_delete_query_tokens(macro_data, &table_schema_data); - // // Search by foreign (d) key as Vec, cause Canyon supports multiple fields having FK annotation // let _search_by_fk_tokens: Vec<(TokenStream, TokenStream)> = // generate_find_by_foreign_key_tokens(macro_data); @@ -314,9 +311,6 @@ fn impl_crud_operations_trait_for_struct( // // The delete impl #delete_tokens - - // // The delete as querybuilder impl - // #_delete_query_tokens }; // let tokens = if !_search_by_fk_tokens.is_empty() { diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index c51040da..f7b49c03 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -1,4 +1,4 @@ -use proc_macro2::TokenStream; +use proc_macro2::{Ident, TokenStream}; use quote::quote; use syn::Type; use crate::query_operations::delete::__details::{create_delete_err_macro, create_delete_err_with_macro, create_delete_macro, create_delete_with_macro}; @@ -7,6 +7,8 @@ use crate::utils::macro_tokens::MacroTokens; /// Generates the TokenStream for the __delete() CRUD operation /// returning a result, indicating a possible failure querying the database pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &String) -> TokenStream { + let mut delete_ops_tokens = TokenStream::new(); + let ty = macro_data.ty; let fields = macro_data.get_struct_fields(); @@ -30,55 +32,59 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let delete_tokens = create_delete_macro(ty, &stmt, &pk_field_value, &q_ret_ty); let delete_with_tokens = create_delete_with_macro(ty, &stmt, &pk_field_value, &q_ret_ty); - quote! { + delete_ops_tokens.extend(quote! { #delete_tokens #delete_with_tokens - } + }); } else { let delete_err_tokens = create_delete_err_macro(ty, &q_ret_ty); let delete_err_with_tokens = create_delete_err_with_macro(ty, &q_ret_ty); - quote! { + delete_ops_tokens.extend(quote! { #delete_err_tokens #delete_err_with_tokens - } + }); } + + let delete_with_querybuilder = generate_delete_query_tokens(&ty, table_schema_data); + delete_ops_tokens.extend(delete_with_querybuilder); + + delete_ops_tokens } /// Generates the TokenStream for the __delete() CRUD operation as a /// [`query_elements::query_builder::QueryBuilder<'a, #ty>`] -pub fn generate_delete_query_tokens( - macro_data: &MacroTokens, +fn generate_delete_query_tokens( + ty: &Ident, table_schema_data: &str, ) -> TokenStream { - let ty = macro_data.ty; - quote! { - // /// Generates a [`canyon_sql::query::DeleteQueryBuilder`] - // /// that allows you to customize the query by adding parameters and constrains dynamically. - // /// - // /// It performs an `DELETE FROM table_name`, where `table_name` it's the name of your - // /// entity but converted to the corresponding database convention, - // /// unless concrete values are set on the available parameters of the - // /// `canyon_macro(table_name = "table_name", schema = "schema")` - // fn delete_query<'a>() -> canyon_sql::query::DeleteQueryBuilder<'a, #ty> { - // canyon_sql::query::DeleteQueryBuilder::new(#table_schema_data, "") - // } - - // /// Generates a [`canyon_sql::query::DeleteQueryBuilder`] - // /// that allows you to customize the query by adding parameters and constrains dynamically. - // /// - // /// It performs an `DELETE FROM table_name`, where `table_name` it's the name of your - // /// entity but converted to the corresponding database convention, - // /// unless concrete values are set on the available parameters of the - // /// `canyon_macro(table_name = "table_name", schema = "schema")` - // /// - // /// The query it's made against the database with the configured datasource - // /// described in the configuration file, and selected with the [`&str`] - // /// passed as parameter. - // fn delete_query_datasource<'a>(datasource_name: &'a str) -> canyon_sql::query::DeleteQueryBuilder<'a, #ty> { - // canyon_sql::query::DeleteQueryBuilder::new(#table_schema_data, datasource_name) - // } + /// Generates a [`canyon_sql::query::DeleteQueryBuilder`] + /// that allows you to customize the query by adding parameters and constrains dynamically. + /// + /// It performs an `DELETE FROM table_name`, where `table_name` it's the name of your + /// entity but converted to the corresponding database convention, + /// unless concrete values are set on the available parameters of the + /// `canyon_macro(table_name = "table_name", schema = "schema")` + fn delete_query<'a>() -> canyon_sql::query::DeleteQueryBuilder<'a, #ty, &'a str> { + canyon_sql::query::DeleteQueryBuilder::new(#table_schema_data, "") + } + + /// Generates a [`canyon_sql::query::DeleteQueryBuilder`] + /// that allows you to customize the query by adding parameters and constrains dynamically. + /// + /// It performs an `DELETE FROM table_name`, where `table_name` it's the name of your + /// entity but converted to the corresponding database convention, + /// unless concrete values are set on the available parameters of the + /// `canyon_macro(table_name = "table_name", schema = "schema")` + /// + /// The query it's made against the database with the configured datasource + /// described in the configuration file, selected with the input parameter + fn delete_query_with<'a, I>(input: I) -> canyon_sql::query::DeleteQueryBuilder<'a, #ty, I> + where I: canyon_sql::core::DbConnection + Send + 'a + { + canyon_sql::query::DeleteQueryBuilder::new(#table_schema_data, input) + } } } diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index 331b908d..c5faec73 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -74,7 +74,7 @@ pub fn generate_find_all_query_tokens( /// described in the configuration file, and selected with the [`&str`] /// passed as parameter. fn select_query_with<'a, I>(input: I) -> canyon_sql::query::SelectQueryBuilder<'a, #ty, I> - where I: canyon_sql::core::DbConnection + Send + 'a, + where I: canyon_sql::core::DbConnection + Send + 'a { canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, input) } From c386ef25573cedbe046bbdd357e48e1fb586d946 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 27 Jan 2025 12:15:53 +0100 Subject: [PATCH 051/155] feat: re-enabled the update operations --- canyon_crud/src/crud.rs | 18 +- canyon_macros/src/lib.rs | 22 +- .../{tests_consts.rs => consts.rs} | 0 canyon_macros/src/query_operations/delete.rs | 17 +- canyon_macros/src/query_operations/mod.rs | 2 +- canyon_macros/src/query_operations/select.rs | 2 +- canyon_macros/src/query_operations/update.rs | 243 +++++---- tests/crud/delete_operations.rs | 172 +++---- tests/crud/querybuilder_operations.rs | 467 +++++++++--------- tests/crud/update_operations.rs | 284 +++++------ tests/tests_models/tournament.rs | 30 +- 11 files changed, 646 insertions(+), 611 deletions(-) rename canyon_macros/src/query_operations/{tests_consts.rs => consts.rs} (100%) diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index ae61b0d6..d163c91e 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -83,16 +83,20 @@ where // datasource_name: &'a str, // ) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'a)>>; - // async fn update(&self) -> Result<(), Box>; + async fn update(&self) -> Result<(), Box>; - // async fn update_with<'a>( - // &self, - // datasource_name: &'a str, - // ) -> Result<(), Box>; + async fn update_with<'a, I>( + &self, + input: I, + ) -> Result<(), Box> + where + I: DbConnection + Send + 'a; - // fn update_query<'a>() -> UpdateQueryBuilder<'a, T>; + fn update_query<'a>() -> UpdateQueryBuilder<'a, T, &'a str>; - // fn update_query_with(datasource_name: &str) -> UpdateQueryBuilder<'_, T>; + fn update_query_with<'a, I>(input: I) -> UpdateQueryBuilder<'a, T, I> + where + I: DbConnection + Send + 'a; async fn delete(&self) -> Result<(), Box>; diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index 0f672955..a0b361a9 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -26,10 +26,8 @@ use query_operations::{ // generate_find_by_foreign_key_tokens, // generate_find_by_reverse_foreign_key_tokens, }, - insert::{generate_insert_tokens, - // generate_multiple_insert_tokens - }, - update::{generate_update_query_tokens, generate_update_tokens}, + insert::generate_insert_tokens, + update::generate_update_tokens, delete::generate_delete_tokens, }; use utils::{function_parser::FunctionParser, helpers, macro_tokens::MacroTokens}; @@ -260,9 +258,7 @@ fn impl_crud_operations_trait_for_struct( // let _insert_multi_tokens = generate_multiple_insert_tokens(macro_data, &table_schema_data); // Builds the update() queries - let _update_tokens = generate_update_tokens(macro_data, &table_schema_data); - // Builds the update() query as a QueryBuilder - let _update_query_tokens = generate_update_query_tokens(macro_data, &table_schema_data); + let update_tokens = generate_update_tokens(macro_data, &table_schema_data); // Builds the delete() queries let delete_tokens = generate_delete_tokens(macro_data, &table_schema_data); @@ -294,22 +290,16 @@ fn impl_crud_operations_trait_for_struct( // The SELECT_QUERYBUILDER impl #find_all_query_tokens - // // The find_by_pk impl - // #_find_by_pk_tokens - // The insert impl #insert_tokens // // The insert of multiple entities impl // #_insert_multi_tokens - // // The update impl - // #_update_tokens - - // // The update as a querybuilder impl - // #_update_query_tokens + // The update impl + #update_tokens - // // The delete impl + // The delete impl #delete_tokens }; diff --git a/canyon_macros/src/query_operations/tests_consts.rs b/canyon_macros/src/query_operations/consts.rs similarity index 100% rename from canyon_macros/src/query_operations/tests_consts.rs rename to canyon_macros/src/query_operations/consts.rs diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index f7b49c03..d9995450 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -8,9 +8,8 @@ use crate::utils::macro_tokens::MacroTokens; /// returning a result, indicating a possible failure querying the database pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &String) -> TokenStream { let mut delete_ops_tokens = TokenStream::new(); - - let ty = macro_data.ty; + let ty = macro_data.ty; let fields = macro_data.get_struct_fields(); let pk = macro_data.get_primary_key_annotation(); @@ -45,10 +44,10 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri #delete_err_with_tokens }); } - + let delete_with_querybuilder = generate_delete_query_tokens(&ty, table_schema_data); delete_ops_tokens.extend(delete_with_querybuilder); - + delete_ops_tokens } @@ -94,7 +93,7 @@ mod __details { use crate::query_operations::macro_template::MacroOperationBuilder; use super::*; - pub fn create_delete_macro(ty: &syn::Ident, stmt: &str, pk_field_value: &TokenStream, ret_ty: &TokenStream) -> MacroOperationBuilder { + pub fn create_delete_macro(ty: &Ident, stmt: &str, pk_field_value: &TokenStream, ret_ty: &TokenStream) -> MacroOperationBuilder { MacroOperationBuilder::new() .fn_name("delete") .with_self_as_ref() @@ -110,7 +109,7 @@ mod __details { .with_no_result_value() } - pub fn create_delete_with_macro(ty: &syn::Ident, stmt: &str, pk_field_value: &TokenStream, ret_ty: &TokenStream) -> MacroOperationBuilder { + pub fn create_delete_with_macro(ty: &Ident, stmt: &str, pk_field_value: &TokenStream, ret_ty: &TokenStream) -> MacroOperationBuilder { MacroOperationBuilder::new() .fn_name("delete_with") .with_self_as_ref() @@ -128,7 +127,7 @@ mod __details { .with_no_result_value() } - pub fn create_delete_err_macro(ty: &syn::Ident, ret_ty: &TokenStream) -> MacroOperationBuilder { + pub fn create_delete_err_macro(ty: &Ident, ret_ty: &TokenStream) -> MacroOperationBuilder { MacroOperationBuilder::new() .fn_name("delete") .with_self_as_ref() @@ -139,7 +138,7 @@ mod __details { .with_direct_error_return(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) } - pub fn create_delete_err_with_macro(ty: &syn::Ident, ret_ty: &TokenStream) -> MacroOperationBuilder { + pub fn create_delete_err_with_macro(ty: &Ident, ret_ty: &TokenStream) -> MacroOperationBuilder { MacroOperationBuilder::new() .fn_name("delete_with") .with_self_as_ref() @@ -154,7 +153,7 @@ mod __details { #[cfg(test)] mod delete_tests { - use crate::query_operations::tests_consts::*; + use crate::query_operations::consts::*; use super::__details::*; use proc_macro2::Span; use quote::quote; diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index a487171e..1e2703cc 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -6,4 +6,4 @@ pub mod update; mod doc_comments; mod macro_template; -mod tests_consts; +mod consts; diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index c5faec73..422dfb0d 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -338,7 +338,7 @@ mod __details { #[cfg(test)] mod macro_builder_read_ops_tests { use super::__details::{count_generators::*, find_all_generators::*, pk_generators::*}; - use crate::query_operations::tests_consts::*; + use crate::query_operations::consts::*; use proc_macro2::Span; use quote::quote; use syn::Ident; diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index 3edca853..c619f458 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -1,15 +1,15 @@ -use proc_macro2::TokenStream; +use proc_macro2::{Ident, TokenStream}; use quote::quote; use crate::utils::macro_tokens::MacroTokens; +use crate::query_operations::update::__details::*; /// Generates the TokenStream for the __update() CRUD operation pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &String) -> TokenStream { - let ty = macro_data.ty; + let mut update_ops_tokens = TokenStream::new(); + let ty = macro_data.ty; let update_columns = macro_data.get_column_names_pk_parsed(); - - // Retrieves the fields of the Struct let fields = macro_data.get_struct_fields(); let mut vec_columns_values: Vec = Vec::new(); @@ -30,113 +30,156 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri .get_pk_index() .expect("Update method failed to retrieve the index of the primary key"); - quote! { - // /// Updates a database record that matches - // /// the current instance of a T type, returning a result - // /// indicating a possible failure querying the database. - // async fn update(&self) -> Result<(), Box> { - // let stmt = format!( - // "UPDATE {} SET {} WHERE {} = ${:?}", - // #table_schema_data, #str_columns_values, #primary_key, #pk_index + 1 - // ); - // let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = &[#(#update_values),*]; - - // <#ty as canyon_sql::core::Transaction<#ty>>::query( - // stmt, update_values, "" - // ).await?; - - // Ok(()) - // } - - - // /// Updates a database record that matches - // /// the current instance of a T type, returning a result - // /// indicating a possible failure querying the database with the - // /// specified datasource - // async fn update_with<'a, I>(&self, input: I) - // -> Result<(), Box> - // { - // let stmt = format!( - // "UPDATE {} SET {} WHERE {} = ${:?}", - // #table_schema_data, #str_columns_values, #primary_key, #pk_index + 1 - // ); - // let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = &[#(#update_values_cloned),*]; - - // <#ty as canyon_sql::core::Transaction<#ty>>::query( - // stmt, update_values, datasource_name - // ).await?; - - // Ok(()) - // } - } + update_ops_tokens.extend(quote! { + /// Updates a database record that matches + /// the current instance of a T type, returning a result + /// indicating a possible failure querying the database. + async fn update(&self) -> Result<(), Box> { + let stmt = format!( + "UPDATE {} SET {} WHERE {} = ${:?}", + #table_schema_data, #str_columns_values, #primary_key, #pk_index + 1 + ); + let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = &[#(#update_values),*]; + + <#ty as canyon_sql::core::Transaction<#ty>>::query( + stmt, update_values, "" + ).await?; + + Ok(()) + } + + /// Updates a database record that matches + /// the current instance of a T type, returning a result + /// indicating a possible failure querying the database with the + /// specified datasource + async fn update_with<'a, I>(&self, input: I) + -> Result<(), Box> + where I: canyon_sql::core::DbConnection + Send + 'a + { + let stmt = format!( + "UPDATE {} SET {} WHERE {} = ${:?}", + #table_schema_data, #str_columns_values, #primary_key, #pk_index + 1 + ); + let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = &[#(#update_values_cloned),*]; + + <#ty as canyon_sql::core::Transaction<#ty>>::query( + stmt, update_values, input + ).await?; + + Ok(()) + } + }); } else { // If there's no primary key, update method over self won't be available. // Use instead the update associated function of the querybuilder + let update_err_tokens = create_update_err_macro(ty); + let update_err_with_tokens = create_update_err_with_macro(ty); - // TODO Returning an error should be a provisional way of doing this - quote! { - // async fn update(&self) - // -> Result<(), Box> - // { - // Err( - // std::io::Error::new( - // std::io::ErrorKind::Unsupported, - // "You can't use the 'update' method on a \ - // CanyonEntity that does not have a #[primary_key] annotation. \ - // If you need to perform an specific search, use the Querybuilder instead." - // ).into_inner().unwrap() - // ) - // } - - // async fn update_with<'a, I>(&self, input: I) - // -> Result<(), Box> - // { - // Err( - // std::io::Error::new( - // std::io::ErrorKind::Unsupported, - // "You can't use the 'update_with' method on a \ - // CanyonEntity that does not have a #[primary_key] annotation. \ - // If you need to perform an specific search, use the Querybuilder instead." - // ).into_inner().unwrap() - // ) - // } - } + update_ops_tokens.extend(quote! { + #update_err_tokens + #update_err_with_tokens + }); } + + let querybuilder_update_tokens = generate_update_query_tokens(ty, table_schema_data); + update_ops_tokens.extend(querybuilder_update_tokens); + + update_ops_tokens } /// Generates the TokenStream for the __update() CRUD operation /// being the query generated with the [`QueryBuilder`] -pub fn generate_update_query_tokens( - macro_data: &MacroTokens, +fn generate_update_query_tokens( + ty: &Ident, table_schema_data: &String, ) -> TokenStream { - let ty = macro_data.ty; - quote! { - // /// Generates a [`canyon_sql::query::UpdateQueryBuilder`] - // /// that allows you to customize the query by adding parameters and constrains dynamically. - // /// - // /// It performs an `UPDATE table_name`, where `table_name` it's the name of your - // /// entity but converted to the corresponding database convention, - // /// unless concrete values are set on the available parameters of the - // /// `canyon_macro(table_name = "table_name", schema = "schema")` - // fn update_query<'a>() -> canyon_sql::query::UpdateQueryBuilder<'a, #ty> { - // canyon_sql::query::UpdateQueryBuilder::new(#table_schema_data, "") - // } - - // /// Generates a [`canyon_sql::query::UpdateQueryBuilder`] - // /// that allows you to customize the query by adding parameters and constrains dynamically. - // /// - // /// It performs an `UPDATE table_name`, where `table_name` it's the name of your - // /// entity but converted to the corresponding database convention, - // /// unless concrete values are set on the available parameters of the - // /// `canyon_macro(table_name = "table_name", schema = "schema")` - // /// - // /// The query it's made against the database with the configured datasource - // /// described in the configuration file, and selected with the [`&str`] - // /// passed as parameter. - // fn update_query_datasource<'a>(datasource_name: &'a str) -> canyon_sql::query::UpdateQueryBuilder<'a, #ty> { - // canyon_sql::query::UpdateQueryBuilder::new(#table_schema_data, datasource_name) - // } + /// Generates a [`canyon_sql::query::UpdateQueryBuilder`] + /// that allows you to customize the query by adding parameters and constrains dynamically. + /// + /// It performs an `UPDATE table_name`, where `table_name` it's the name of your + /// entity but converted to the corresponding database convention, + /// unless concrete values are set on the available parameters of the + /// `canyon_macro(table_name = "table_name", schema = "schema")` + fn update_query<'a>() -> canyon_sql::query::UpdateQueryBuilder<'a, #ty, &'a str> { + canyon_sql::query::UpdateQueryBuilder::new(#table_schema_data, "") + } + + /// Generates a [`canyon_sql::query::UpdateQueryBuilder`] + /// that allows you to customize the query by adding parameters and constrains dynamically. + /// + /// It performs an `UPDATE table_name`, where `table_name` it's the name of your + /// entity but converted to the corresponding database convention, + /// unless concrete values are set on the available parameters of the + /// `canyon_macro(table_name = "table_name", schema = "schema")` + /// + /// The query it's made against the database with the configured datasource + /// described in the configuration file, and selected with the input parameter + fn update_query_with<'a, I>(input: I) -> canyon_sql::query::UpdateQueryBuilder<'a, #ty, I> + where I: canyon_sql::core::DbConnection + Send + 'a + { + canyon_sql::query::UpdateQueryBuilder::new(#table_schema_data, input) + } } } + +mod __details { + use quote::quote; + use proc_macro2::TokenStream; + use crate::query_operations::consts::VOID_RET_TY; + use crate::query_operations::doc_comments; + use crate::query_operations::macro_template::MacroOperationBuilder; + + pub fn create_update_err_macro(ty: &syn::Ident) -> MacroOperationBuilder { + MacroOperationBuilder::new() + .fn_name("update") + .with_self_as_ref() + .user_type(ty) + .return_type_ts(&VOID_RET_TY.with(|v| v.borrow().clone())) + .raw_return() + .add_doc_comment(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) + .with_direct_error_return(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) + } + + pub fn create_update_err_with_macro(ty: &syn::Ident) -> MacroOperationBuilder { + MacroOperationBuilder::new() + .fn_name("update_with") + .with_self_as_ref() + .with_input_param() + .user_type(ty) + .return_type_ts(&VOID_RET_TY.with(|v| v.borrow().clone())) + .raw_return() + .add_doc_comment(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) + .with_direct_error_return(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) + } +} + +#[cfg(test)] +mod update_tokens_tests { + use crate::query_operations::consts::{INPUT_PARAM, LT_CONSTRAINT, RES_VOID_RET_TY, RES_VOID_RET_TY_LT, USER_MOCK_TY, VOID_RET_TY}; + use crate::query_operations::update::__details::{create_update_err_macro, create_update_err_with_macro}; + + #[test] + fn test_macro_builder_update_err() { + let update_err_builder = create_update_err_macro( + &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()) + ); + let update_err = update_err_builder.generate_tokens().to_string(); + + assert!(update_err.contains("async fn update")); + assert!(update_err.contains(RES_VOID_RET_TY)); + } + + #[test] + fn test_macro_builder_update_err_with() { + let update_err_with_builder = create_update_err_with_macro( + &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()) + ); + let update_err_with = update_err_with_builder.generate_tokens().to_string(); + + assert!(update_err_with.contains("async fn update_with")); + assert!(update_err_with.contains(RES_VOID_RET_TY_LT)); + assert!(update_err_with.contains(LT_CONSTRAINT)); + assert!(update_err_with.contains(INPUT_PARAM)); + } +} \ No newline at end of file diff --git a/tests/crud/delete_operations.rs b/tests/crud/delete_operations.rs index f3953732..e9bb61ef 100644 --- a/tests/crud/delete_operations.rs +++ b/tests/crud/delete_operations.rs @@ -64,96 +64,96 @@ fn test_crud_delete_method_operation() { ); } -// /// Same as the delete test, but performing the operations with the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_with_mssql_method_operation() { -// // For test the delete, we will insert a new instance of the database, and then, -// // after inspect it, we will proceed to delete it -// let mut new_league: League = League { -// id: Default::default(), -// ext_id: 7892635306594_i64, -// slug: "some-new-league".to_string(), -// name: "Some New League".to_string(), -// region: "Bahía de cochinos".to_string(), -// image_url: "https://nobodyspectsandimage.io".to_string(), -// }; +/// Same as the delete test, but performing the operations with the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_delete_with_mssql_method_operation() { + // For test the delete, we will insert a new instance of the database, and then, + // after inspect it, we will proceed to delete it + let mut new_league: League = League { + id: Default::default(), + ext_id: 7892635306594_i64, + slug: "some-new-league".to_string(), + name: "Some New League".to_string(), + region: "Bahía de cochinos".to_string(), + image_url: "https://nobodyspectsandimage.io".to_string(), + }; -// // We insert the instance on the database, on the `League` entity -// new_league -// .insert_with(SQL_SERVER_DS) -// .await -// .expect("Failed insert operation"); -// assert_eq!( -// new_league.id, -// League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) -// .await -// .expect("Request error") -// .expect("None value") -// .id -// ); + // We insert the instance on the database, on the `League` entity + new_league + .insert_with(SQL_SERVER_DS) + .await + .expect("Failed insert operation"); + assert_eq!( + new_league.id, + League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) + .await + .expect("Request error") + .expect("None value") + .id + ); -// // Now that we have an instance mapped to some entity by a primary key, we can now -// // remove that entry from the database with the delete operation -// new_league -// .delete_with(SQL_SERVER_DS) -// .await -// .expect("Failed to delete the operation"); + // Now that we have an instance mapped to some entity by a primary key, we can now + // remove that entry from the database with the delete operation + new_league + .delete_with(SQL_SERVER_DS) + .await + .expect("Failed to delete the operation"); -// // To check the success, we can query by the primary key value and check if, after unwrap() -// // the result of the operation, the find by primary key contains Some(v) or None -// // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> -// assert_eq!( -// League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) -// .await -// .expect("Unwrapping the result, letting the Option"), -// None -// ); -// } + // To check the success, we can query by the primary key value and check if, after unwrap() + // the result of the operation, the find by primary key contains Some(v) or None + // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> + assert_eq!( + League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) + .await + .expect("Unwrapping the result, letting the Option"), + None + ); +} -// /// Same as the delete test, but performing the operations with the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_with_mysql_method_operation() { -// // For test the delete, we will insert a new instance of the database, and then, -// // after inspect it, we will proceed to delete it -// let mut new_league: League = League { -// id: Default::default(), -// ext_id: 7892635306594_i64, -// slug: "some-new-league".to_string(), -// name: "Some New League".to_string(), -// region: "Bahía de cochinos".to_string(), -// image_url: "https://nobodyspectsandimage.io".to_string(), -// }; +/// Same as the delete test, but performing the operations with the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_delete_with_mysql_method_operation() { + // For test the delete, we will insert a new instance of the database, and then, + // after inspect it, we will proceed to delete it + let mut new_league: League = League { + id: Default::default(), + ext_id: 7892635306594_i64, + slug: "some-new-league".to_string(), + name: "Some New League".to_string(), + region: "Bahía de cochinos".to_string(), + image_url: "https://nobodyspectsandimage.io".to_string(), + }; -// // We insert the instance on the database, on the `League` entity -// new_league -// .insert_with(MYSQL_DS) -// .await -// .expect("Failed insert operation"); -// assert_eq!( -// new_league.id, -// League::find_by_pk_with(&new_league.id, MYSQL_DS) -// .await -// .expect("Request error") -// .expect("None value") -// .id -// ); + // We insert the instance on the database, on the `League` entity + new_league + .insert_with(MYSQL_DS) + .await + .expect("Failed insert operation"); + assert_eq!( + new_league.id, + League::find_by_pk_with(&new_league.id, MYSQL_DS) + .await + .expect("Request error") + .expect("None value") + .id + ); -// // Now that we have an instance mapped to some entity by a primary key, we can now -// // remove that entry from the database with the delete operation -// new_league -// .delete_with(MYSQL_DS) -// .await -// .expect("Failed to delete the operation"); + // Now that we have an instance mapped to some entity by a primary key, we can now + // remove that entry from the database with the delete operation + new_league + .delete_with(MYSQL_DS) + .await + .expect("Failed to delete the operation"); -// // To check the success, we can query by the primary key value and check if, after unwrap() -// // the result of the operation, the find by primary key contains Some(v) or None -// // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> -// assert_eq!( -// League::find_by_pk_with(&new_league.id, MYSQL_DS) -// .await -// .expect("Unwrapping the result, letting the Option"), -// None -// ); -// } + // To check the success, we can query by the primary key value and check if, after unwrap() + // the result of the operation, the find by primary key contains Some(v) or None + // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> + assert_eq!( + League::find_by_pk_with(&new_league.id, MYSQL_DS) + .await + .expect("Unwrapping the result, letting the Option"), + None + ); +} diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index eca6592e..8facb6c7 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -220,237 +220,236 @@ fn test_crud_find_with_querybuilder_with_mysql() { assert!(!filtered_find_players.unwrap().is_empty()); } -// -// /// Updates the values of the range on entries defined by the constraint parameters -// /// in the database entity -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_with_querybuilder() { -// // Find all the leagues with ID less or equals that 7 -// // and where it's region column value is equals to 'Korea' -// let mut q = League::update_query() -// .set(&[ -// (LeagueField::slug, "Updated with the QueryBuilder"), -// (LeagueField::name, "Random"), -// ]) -// .r#where(LeagueFieldValue::id(&1), Comp::Gt) -// .and(LeagueFieldValue::id(&8), Comp::Lt); -// -// /* NOTE: Family of QueryBuilders are clone, useful in case of need to read the generated SQL -// let qpr = q.clone(); -// println!("PSQL: {:?}", qpr.read_sql()); -// */ -// // We can now back to the original an throw the query -// q.query() -// .await -// .expect("Failed to update records with the querybuilder"); -// -// let found_updated_values = League::select_query() -// .r#where(LeagueFieldValue::id(&1), Comp::Gt) -// .and(LeagueFieldValue::id(&7), Comp::Lt) -// .query() -// .await -// .expect("Failed to retrieve database League entries with the querybuilder"); -// -// found_updated_values -// .iter() -// .for_each(|league| assert_eq!(league.slug, "Updated with the QueryBuilder")); -// } -// -// /// Same as above, but with the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_with_querybuilder_with_mssql() { -// // Find all the leagues with ID less or equals that 7 -// // and where it's region column value is equals to 'Korea' -// let mut q = Player::update_query_with(SQL_SERVER_DS); -// q.set(&[ -// (PlayerField::summoner_name, "Random updated player name"), -// (PlayerField::first_name, "I am an updated first name"), -// ]) -// .r#where(PlayerFieldValue::id(&1), Comp::Gt) -// .and(PlayerFieldValue::id(&8), Comp::Lt) -// .query() -// .await -// .expect("Failed to update records with the querybuilder"); -// -// let found_updated_values = Player::select_query_with(SQL_SERVER_DS) -// .r#where(PlayerFieldValue::id(&1), Comp::Gt) -// .and(PlayerFieldValue::id(&7), Comp::LtEq) -// .query() -// .await -// .expect("Failed to retrieve database League entries with the querybuilder"); -// -// found_updated_values.iter().for_each(|player| { -// assert_eq!(player.summoner_name, "Random updated player name"); -// assert_eq!(player.first_name, "I am an updated first name"); -// }); -// } -// -// /// Same as above, but with the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_with_querybuilder_with_mysql() { -// // Find all the leagues with ID less or equals that 7 -// // and where it's region column value is equals to 'Korea' -// -// let mut q = Player::update_query_with(MYSQL_DS); -// q.set(&[ -// (PlayerField::summoner_name, "Random updated player name"), -// (PlayerField::first_name, "I am an updated first name"), -// ]) -// .r#where(PlayerFieldValue::id(&1), Comp::Gt) -// .and(PlayerFieldValue::id(&8), Comp::Lt) -// .query() -// .await -// .expect("Failed to update records with the querybuilder"); -// -// let found_updated_values = Player::select_query_with(MYSQL_DS) -// .r#where(PlayerFieldValue::id(&1), Comp::Gt) -// .and(PlayerFieldValue::id(&7), Comp::LtEq) -// .query() -// .await -// .expect("Failed to retrieve database League entries with the querybuilder"); -// -// found_updated_values.iter().for_each(|player| { -// assert_eq!(player.summoner_name, "Random updated player name"); -// assert_eq!(player.first_name, "I am an updated first name"); -// }); -// } -// -// /// Deletes entries from the mapped entity `T` that are in the ranges filtered -// /// with the QueryBuilder -// /// -// /// Note if the database is persisted (not created and destroyed on every docker or -// /// GitHub Action wake up), it won't delete things that already have been deleted, -// /// but this isn't an error. They just don't exists. -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_with_querybuilder() { -// Tournament::delete_query() -// .r#where(TournamentFieldValue::id(&14), Comp::Gt) -// .and(TournamentFieldValue::id(&16), Comp::Lt) -// .query() -// .await -// .expect("Error connecting with the database on the delete operation"); -// -// assert_eq!(Tournament::find_by_pk(&15).await.unwrap(), None); -// } -// -// /// Same as the above delete, but with the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_with_querybuilder_with_mssql() { -// Player::delete_query_with(SQL_SERVER_DS) -// .r#where(PlayerFieldValue::id(&120), Comp::Gt) -// .and(PlayerFieldValue::id(&130), Comp::Lt) -// .query() -// .await -// .expect("Error connecting with the database when we are going to delete data! :)"); -// -// assert!(Player::select_query_with(SQL_SERVER_DS) -// .r#where(PlayerFieldValue::id(&122), Comp::Eq) -// .query() -// .await -// .unwrap() -// .is_empty()); -// } -// -// /// Same as the above delete, but with the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_with_querybuilder_with_mysql() { -// Player::delete_query_with(MYSQL_DS) -// .r#where(PlayerFieldValue::id(&120), Comp::Gt) -// .and(PlayerFieldValue::id(&130), Comp::Lt) -// .query() -// .await -// .expect("Error connecting with the database when we are going to delete data! :)"); -// -// assert!(Player::select_query_with(MYSQL_DS) -// .r#where(PlayerFieldValue::id(&122), Comp::Eq) -// .query() -// .await -// .unwrap() -// .is_empty()); -// } -// -// /// Tests for the generated SQL query after use the -// /// WHERE clause -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_where_clause() { -// let mut l = League::select_query(); -// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq); -// -// assert_eq!(l.read_sql(), "SELECT * FROM league WHERE name = $1") -// } -// -// /// Tests for the generated SQL query after use the -// /// AND clause -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_and_clause() { -// let mut l = League::select_query(); -// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) -// .and(LeagueFieldValue::id(&10), Comp::LtEq); -// -// assert_eq!( -// l.read_sql().trim(), -// "SELECT * FROM league WHERE name = $1 AND id <= $2" -// ) -// } -// -// /// Tests for the generated SQL query after use the -// /// AND clause -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_and_clause_with_in_constraint() { -// let mut l = League::select_query(); -// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) -// .and_values_in(LeagueField::id, &[1, 7, 10]); -// -// assert_eq!( -// l.read_sql().trim(), -// "SELECT * FROM league WHERE name = $1 AND id IN ($1, $2, $3)" -// ) -// } -// -// /// Tests for the generated SQL query after use the -// /// AND clause -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_or_clause() { -// let mut l = League::select_query(); -// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) -// .or(LeagueFieldValue::id(&10), Comp::LtEq); -// -// assert_eq!( -// l.read_sql().trim(), -// "SELECT * FROM league WHERE name = $1 OR id <= $2" -// ) -// } -// -// /// Tests for the generated SQL query after use the -// /// AND clause -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_or_clause_with_in_constraint() { -// let mut l = League::select_query(); -// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) -// .or_values_in(LeagueField::id, &[1, 7, 10]); -// -// assert_eq!( -// l.read_sql(), -// "SELECT * FROM league WHERE name = $1 OR id IN ($1, $2, $3)" -// ) -// } -// -// /// Tests for the generated SQL query after use the -// /// AND clause -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_order_by_clause() { -// let mut l = League::select_query(); -// l.r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) -// .order_by(LeagueField::id, false); -// -// assert_eq!( -// l.read_sql(), -// "SELECT * FROM league WHERE name = $1 ORDER BY id" -// ) -// } + +/// Updates the values of the range on entries defined by the constraint parameters +/// in the database entity +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_update_with_querybuilder() { + // Find all the leagues with ID less or equals that 7 + // and where it's region column value is equals to 'Korea' + let mut q = League::update_query() + .set(&[ + (LeagueField::slug, "Updated with the QueryBuilder"), + (LeagueField::name, "Random"), + ]) + .r#where(LeagueFieldValue::id(&1), Comp::Gt) + .and(LeagueFieldValue::id(&8), Comp::Lt); + + /* NOTE: Family of QueryBuilders are clone, useful in case of need to read the generated SQL + let qpr = q.clone(); + println!("PSQL: {:?}", qpr.read_sql()); + */ + q.query() + .await + .expect("Failed to update records with the querybuilder"); + + let found_updated_values = League::select_query() + .r#where(LeagueFieldValue::id(&1), Comp::Gt) + .and(LeagueFieldValue::id(&7), Comp::Lt) + .query() + .await + .expect("Failed to retrieve database League entries with the querybuilder"); + + found_updated_values + .iter() + .for_each(|league| assert_eq!(league.slug, "Updated with the QueryBuilder")); +} + +/// Same as above, but with the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_update_with_querybuilder_with_mssql() { + // Find all the leagues with ID less or equals that 7 + // and where it's region column value is equals to 'Korea' + let mut q = Player::update_query_with(SQL_SERVER_DS); + q.set(&[ + (PlayerField::summoner_name, "Random updated player name"), + (PlayerField::first_name, "I am an updated first name"), + ]) + .r#where(PlayerFieldValue::id(&1), Comp::Gt) + .and(PlayerFieldValue::id(&8), Comp::Lt) + .query() + .await + .expect("Failed to update records with the querybuilder"); + + let found_updated_values = Player::select_query_with(SQL_SERVER_DS) + .r#where(PlayerFieldValue::id(&1), Comp::Gt) + .and(PlayerFieldValue::id(&7), Comp::LtEq) + .query() + .await + .expect("Failed to retrieve database League entries with the querybuilder"); + + found_updated_values.iter().for_each(|player| { + assert_eq!(player.summoner_name, "Random updated player name"); + assert_eq!(player.first_name, "I am an updated first name"); + }); +} + +/// Same as above, but with the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_update_with_querybuilder_with_mysql() { + // Find all the leagues with ID less or equals that 7 + // and where it's region column value is equals to 'Korea' + + let mut q = Player::update_query_with(MYSQL_DS); + q.set(&[ + (PlayerField::summoner_name, "Random updated player name"), + (PlayerField::first_name, "I am an updated first name"), + ]) + .r#where(PlayerFieldValue::id(&1), Comp::Gt) + .and(PlayerFieldValue::id(&8), Comp::Lt) + .query() + .await + .expect("Failed to update records with the querybuilder"); + + let found_updated_values = Player::select_query_with(MYSQL_DS) + .r#where(PlayerFieldValue::id(&1), Comp::Gt) + .and(PlayerFieldValue::id(&7), Comp::LtEq) + .query() + .await + .expect("Failed to retrieve database League entries with the querybuilder"); + + found_updated_values.iter().for_each(|player| { + assert_eq!(player.summoner_name, "Random updated player name"); + assert_eq!(player.first_name, "I am an updated first name"); + }); +} + +/// Deletes entries from the mapped entity `T` that are in the ranges filtered +/// with the QueryBuilder +/// +/// Note if the database is persisted (not created and destroyed on every docker or +/// GitHub Action wake up), it won't delete things that already have been deleted, +/// but this isn't an error. They just don't exists. +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_delete_with_querybuilder() { + Tournament::delete_query() + .r#where(TournamentFieldValue::id(&14), Comp::Gt) + .and(TournamentFieldValue::id(&16), Comp::Lt) + .query() + .await + .expect("Error connecting with the database on the delete operation"); + + assert_eq!(Tournament::find_by_pk(&15).await.unwrap(), None); +} + +/// Same as the above delete, but with the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_delete_with_querybuilder_with_mssql() { + Player::delete_query_with(SQL_SERVER_DS) + .r#where(PlayerFieldValue::id(&120), Comp::Gt) + .and(PlayerFieldValue::id(&130), Comp::Lt) + .query() + .await + .expect("Error connecting with the database when we are going to delete data! :)"); + + assert!(Player::select_query_with(SQL_SERVER_DS) + .r#where(PlayerFieldValue::id(&122), Comp::Eq) + .query() + .await + .unwrap() + .is_empty()); +} + +/// Same as the above delete, but with the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_delete_with_querybuilder_with_mysql() { + Player::delete_query_with(MYSQL_DS) + .r#where(PlayerFieldValue::id(&120), Comp::Gt) + .and(PlayerFieldValue::id(&130), Comp::Lt) + .query() + .await + .expect("Error connecting with the database when we are going to delete data! :)"); + + assert!(Player::select_query_with(MYSQL_DS) + .r#where(PlayerFieldValue::id(&122), Comp::Eq) + .query() + .await + .unwrap() + .is_empty()); +} + +/// Tests for the generated SQL query after use the +/// WHERE clause +#[canyon_sql::macros::canyon_tokio_test] +fn test_where_clause() { + let mut l = League::select_query() + .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq); + + assert_eq!(l.read_sql(), "SELECT * FROM league WHERE name = $1") +} + +/// Tests for the generated SQL query after use the +/// AND clause +#[canyon_sql::macros::canyon_tokio_test] +fn test_and_clause() { + let mut l = League::select_query() + .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) + .and(LeagueFieldValue::id(&10), Comp::LtEq); + + assert_eq!( + l.read_sql().trim(), + "SELECT * FROM league WHERE name = $1 AND id <= $2" + ) +} + +/// Tests for the generated SQL query after use the +/// AND clause +#[canyon_sql::macros::canyon_tokio_test] +fn test_and_clause_with_in_constraint() { + let mut l = League::select_query() + .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) + .and_values_in(LeagueField::id, &[1, 7, 10]); + + assert_eq!( + l.read_sql().trim(), + "SELECT * FROM league WHERE name = $1 AND id IN ($1, $2, $3)" + ) +} + +/// Tests for the generated SQL query after use the +/// AND clause +#[canyon_sql::macros::canyon_tokio_test] +fn test_or_clause() { + let mut l = League::select_query() + .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) + .or(LeagueFieldValue::id(&10), Comp::LtEq); + + assert_eq!( + l.read_sql().trim(), + "SELECT * FROM league WHERE name = $1 OR id <= $2" + ) +} + +/// Tests for the generated SQL query after use the +/// AND clause +#[canyon_sql::macros::canyon_tokio_test] +fn test_or_clause_with_in_constraint() { + let mut l = League::select_query() + .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) + .or_values_in(LeagueField::id, &[1, 7, 10]); + + assert_eq!( + l.read_sql(), + "SELECT * FROM league WHERE name = $1 OR id IN ($1, $2, $3)" + ) +} + +/// Tests for the generated SQL query after use the +/// AND clause +#[canyon_sql::macros::canyon_tokio_test] +fn test_order_by_clause() { + let mut l = League::select_query() + .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) + .order_by(LeagueField::id, false); + + assert_eq!( + l.read_sql(), + "SELECT * FROM league WHERE name = $1 ORDER BY id" + ) +} diff --git a/tests/crud/update_operations.rs b/tests/crud/update_operations.rs index cf00f595..4c3fffb0 100644 --- a/tests/crud/update_operations.rs +++ b/tests/crud/update_operations.rs @@ -1,142 +1,142 @@ -// use crate::tests_models::league::*; -// // Integration tests for the CRUD operations available in `Canyon` that -// /// generates and executes *UPDATE* statements -// use canyon_sql::crud::CrudOperations; - -// #[cfg(feature = "mysql")] -// use crate::constants::MYSQL_DS; -// #[cfg(feature = "mssql")] -// use crate::constants::SQL_SERVER_DS; - -// /// Update operation is a *CRUD* method defined for some entity `T`, that works by appliying -// /// some change to a Rust's entity instance, and persisting them into the database. -// /// -// /// The `t.update(&self)` operation is only enabled for types that -// /// has, at least, one of it's fields annotated with a `#[primary_key]` -// /// operation, because we use that concrete field to construct the clause that targets -// /// that entity. -// /// -// /// Attempt of usage the `t.update(&self)` method on an entity without `#[primary_key]` -// /// will raise a runtime error. -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_method_operation() { -// // We first retrieve some entity from the database. Note that we must make -// // the retrieved instance mutable of clone it to a new mutable resource -// let mut updt_candidate: League = League::find_by_pk(&1) -// .await -// .expect("[1] - Failed the query to the database") -// .expect("[1] - No entity found for the primary key value passed in"); - -// // The ext_id field value is extracted from the sql scripts under the -// // docker/sql folder. We are retrieving the first entity inserted at the -// // wake up time of the database, and now checking some of its properties. -// assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); - -// // Modify the value, and perform the update -// let updt_value: i64 = 593064_i64; -// updt_candidate.ext_id = updt_value; -// updt_candidate -// .update() -// .await -// .expect("Failed the update operation"); - -// // Retrieve it again, and check if the value was really updated -// let updt_entity: League = League::find_by_pk(&1) -// .await -// .expect("[2] - Failed the query to the database") -// .expect("[2] - No entity found for the primary key value passed in"); - -// assert_eq!(updt_entity.ext_id, updt_value); - -// // We rollback the changes to the initial value to don't broke other tests -// // the next time that will run -// updt_candidate.ext_id = 100695891328981122_i64; -// updt_candidate -// .update() -// .await -// .expect("Failed the restablish initial value update operation"); -// } - -// /// Same as the above test, but with the specified datasource. -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_with_mssql_method_operation() { -// // We first retrieve some entity from the database. Note that we must make -// // the retrieved instance mutable of clone it to a new mutable resource -// let mut updt_candidate: League = League::find_by_pk_with(&1, SQL_SERVER_DS) -// .await -// .expect("[1] - Failed the query to the database") -// .expect("[1] - No entity found for the primary key value passed in"); - -// // The ext_id field value is extracted from the sql scripts under the -// // docker/sql folder. We are retrieving the first entity inserted at the -// // wake up time of the database, and now checking some of its properties. -// assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); - -// // Modify the value, and perform the update -// let updt_value: i64 = 59306442534_i64; -// updt_candidate.ext_id = updt_value; -// updt_candidate -// .update_with(SQL_SERVER_DS) -// .await -// .expect("Failed the update operation"); - -// // Retrieve it again, and check if the value was really updated -// let updt_entity: League = League::find_by_pk_with(&1, SQL_SERVER_DS) -// .await -// .expect("[2] - Failed the query to the database") -// .expect("[2] - No entity found for the primary key value passed in"); - -// assert_eq!(updt_entity.ext_id, updt_value); - -// // We rollback the changes to the initial value to don't broke other tests -// // the next time that will run -// updt_candidate.ext_id = 100695891328981122_i64; -// updt_candidate -// .update_with(SQL_SERVER_DS) -// .await -// .expect("Failed to restablish the initial value update operation"); -// } - -// /// Same as the above test, but with the specified datasource. -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_with_mysql_method_operation() { -// // We first retrieve some entity from the database. Note that we must make -// // the retrieved instance mutable of clone it to a new mutable resource - -// let mut updt_candidate: League = League::find_by_pk_with(&1, MYSQL_DS) -// .await -// .expect("[1] - Failed the query to the database") -// .expect("[1] - No entity found for the primary key value passed in"); - -// // The ext_id field value is extracted from the sql scripts under the -// // docker/sql folder. We are retrieving the first entity inserted at the -// // wake up time of the database, and now checking some of its properties. -// assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); - -// // Modify the value, and perform the update -// let updt_value: i64 = 59306442534_i64; -// updt_candidate.ext_id = updt_value; -// updt_candidate -// .update_with(MYSQL_DS) -// .await -// .expect("Failed the update operation"); - -// // Retrieve it again, and check if the value was really updated -// let updt_entity: League = League::find_by_pk_with(&1, MYSQL_DS) -// .await -// .expect("[2] - Failed the query to the database") -// .expect("[2] - No entity found for the primary key value passed in"); - -// assert_eq!(updt_entity.ext_id, updt_value); - -// // We rollback the changes to the initial value to don't broke other tests -// // the next time that will run -// updt_candidate.ext_id = 100695891328981122_i64; -// updt_candidate -// .update_with(MYSQL_DS) -// .await -// .expect("Failed to restablish the initial value update operation"); -// } +use crate::tests_models::league::*; +// Integration tests for the CRUD operations available in `Canyon` that +/// generates and executes *UPDATE* statements +use canyon_sql::crud::CrudOperations; + +#[cfg(feature = "mysql")] +use crate::constants::MYSQL_DS; +#[cfg(feature = "mssql")] +use crate::constants::SQL_SERVER_DS; + +/// Update operation is a *CRUD* method defined for some entity `T`, that works by appliying +/// some change to a Rust's entity instance, and persisting them into the database. +/// +/// The `t.update(&self)` operation is only enabled for types that +/// has, at least, one of it's fields annotated with a `#[primary_key]` +/// operation, because we use that concrete field to construct the clause that targets +/// that entity. +/// +/// Attempt of usage the `t.update(&self)` method on an entity without `#[primary_key]` +/// will raise a runtime error. +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_update_method_operation() { + // We first retrieve some entity from the database. Note that we must make + // the retrieved instance mutable of clone it to a new mutable resource + let mut updt_candidate: League = League::find_by_pk(&1) + .await + .expect("[1] - Failed the query to the database") + .expect("[1] - No entity found for the primary key value passed in"); + + // The ext_id field value is extracted from the sql scripts under the + // docker/sql folder. We are retrieving the first entity inserted at the + // wake up time of the database, and now checking some of its properties. + assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); + + // Modify the value, and perform the update + let updt_value: i64 = 593064_i64; + updt_candidate.ext_id = updt_value; + updt_candidate + .update() + .await + .expect("Failed the update operation"); + + // Retrieve it again, and check if the value was really updated + let updt_entity: League = League::find_by_pk(&1) + .await + .expect("[2] - Failed the query to the database") + .expect("[2] - No entity found for the primary key value passed in"); + + assert_eq!(updt_entity.ext_id, updt_value); + + // We rollback the changes to the initial value to don't broke other tests + // the next time that will run + updt_candidate.ext_id = 100695891328981122_i64; + updt_candidate + .update() + .await + .expect("Failed the restablish initial value update operation"); +} + +/// Same as the above test, but with the specified datasource. +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_update_with_mssql_method_operation() { + // We first retrieve some entity from the database. Note that we must make + // the retrieved instance mutable of clone it to a new mutable resource + let mut updt_candidate: League = League::find_by_pk_with(&1, SQL_SERVER_DS) + .await + .expect("[1] - Failed the query to the database") + .expect("[1] - No entity found for the primary key value passed in"); + + // The ext_id field value is extracted from the sql scripts under the + // docker/sql folder. We are retrieving the first entity inserted at the + // wake up time of the database, and now checking some of its properties. + assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); + + // Modify the value, and perform the update + let updt_value: i64 = 59306442534_i64; + updt_candidate.ext_id = updt_value; + updt_candidate + .update_with(SQL_SERVER_DS) + .await + .expect("Failed the update operation"); + + // Retrieve it again, and check if the value was really updated + let updt_entity: League = League::find_by_pk_with(&1, SQL_SERVER_DS) + .await + .expect("[2] - Failed the query to the database") + .expect("[2] - No entity found for the primary key value passed in"); + + assert_eq!(updt_entity.ext_id, updt_value); + + // We rollback the changes to the initial value to don't broke other tests + // the next time that will run + updt_candidate.ext_id = 100695891328981122_i64; + updt_candidate + .update_with(SQL_SERVER_DS) + .await + .expect("Failed to restablish the initial value update operation"); +} + +/// Same as the above test, but with the specified datasource. +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_update_with_mysql_method_operation() { + // We first retrieve some entity from the database. Note that we must make + // the retrieved instance mutable of clone it to a new mutable resource + + let mut updt_candidate: League = League::find_by_pk_with(&1, MYSQL_DS) + .await + .expect("[1] - Failed the query to the database") + .expect("[1] - No entity found for the primary key value passed in"); + + // The ext_id field value is extracted from the sql scripts under the + // docker/sql folder. We are retrieving the first entity inserted at the + // wake up time of the database, and now checking some of its properties. + assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); + + // Modify the value, and perform the update + let updt_value: i64 = 59306442534_i64; + updt_candidate.ext_id = updt_value; + updt_candidate + .update_with(MYSQL_DS) + .await + .expect("Failed the update operation"); + + // Retrieve it again, and check if the value was really updated + let updt_entity: League = League::find_by_pk_with(&1, MYSQL_DS) + .await + .expect("[2] - Failed the query to the database") + .expect("[2] - No entity found for the primary key value passed in"); + + assert_eq!(updt_entity.ext_id, updt_value); + + // We rollback the changes to the initial value to don't broke other tests + // the next time that will run + updt_candidate.ext_id = 100695891328981122_i64; + updt_candidate + .update_with(MYSQL_DS) + .await + .expect("Failed to restablish the initial value update operation"); +} diff --git a/tests/tests_models/tournament.rs b/tests/tests_models/tournament.rs index e6fab352..880076f4 100644 --- a/tests/tests_models/tournament.rs +++ b/tests/tests_models/tournament.rs @@ -1,15 +1,15 @@ -// use crate::tests_models::league::League; -// use canyon_sql::{date_time::NaiveDate, macros::*}; -// -// #[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] -// #[canyon_entity] -// pub struct Tournament { -// #[primary_key] -// id: i32, -// ext_id: i64, -// slug: String, -// start_date: NaiveDate, -// end_date: NaiveDate, -// #[foreign_key(table = "league", column = "id")] -// league: i32, -// } +use crate::tests_models::league::League; +use canyon_sql::{date_time::NaiveDate, macros::*}; + +#[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] +#[canyon_entity] +pub struct Tournament { + #[primary_key] + id: i32, + ext_id: i64, + slug: String, + start_date: NaiveDate, + end_date: NaiveDate, + #[foreign_key(table = "league", column = "id")] + league: i32, +} From 03ba49a6163f3b248872532b4f74f4a2f5da1c56 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 27 Jan 2025 17:50:12 +0100 Subject: [PATCH 052/155] feat: re-enabled the multi-insert operations --- canyon_crud/src/crud.rs | 18 +- canyon_macros/src/lib.rs | 3 - canyon_macros/src/query_operations/insert.rs | 651 ++++++++++--------- tests/crud/init_mssql.rs | 124 ++-- tests/crud/querybuilder_operations.rs | 20 +- 5 files changed, 412 insertions(+), 404 deletions(-) diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index d163c91e..8cd0a03a 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -74,14 +74,16 @@ where where I: DbConnection + Send + 'a; - // async fn multi_insert<'a>( - // instances: &'a mut [&'a mut T], - // ) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'a)>>; - - // async fn multi_insert_with<'a>( - // instances: &'a mut [&'a mut T], - // datasource_name: &'a str, - // ) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'a)>>; + async fn multi_insert<'a>( + instances: &'a mut [&'a mut T], + ) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'a)>>; + + async fn multi_insert_with<'a, I>( + instances: &'a mut [&'a mut T], + input: I, + ) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'a)>> + where + I: DbConnection + Send + 'a; async fn update(&self) -> Result<(), Box>; diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index a0b361a9..55dfbe4a 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -293,9 +293,6 @@ fn impl_crud_operations_trait_for_struct( // The insert impl #insert_tokens - // // The insert of multiple entities impl - // #_insert_multi_tokens - // The update impl #update_tokens diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 214d1232..47caeebd 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -1,12 +1,14 @@ use proc_macro2::TokenStream; use quote::quote; - +use canyon_entities::manager_builder::generate_user_struct; use crate::utils::macro_tokens::MacroTokens; /// Generates the TokenStream for the _insert_result() CRUD operation pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &String) -> TokenStream { + let mut insert_ops_tokens = TokenStream::new(); + let ty = macro_data.ty; - + // Retrieves the fields of the Struct as a collection of Strings, already parsed // the condition of remove the primary key if it's present and it's autoincremental let insert_columns = macro_data.get_column_names_pk_parsed().join(", "); @@ -105,7 +107,7 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri } }; - quote! { + insert_ops_tokens.extend(quote! { /// Inserts into a database entity the current data in `self`, generating a new /// entry (row), returning the `PRIMARY KEY` = `self.` with the specified /// datasource by its `datasource name`, defined in the configuration file. @@ -199,323 +201,330 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri #insert_transaction } + }); + + let multi_insert_tokens = generate_multiple_insert_tokens(macro_data, table_schema_data); + insert_ops_tokens.extend(multi_insert_tokens); + + insert_ops_tokens +} + +/// Generates the TokenStream for the __insert() CRUD operation, but being available +/// as a [`QueryBuilder`] object, and instead of being a method over some [`T`] type, +/// as an associated function for [`T`] +/// +/// This, also lets the user have the option to be able to insert multiple +/// [`T`] objects in only one query +fn generate_multiple_insert_tokens( + macro_data: &MacroTokens, + table_schema_data: &String, +) -> TokenStream { + let ty = macro_data.ty; + + // Retrieves the fields of the Struct as continuous String + let column_names = macro_data.get_struct_fields_as_strings(); + + // Retrieves the fields of the Struct + let fields = macro_data.get_struct_fields(); + + let macro_fields = fields.iter().map(|field| quote! { &instance.#field }); + let macro_fields_cloned = macro_fields.clone(); + + let pk = macro_data.get_primary_key_annotation().unwrap_or_default(); + + let pk_ident_type = macro_data + ._fields_with_types() + .into_iter() + .find(|(i, _t)| *i == pk); + + let multi_insert_transaction = if let Some(pk_data) = &pk_ident_type { + let pk_ident = &pk_data.0; + let pk_type = &pk_data.1; + + quote! { + mapped_fields = #column_names + .split(", ") + .map( |column_name| format!("\"{}\"", column_name)) + .collect::>() + .join(", "); + + let mut split = mapped_fields.split(", ") + .collect::>(); + + let pk_value_index = split.iter() + .position(|pk| *pk == format!("\"{}\"", #pk).as_str()) + .expect("Error. No primary key found when should be there"); + split.retain(|pk| *pk != format!("\"{}\"", #pk).as_str()); + mapped_fields = split.join(", ").to_string(); + + let mut fields_placeholders = String::new(); + + let mut elements_counter = 0; + let mut values_counter = 1; + let values_arr_len = final_values.len(); + + for vector in final_values.iter_mut() { + let mut inner_counter = 0; + fields_placeholders.push('('); + vector.remove(pk_value_index); + + for _value in vector.iter() { + if inner_counter < vector.len() - 1 { + fields_placeholders.push_str(&("$".to_owned() + &values_counter.to_string() + ",")); + } else { + fields_placeholders.push_str(&("$".to_owned() + &values_counter.to_string())); + } + + inner_counter += 1; + values_counter += 1; + } + + elements_counter += 1; + + if elements_counter < values_arr_len { + fields_placeholders.push_str("), "); + } else { + fields_placeholders.push(')'); + } + } + + let stmt = format!( + "INSERT INTO {} ({}) VALUES {} RETURNING {}", + #table_schema_data, + mapped_fields, + fields_placeholders, + #pk + ); + + let mut v_arr = Vec::new(); + for arr in final_values.iter() { + for value in arr { + v_arr.push(*value) + } + } + + let multi_insert_result = <#ty as canyon_sql::core::Transaction<#ty>>::query( + stmt, + v_arr, + input + ).await?; + + match multi_insert_result { + #[cfg(feature="postgres")] + canyon_sql::core::CanyonRows::Postgres(mut v) => { + for (idx, instance) in instances.iter_mut().enumerate() { + instance.#pk_ident = v + .get(idx) + .expect("Failed getting the returned IDs for a multi insert") + .get::<&str, #pk_type>(#pk); + } + + Ok(()) + }, + #[cfg(feature="mssql")] + canyon_sql::core::CanyonRows::Tiberius(mut v) => { + for (idx, instance) in instances.iter_mut().enumerate() { + instance.#pk_ident = v + .get(idx) + .expect("Failed getting the returned IDs for a multi insert") + .get::<#pk_type, &str>(#pk) + .expect("SQL Server primary key type failed to be set as value"); + } + + Ok(()) + }, + #[cfg(feature="mysql")] + canyon_sql::core::CanyonRows::MySQL(mut v) => { + for (idx, instance) in instances.iter_mut().enumerate() { + instance.#pk_ident = v + .get(idx) + .expect("Failed getting the returned IDs for a multi insert") + .get::<#pk_type,usize>(0) + .expect("MYSQL primary key type failed to be set as value"); + } + Ok(()) + }, + _ => panic!() // TODO remove when the generics will be refactored + } + } + } else { + quote! { + mapped_fields = #column_names + .split(", ") + .map( |column_name| format!("\"{}\"", column_name)) + .collect::>() + .join(", "); + + let mut split = mapped_fields.split(", ") + .collect::>(); + + let mut fields_placeholders = String::new(); + + let mut elements_counter = 0; + let mut values_counter = 1; + let values_arr_len = final_values.len(); + + for vector in final_values.iter_mut() { + let mut inner_counter = 0; + fields_placeholders.push('('); + + for _value in vector.iter() { + if inner_counter < vector.len() - 1 { + fields_placeholders.push_str(&("$".to_owned() + &values_counter.to_string() + ",")); + } else { + fields_placeholders.push_str(&("$".to_owned() + &values_counter.to_string())); + } + + inner_counter += 1; + values_counter += 1; + } + + elements_counter += 1; + + if elements_counter < values_arr_len { + fields_placeholders.push_str("), "); + } else { + fields_placeholders.push(')'); + } + } + + let stmt = format!( + "INSERT INTO {} ({}) VALUES {}", + #table_schema_data, + mapped_fields, + fields_placeholders + ); + + let mut v_arr = Vec::new(); + for arr in final_values.iter() { + for value in arr { + v_arr.push(*value) + } + } + + <#ty as canyon_sql::core::Transaction<#ty>>::query( + stmt, + v_arr, + input + ).await?; + + Ok(()) + } + }; + + quote! { + /// Inserts multiple instances of some type `T` into its related table. + /// + /// ``` + /// let mut new_league = League { + /// id: Default::default(), + /// ext_id: 392489032, + /// slug: "League10".to_owned(), + /// name: "League10also".to_owned(), + /// region: "Turkey".to_owned(), + /// image_url: "https://www.sdklafjsd.com".to_owned() + /// }; + /// let mut new_league2 = League { + /// id: Default::default(), + /// ext_id: 392489032, + /// slug: "League11".to_owned(), + /// name: "League11also".to_owned(), + /// region: "LDASKJF".to_owned(), + /// image_url: "https://www.sdklafjsd.com".to_owned() + /// }; + /// let mut new_league3 = League { + /// id: Default::default(), + /// ext_id: 9687392489032, + /// slug: "League3".to_owned(), + /// name: "3League".to_owned(), + /// region: "EU".to_owned(), + /// image_url: "https://www.lag.com".to_owned() + ///}; + /// + /// League::insert_multiple( + /// &mut [&mut new_league, &mut new_league2, &mut new_league3] + /// ).await + ///.ok(); + /// ``` + async fn multi_insert<'a>(instances: &'a mut [&'a mut #ty]) -> ( + Result<(), Box> + ) { + use canyon_sql::core::QueryParameter; + let input = ""; + + let mut final_values: Vec>> = Vec::new(); + for instance in instances.iter() { + let intermediate: &[&dyn QueryParameter<'_>] = &[#(#macro_fields),*]; + + let mut longer_lived: Vec<&dyn QueryParameter<'_>> = Vec::new(); + for value in intermediate.into_iter() { + longer_lived.push(*value) + } + + final_values.push(longer_lived) + } + + let mut mapped_fields: String = String::new(); + + #multi_insert_transaction + } + + /// Inserts multiple instances of some type `T` into its related table with the specified + /// datasource by its `datasource name`, defined in the configuration file. + /// + /// ``` + /// let mut new_league = League { + /// id: Default::default(), + /// ext_id: 392489032, + /// slug: "League10".to_owned(), + /// name: "League10also".to_owned(), + /// region: "Turkey".to_owned(), + /// image_url: "https://www.sdklafjsd.com".to_owned() + /// }; + /// let mut new_league2 = League { + /// id: Default::default(), + /// ext_id: 392489032, + /// slug: "League11".to_owned(), + /// name: "League11also".to_owned(), + /// region: "LDASKJF".to_owned(), + /// image_url: "https://www.sdklafjsd.com".to_owned() + /// }; + /// let mut new_league3 = League { + /// id: Default::default(), + /// ext_id: 9687392489032, + /// slug: "League3".to_owned(), + /// name: "3League".to_owned(), + /// region: "EU".to_owned(), + /// image_url: "https://www.lag.com".to_owned() + /// }; + /// + /// League::insert_multiple( + /// &mut [&mut new_league, &mut new_league2, &mut new_league3] + /// ).await + /// .ok(); + /// ``` + async fn multi_insert_with<'a, I>(instances: &'a mut [&'a mut #ty], input: I) -> + Result<(), Box> + where + I: canyon_sql::core::DbConnection + Send + 'a + { + use canyon_sql::core::QueryParameter; + + let mut final_values: Vec>> = Vec::new(); + for instance in instances.iter() { + let intermediate: &[&dyn QueryParameter<'_>] = &[#(#macro_fields_cloned),*]; + + let mut longer_lived: Vec<&dyn QueryParameter<'_>> = Vec::new(); + for value in intermediate.into_iter() { + longer_lived.push(*value) + } + + final_values.push(longer_lived) + } + + let mut mapped_fields: String = String::new(); + + #multi_insert_transaction + } } } -// -// /// Generates the TokenStream for the __insert() CRUD operation, but being available -// /// as a [`QueryBuilder`] object, and instead of being a method over some [`T`] type, -// /// as an associated function for [`T`] -// /// -// /// This, also lets the user to have the option to be able to insert multiple -// /// [`T`] objects in only one query -// pub fn generate_multiple_insert_tokens( -// macro_data: &MacroTokens, -// table_schema_data: &String, -// ) -> TokenStream { -// let ty = macro_data.ty; -// -// // Retrieves the fields of the Struct as continuous String -// let column_names = macro_data.get_struct_fields_as_strings(); -// -// // Retrieves the fields of the Struct -// let fields = macro_data.get_struct_fields(); -// -// let macro_fields = fields.iter().map(|field| quote! { &instance.#field }); -// let macro_fields_cloned = macro_fields.clone(); -// -// let pk = macro_data.get_primary_key_annotation().unwrap_or_default(); -// -// let pk_ident_type = macro_data -// ._fields_with_types() -// .into_iter() -// .find(|(i, _t)| *i == pk); -// -// let multi_insert_transaction = if let Some(pk_data) = &pk_ident_type { -// let pk_ident = &pk_data.0; -// let pk_type = &pk_data.1; -// -// quote! { -// mapped_fields = #column_names -// .split(", ") -// .map( |column_name| format!("\"{}\"", column_name)) -// .collect::>() -// .join(", "); -// -// let mut split = mapped_fields.split(", ") -// .collect::>(); -// -// let pk_value_index = split.iter() -// .position(|pk| *pk == format!("\"{}\"", #pk).as_str()) -// .expect("Error. No primary key found when should be there"); -// split.retain(|pk| *pk != format!("\"{}\"", #pk).as_str()); -// mapped_fields = split.join(", ").to_string(); -// -// let mut fields_placeholders = String::new(); -// -// let mut elements_counter = 0; -// let mut values_counter = 1; -// let values_arr_len = final_values.len(); -// -// for vector in final_values.iter_mut() { -// let mut inner_counter = 0; -// fields_placeholders.push('('); -// vector.remove(pk_value_index); -// -// for _value in vector.iter() { -// if inner_counter < vector.len() - 1 { -// fields_placeholders.push_str(&("$".to_owned() + &values_counter.to_string() + ",")); -// } else { -// fields_placeholders.push_str(&("$".to_owned() + &values_counter.to_string())); -// } -// -// inner_counter += 1; -// values_counter += 1; -// } -// -// elements_counter += 1; -// -// if elements_counter < values_arr_len { -// fields_placeholders.push_str("), "); -// } else { -// fields_placeholders.push(')'); -// } -// } -// -// let stmt = format!( -// "INSERT INTO {} ({}) VALUES {} RETURNING {}", -// #table_schema_data, -// mapped_fields, -// fields_placeholders, -// #pk -// ); -// -// let mut v_arr = Vec::new(); -// for arr in final_values.iter() { -// for value in arr { -// v_arr.push(*value) -// } -// } -// -// let multi_insert_result = <#ty as canyon_sql::core::Transaction<#ty>>::query( -// stmt, -// v_arr, -// datasource_name -// ).await?; -// -// match multi_insert_result { -// #[cfg(feature="postgres")] -// canyon_sql::core::CanyonRows::Postgres(mut v) => { -// for (idx, instance) in instances.iter_mut().enumerate() { -// instance.#pk_ident = v -// .get(idx) -// .expect("Failed getting the returned IDs for a multi insert") -// .get::<&str, #pk_type>(#pk); -// } -// -// Ok(()) -// }, -// #[cfg(feature="mssql")] -// canyon_sql::core::CanyonRows::Tiberius(mut v) => { -// for (idx, instance) in instances.iter_mut().enumerate() { -// instance.#pk_ident = v -// .get(idx) -// .expect("Failed getting the returned IDs for a multi insert") -// .get::<#pk_type, &str>(#pk) -// .expect("SQL Server primary key type failed to be set as value"); -// } -// -// Ok(()) -// }, -// #[cfg(feature="mysql")] -// canyon_sql::core::CanyonRows::MySQL(mut v) => { -// for (idx, instance) in instances.iter_mut().enumerate() { -// instance.#pk_ident = v -// .get(idx) -// .expect("Failed getting the returned IDs for a multi insert") -// .get::<#pk_type,usize>(0) -// .expect("MYSQL primary key type failed to be set as value"); -// } -// Ok(()) -// }, -// _ => panic!() // TODO remove when the generics will be refactored -// } -// } -// } else { -// quote! { -// mapped_fields = #column_names -// .split(", ") -// .map( |column_name| format!("\"{}\"", column_name)) -// .collect::>() -// .join(", "); -// -// let mut split = mapped_fields.split(", ") -// .collect::>(); -// -// let mut fields_placeholders = String::new(); -// -// let mut elements_counter = 0; -// let mut values_counter = 1; -// let values_arr_len = final_values.len(); -// -// for vector in final_values.iter_mut() { -// let mut inner_counter = 0; -// fields_placeholders.push('('); -// -// for _value in vector.iter() { -// if inner_counter < vector.len() - 1 { -// fields_placeholders.push_str(&("$".to_owned() + &values_counter.to_string() + ",")); -// } else { -// fields_placeholders.push_str(&("$".to_owned() + &values_counter.to_string())); -// } -// -// inner_counter += 1; -// values_counter += 1; -// } -// -// elements_counter += 1; -// -// if elements_counter < values_arr_len { -// fields_placeholders.push_str("), "); -// } else { -// fields_placeholders.push(')'); -// } -// } -// -// let stmt = format!( -// "INSERT INTO {} ({}) VALUES {}", -// #table_schema_data, -// mapped_fields, -// fields_placeholders -// ); -// -// let mut v_arr = Vec::new(); -// for arr in final_values.iter() { -// for value in arr { -// v_arr.push(*value) -// } -// } -// -// <#ty as canyon_sql::core::Transaction<#ty>>::query( -// stmt, -// v_arr, -// datasource_name -// ).await?; -// -// Ok(()) -// } -// }; -// -// quote! { -// ///// Inserts multiple instances of some type `T` into its related table. -// ///// -// ///// ``` -// ///// let mut new_league = League { -// ///// id: Default::default(), -// ///// ext_id: 392489032, -// ///// slug: "League10".to_owned(), -// ///// name: "League10also".to_owned(), -// ///// region: "Turkey".to_owned(), -// ///// image_url: "https://www.sdklafjsd.com".to_owned() -// ///// }; -// ///// let mut new_league2 = League { -// ///// id: Default::default(), -// ///// ext_id: 392489032, -// ///// slug: "League11".to_owned(), -// ///// name: "League11also".to_owned(), -// ///// region: "LDASKJF".to_owned(), -// ///// image_url: "https://www.sdklafjsd.com".to_owned() -// ///// }; -// ///// let mut new_league3 = League { -// ///// id: Default::default(), -// ///// ext_id: 9687392489032, -// ///// slug: "League3".to_owned(), -// ///// name: "3League".to_owned(), -// ///// region: "EU".to_owned(), -// ///// image_url: "https://www.lag.com".to_owned() -// ///// }; -// ///// -// ///// League::insert_multiple( -// ///// &mut [&mut new_league, &mut new_league2, &mut new_league3] -// ///// ).await -// ///// .ok(); -// ///// ``` -// //// async fn multi_insert<'a>(instances: &'a mut [&'a mut #ty]) -> ( -// //// Result<(), Box> -// //// ) { -// //// use canyon_sql::core::QueryParameter; -// //// let datasource_name = ""; -// -// //// let mut final_values: Vec>> = Vec::new(); -// //// for instance in instances.iter() { -// //// let intermediate: &[&dyn QueryParameter<'_>] = &[#(#macro_fields),*]; -// -// //// let mut longer_lived: Vec<&dyn QueryParameter<'_>> = Vec::new(); -// //// for value in intermediate.into_iter() { -// //// longer_lived.push(*value) -// //// } -// -// //// final_values.push(longer_lived) -// //// } -// -// //// let mut mapped_fields: String = String::new(); -// -// //// #multi_insert_transaction -// //// } -// -// ///// Inserts multiple instances of some type `T` into its related table with the specified -// ///// datasource by it's `datasouce name`, defined in the configuration file. -// ///// -// ///// ``` -// ///// let mut new_league = League { -// ///// id: Default::default(), -// ///// ext_id: 392489032, -// ///// slug: "League10".to_owned(), -// ///// name: "League10also".to_owned(), -// ///// region: "Turkey".to_owned(), -// ///// image_url: "https://www.sdklafjsd.com".to_owned() -// ///// }; -// ///// let mut new_league2 = League { -// ///// id: Default::default(), -// ///// ext_id: 392489032, -// ///// slug: "League11".to_owned(), -// ///// name: "League11also".to_owned(), -// ///// region: "LDASKJF".to_owned(), -// ///// image_url: "https://www.sdklafjsd.com".to_owned() -// ///// }; -// ///// let mut new_league3 = League { -// ///// id: Default::default(), -// ///// ext_id: 9687392489032, -// ///// slug: "League3".to_owned(), -// ///// name: "3League".to_owned(), -// ///// region: "EU".to_owned(), -// ///// image_url: "https://www.lag.com".to_owned() -// ///// }; -// ///// -// ///// League::insert_multiple( -// ///// &mut [&mut new_league, &mut new_league2, &mut new_league3] -// ///// ).await -// ///// .ok(); -// ///// ``` -// // async fn multi_insert_datasource<'a>(instances: &'a mut [&'a mut #ty], datasource_name: &'a str) -> ( -// // Result<(), Box> -// // ) { -// // use canyon_sql::core::QueryParameter; -// -// // let mut final_values: Vec>> = Vec::new(); -// // for instance in instances.iter() { -// // let intermediate: &[&dyn QueryParameter<'_>] = &[#(#macro_fields_cloned),*]; -// -// // let mut longer_lived: Vec<&dyn QueryParameter<'_>> = Vec::new(); -// // for value in intermediate.into_iter() { -// // longer_lived.push(*value) -// // } -// -// // final_values.push(longer_lived) -// // } -// -// // let mut mapped_fields: String = String::new(); -// -// // #multi_insert_transaction -// // } -// } -// } diff --git a/tests/crud/init_mssql.rs b/tests/crud/init_mssql.rs index 426fb0d5..2f4f43a9 100644 --- a/tests/crud/init_mssql.rs +++ b/tests/crud/init_mssql.rs @@ -1,62 +1,62 @@ -// use crate::constants::SQL_SERVER_CREATE_TABLES; -// use crate::constants::SQL_SERVER_DS; -// use crate::constants::SQL_SERVER_FILL_TABLE_VALUES; -// use crate::tests_models::league::League; -// -// use canyon_sql::crud::CrudOperations; -// use canyon_sql::db_clients::tiberius::{Client, Config}; -// use canyon_sql::runtime::tokio::net::TcpStream; -// use canyon_sql::runtime::tokio_util::compat::TokioAsyncWriteCompatExt; -// -// /// In order to initialize data on `SqlServer`. we must manually insert it -// /// when the docker starts. SqlServer official docker from Microsoft does -// /// not allow you to run `.sql` files against the database (not at least, without) -// /// using a workaround. So, we are going to query the `SqlServer` to check if already -// /// has some data (other processes, persistence or multi-threading envs), af if not, -// /// we are going to retrieve the inserted data on the `postgreSQL` at start-up and -// /// inserting into the `SqlServer` instance. -// /// -// /// This will be marked as `#[ignore]`, so we can force to run first the marked as -// /// ignored, check the data available, perform the necessary init operations and -// /// then *cargo test * the real integration tests -// #[canyon_sql::macros::canyon_tokio_test] -// #[ignore] -// fn initialize_sql_server_docker_instance() { -// static CONN_STR: &str = // TODO: change this for the DS when will be in the public API -// "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true"; -// -// canyon_sql::runtime::futures::executor::block_on(async { -// let config = Config::from_ado_string(CONN_STR).unwrap(); -// -// let tcp = TcpStream::connect(config.get_addr()).await.unwrap(); -// let tcp2 = TcpStream::connect(config.get_addr()).await.unwrap(); -// tcp.set_nodelay(true).ok(); -// -// let mut client = Client::connect(config.clone(), tcp.compat_write()) -// .await -// .unwrap(); -// -// // Create the tables -// let query_result = client.query(SQL_SERVER_CREATE_TABLES, &[]).await; -// assert!(query_result.is_ok()); -// -// let leagues_sql = League::find_all_with(SQL_SERVER_DS).await; -// println!("LSQL ERR: {leagues_sql:?}"); -// assert!(leagues_sql.is_ok()); -// -// match leagues_sql { -// Ok(ref leagues) => { -// let leagues_len = leagues.len(); -// println!("Leagues already inserted on SQLSERVER: {:?}", &leagues_len); -// if leagues.len() < 10 { -// let mut client2 = Client::connect(config, tcp2.compat_write()) -// .await -// .expect("Can't connect to MSSQL"); -// let result = client2.query(SQL_SERVER_FILL_TABLE_VALUES, &[]).await; -// assert!(result.is_ok()); -// } -// } -// Err(e) => eprintln!("Error retrieving the leagues: {e}"), -// } -// }); -// } +use crate::constants::SQL_SERVER_CREATE_TABLES; +use crate::constants::SQL_SERVER_DS; +use crate::constants::SQL_SERVER_FILL_TABLE_VALUES; +use crate::tests_models::league::League; + +use canyon_sql::crud::CrudOperations; +use canyon_sql::db_clients::tiberius::{Client, Config}; +use canyon_sql::runtime::tokio::net::TcpStream; +use canyon_sql::runtime::tokio_util::compat::TokioAsyncWriteCompatExt; + +/// In order to initialize data on `SqlServer`. we must manually insert it +/// when the docker starts. SqlServer official docker from Microsoft does +/// not allow you to run `.sql` files against the database (not at least, without) +/// using a workaround. So, we are going to query the `SqlServer` to check if already +/// has some data (other processes, persistence or multi-threading envs), af if not, +/// we are going to retrieve the inserted data on the `postgreSQL` at start-up and +/// inserting into the `SqlServer` instance. +/// +/// This will be marked as `#[ignore]`, so we can force to run first the marked as +/// ignored, check the data available, perform the necessary init operations and +/// then *cargo test * the real integration tests +#[canyon_sql::macros::canyon_tokio_test] +#[ignore] +fn initialize_sql_server_docker_instance() { + static CONN_STR: &str = // TODO: change this for the DS when will be in the public API + "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true"; + + canyon_sql::runtime::futures::executor::block_on(async { + let config = Config::from_ado_string(CONN_STR).unwrap(); + + let tcp = TcpStream::connect(config.get_addr()).await.unwrap(); + let tcp2 = TcpStream::connect(config.get_addr()).await.unwrap(); + tcp.set_nodelay(true).ok(); + + let mut client = Client::connect(config.clone(), tcp.compat_write()) + .await + .unwrap(); + + // Create the tables + let query_result = client.query(SQL_SERVER_CREATE_TABLES, &[]).await; + assert!(query_result.is_ok()); + + let leagues_sql = League::find_all_with(SQL_SERVER_DS).await; + println!("LSQL ERR: {leagues_sql:?}"); + assert!(leagues_sql.is_ok()); + + match leagues_sql { + Ok(ref leagues) => { + let leagues_len = leagues.len(); + println!("Leagues already inserted on SQLSERVER: {:?}", &leagues_len); + if leagues.len() < 10 { + let mut client2 = Client::connect(config, tcp2.compat_write()) + .await + .expect("Can't connect to MSSQL"); + let result = client2.query(SQL_SERVER_FILL_TABLE_VALUES, &[]).await; + assert!(result.is_ok()); + } + } + Err(e) => eprintln!("Error retrieving the leagues: {e}"), + } + }); +} diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index 8facb6c7..10f0657b 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -126,7 +126,7 @@ fn test_crud_find_with_querybuilder_and_leftlike() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_leftlike_with_mssql() { // Find all the leagues whose name ends with "CK" - let mut filtered_leagues_result = League::select_query() + let filtered_leagues_result = League::select_query() .r#where(LeagueFieldValue::name(&"CK"), Like::Left); assert_eq!( @@ -228,7 +228,7 @@ fn test_crud_find_with_querybuilder_with_mysql() { fn test_crud_update_with_querybuilder() { // Find all the leagues with ID less or equals that 7 // and where it's region column value is equals to 'Korea' - let mut q = League::update_query() + let q = League::update_query() .set(&[ (LeagueField::slug, "Updated with the QueryBuilder"), (LeagueField::name, "Random"), @@ -262,7 +262,7 @@ fn test_crud_update_with_querybuilder() { fn test_crud_update_with_querybuilder_with_mssql() { // Find all the leagues with ID less or equals that 7 // and where it's region column value is equals to 'Korea' - let mut q = Player::update_query_with(SQL_SERVER_DS); + let q = Player::update_query_with(SQL_SERVER_DS); q.set(&[ (PlayerField::summoner_name, "Random updated player name"), (PlayerField::first_name, "I am an updated first name"), @@ -293,7 +293,7 @@ fn test_crud_update_with_querybuilder_with_mysql() { // Find all the leagues with ID less or equals that 7 // and where it's region column value is equals to 'Korea' - let mut q = Player::update_query_with(MYSQL_DS); + let q = Player::update_query_with(MYSQL_DS); q.set(&[ (PlayerField::summoner_name, "Random updated player name"), (PlayerField::first_name, "I am an updated first name"), @@ -378,7 +378,7 @@ fn test_crud_delete_with_querybuilder_with_mysql() { /// WHERE clause #[canyon_sql::macros::canyon_tokio_test] fn test_where_clause() { - let mut l = League::select_query() + let l = League::select_query() .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq); assert_eq!(l.read_sql(), "SELECT * FROM league WHERE name = $1") @@ -388,7 +388,7 @@ fn test_where_clause() { /// AND clause #[canyon_sql::macros::canyon_tokio_test] fn test_and_clause() { - let mut l = League::select_query() + let l = League::select_query() .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) .and(LeagueFieldValue::id(&10), Comp::LtEq); @@ -402,7 +402,7 @@ fn test_and_clause() { /// AND clause #[canyon_sql::macros::canyon_tokio_test] fn test_and_clause_with_in_constraint() { - let mut l = League::select_query() + let l = League::select_query() .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) .and_values_in(LeagueField::id, &[1, 7, 10]); @@ -416,7 +416,7 @@ fn test_and_clause_with_in_constraint() { /// AND clause #[canyon_sql::macros::canyon_tokio_test] fn test_or_clause() { - let mut l = League::select_query() + let l = League::select_query() .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) .or(LeagueFieldValue::id(&10), Comp::LtEq); @@ -430,7 +430,7 @@ fn test_or_clause() { /// AND clause #[canyon_sql::macros::canyon_tokio_test] fn test_or_clause_with_in_constraint() { - let mut l = League::select_query() + let l = League::select_query() .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) .or_values_in(LeagueField::id, &[1, 7, 10]); @@ -444,7 +444,7 @@ fn test_or_clause_with_in_constraint() { /// AND clause #[canyon_sql::macros::canyon_tokio_test] fn test_order_by_clause() { - let mut l = League::select_query() + let l = League::select_query() .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) .order_by(LeagueField::id, false); From a19bc4adf47600cfa1a4e061b993b8bed5917935 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 28 Jan 2025 09:30:20 +0100 Subject: [PATCH 053/155] feat: re-enabled the foreign key operations --- canyon_macros/src/lib.rs | 76 ++-- .../src/query_operations/foreign_key.rs | 366 +++++++++--------- tests/crud/foreign_key_operations.rs | 326 ++++++++-------- 3 files changed, 391 insertions(+), 377 deletions(-) diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index 55dfbe4a..f55bdb5c 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -22,13 +22,15 @@ use syn::{DeriveInput, Fields, Type, Visibility}; use query_operations::{ select::{ generate_read_operations_tokens, - generate_find_all_query_tokens, - // generate_find_by_foreign_key_tokens, - // generate_find_by_reverse_foreign_key_tokens, + generate_find_all_query_tokens }, insert::generate_insert_tokens, update::generate_update_tokens, delete::generate_delete_tokens, + foreign_key::{ + generate_find_by_foreign_key_tokens, + generate_find_by_reverse_foreign_key_tokens, + } }; use utils::{function_parser::FunctionParser, helpers, macro_tokens::MacroTokens}; @@ -263,19 +265,19 @@ fn impl_crud_operations_trait_for_struct( // Builds the delete() queries let delete_tokens = generate_delete_tokens(macro_data, &table_schema_data); - // // Search by foreign (d) key as Vec, cause Canyon supports multiple fields having FK annotation - // let _search_by_fk_tokens: Vec<(TokenStream, TokenStream)> = - // generate_find_by_foreign_key_tokens(macro_data); - // let fk_method_signatures = _search_by_fk_tokens.iter().map(|(sign, _)| sign); - // let fk_method_implementations = _search_by_fk_tokens.iter().map(|(_, m_impl)| m_impl); + // Search by foreign (d) key as Vec, cause Canyon supports multiple fields having FK annotation + let _search_by_fk_tokens: Vec<(TokenStream, TokenStream)> = + generate_find_by_foreign_key_tokens(macro_data); + let fk_method_signatures = _search_by_fk_tokens.iter().map(|(sign, _)| sign); + let fk_method_implementations = _search_by_fk_tokens.iter().map(|(_, m_impl)| m_impl); - // // The tokens for generating the methods that enable Canyon to retrieve the child entities that are of T type - // // given a parent entity U: ForeignKeyable, as an associated function for the child type (T) - // let _search_by_revese_fk_tokens: Vec<(TokenStream, TokenStream)> = - // generate_find_by_reverse_foreign_key_tokens(macro_data, &table_schema_data); - // let rev_fk_method_signatures = _search_by_revese_fk_tokens.iter().map(|(sign, _)| sign); - // let rev_fk_method_implementations = - // _search_by_revese_fk_tokens.iter().map(|(_, m_impl)| m_impl); + // The tokens for generating the methods that enable Canyon to retrieve the child entities that are of T type + // given a parent entity U: ForeignKeyable, as an associated function for the child type (T) + let _search_by_revese_fk_tokens: Vec<(TokenStream, TokenStream)> = + generate_find_by_reverse_foreign_key_tokens(macro_data, &table_schema_data); + let rev_fk_method_signatures = _search_by_revese_fk_tokens.iter().map(|(sign, _)| sign); + let rev_fk_method_implementations = + _search_by_revese_fk_tokens.iter().map(|(_, m_impl)| m_impl); // The autogenerated name for the trait that holds the fk and rev fk searches let fk_trait_ident = Ident::new( @@ -302,37 +304,37 @@ fn impl_crud_operations_trait_for_struct( // let tokens = if !_search_by_fk_tokens.is_empty() { let a: Vec = vec![]; - let tokens = if a.is_empty() { + let tokens = if a.is_empty() { // TODO: push the tokens conditionally quote! { use canyon_sql::core::IntoResults; - #[canyon_sql::macros::async_trait] + #[canyon_sql::macros::async_trait] // TODO: get rid of the async_trait impl canyon_sql::crud::CrudOperations<#ty> for #ty { #crud_operations_tokens } impl canyon_sql::core::Transaction<#ty> for #ty {} - // /// Hidden trait for generate the foreign key operations available - // /// in Canyon without have to define them before hand in CrudOperations - // /// because it's just impossible with the actual system (where the methods - // /// are generated dynamically based on some properties of the `foreign_key` - // /// annotation) - // #[canyon_sql::macros::async_trait] - // pub trait #fk_trait_ident<#ty> { - // #(#fk_method_signatures)* - // #(#rev_fk_method_signatures)* - // } - // #[canyon_sql::macros::async_trait] - // impl #fk_trait_ident<#ty> for #ty - // where #ty: - // std::fmt::Debug + - // canyon_sql::crud::CrudOperations<#ty> + - // canyon_sql::core::RowMapper<#ty> - // { - // #(#fk_method_implementations)* - // #(#rev_fk_method_implementations)* - // } + /// Hidden trait for generate the foreign key operations available + /// in Canyon without have to define them beforehand in CrudOperations + /// because it's just impossible with the actual system (where the methods + /// are generated dynamically based on some properties of the `foreign_key` + /// annotation) + #[canyon_sql::macros::async_trait] + pub trait #fk_trait_ident<#ty> { + #(#fk_method_signatures)* + #(#rev_fk_method_signatures)* + } + #[canyon_sql::macros::async_trait] + impl #fk_trait_ident<#ty> for #ty + where #ty: + std::fmt::Debug + + canyon_sql::crud::CrudOperations<#ty> + + canyon_sql::core::RowMapper<#ty> + { + #(#fk_method_implementations)* + #(#rev_fk_method_implementations)* + } } } else { quote! { diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index 82491636..6d9530cc 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -1,179 +1,191 @@ -// /// Generates the TokenStream for build the search by foreign key feature, also as a method instance -// /// of a T type of as an associated function of same T type, but wrapped as a Result, representing -// /// a possible failure querying the database, a bad or missing FK annotation or a missed ForeignKeyable -// /// derive macro on the parent side of the relation -// pub fn generate_find_by_foreign_key_tokens( -// macro_data: &MacroTokens<'_>, -// ) -> Vec<(TokenStream, TokenStream)> { -// let mut fk_quotes: Vec<(TokenStream, TokenStream)> = Vec::new(); - -// for (field_ident, fk_annot) in macro_data.get_fk_annotations().iter() { -// if let EntityFieldAnnotation::ForeignKey(table, column) = fk_annot { -// let method_name = "search_".to_owned() + table; - -// // TODO this is not a good implementation. We must try to capture the -// // related entity in some way, and compare it with something else -// let fk_ty = database_table_name_to_struct_ident(table); - -// // Generate and identifier for the method based on the convention of "search_related_types" -// // where types is a placeholder for the plural name of the type referenced -// let method_name_ident = -// proc_macro2::Ident::new(&method_name, proc_macro2::Span::call_site()); -// let method_name_ident_with = proc_macro2::Ident::new( -// &format!("{}_with", &method_name), -// proc_macro2::Span::call_site(), -// ); -// let quoted_method_signature: TokenStream = quote! { -// async fn #method_name_ident(&self) -> -// Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> -// }; -// let quoted_with_method_signature: TokenStream = quote! { -// async fn #method_name_ident_with<'a>(&self, input: I) -> -// Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> -// }; - -// let stmt = format!( -// "SELECT * FROM {} WHERE {} = $1", -// table, -// format!("\"{column}\"").as_str(), -// ); -// let result_handler = quote! { -// match result { -// n if n.len() == 0 => Ok(None), -// _ => Ok(Some( -// result.into_results::<#fk_ty>().remove(0) -// )) -// } -// }; - -// fk_quotes.push(( -// quote! { #quoted_method_signature; }, -// quote! { -// /// Searches the parent entity (if exists) for this type -// #quoted_method_signature { -// let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( -// #stmt, -// &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], -// "" -// ).await?; - -// #result_handler -// } -// }, -// )); - -// fk_quotes.push(( -// quote! { #quoted_with_method_signature; }, -// quote! { -// /// Searches the parent entity (if exists) for this type with the specified datasource -// #quoted_with_method_signature { -// let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( -// #stmt, -// &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], -// datasource_name -// ).await?; - -// #result_handler -// } -// }, -// )); -// } -// } - -// fk_quotes +use proc_macro2::TokenStream; +use quote::quote; +use canyon_entities::field_annotation::EntityFieldAnnotation; +use crate::utils::helpers::database_table_name_to_struct_ident; +use crate::utils::macro_tokens::MacroTokens; + +// pub fn generate_find_by_fk_ops(macro_data: &MacroTokens<'_>, table_schema_data: &str) -> Vec<(TokenStream, TokenStream)> { +// let find_by_fk_ops_tokens = generate_find_by_foreign_key_tokens(macro_data); +// let find_by_reverse_fk_ops_tokens = generate_find_by_reverse_foreign_key_tokens(macro_data, table_schema_data); // } -// /// Generates the TokenStream for build the __search_by_foreign_key() CRUD -// /// associated function, but wrapped as a Result, representing -// /// a possible failure querying the database, a bad or missing FK annotation or a missed ForeignKeyable -// /// derive macro on the parent side of the relation -// pub fn generate_find_by_reverse_foreign_key_tokens( -// macro_data: &MacroTokens<'_>, -// table_schema_data: &String, -// ) -> Vec<(TokenStream, TokenStream)> { -// let mut rev_fk_quotes: Vec<(TokenStream, TokenStream)> = Vec::new(); -// let ty = macro_data.ty; - -// for (field_ident, fk_annot) in macro_data.get_fk_annotations().iter() { -// if let EntityFieldAnnotation::ForeignKey(table, column) = fk_annot { -// let method_name = format!("search_{table}_childrens"); - -// // Generate and identifier for the method based on the convention of "search_by__" (note the double underscore) -// // plus the 'table_name' property of the ForeignKey annotation -// let method_name_ident = -// proc_macro2::Ident::new(&method_name, proc_macro2::Span::call_site()); -// let method_name_ident_with = proc_macro2::Ident::new( -// &format!("{}_with", &method_name), -// proc_macro2::Span::call_site(), -// ); -// let quoted_method_signature: TokenStream = quote! { -// async fn #method_name_ident<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send>(value: &F) -> -// Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> -// }; -// let quoted_with_method_signature: TokenStream = quote! { -// async fn #method_name_ident_with<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send> -// (value: &F, input: I) -> -// Result, Box<(dyn std::error::Error + Send + Sync + 'static)>> -// }; - -// let f_ident = field_ident.to_string(); - -// rev_fk_quotes.push(( -// quote! { #quoted_method_signature; }, -// quote! { -// /// Given a parent entity T annotated with the derive proc macro `ForeignKeyable`, -// /// performns a search to find the children that belong to that concrete parent. -// #quoted_method_signature -// { -// let lookage_value = value.get_fk_column(#column) -// .expect(format!( -// "Column: {:?} not found in type: {:?}", #column, #table -// ).as_str()); - -// let stmt = format!( -// "SELECT * FROM {} WHERE {} = $1", -// #table_schema_data, -// format!("\"{}\"", #f_ident).as_str() -// ); - -// Ok(<#ty as canyon_sql::core::Transaction<#ty>>::query( -// stmt, -// &[lookage_value], -// "" -// ).await?.into_results::<#ty>()) -// } -// }, -// )); - -// rev_fk_quotes.push(( -// quote! { #quoted_with_method_signature; }, -// quote! { -// /// Given a parent entity T annotated with the derive proc macro `ForeignKeyable`, -// /// performns a search to find the children that belong to that concrete parent -// /// with the specified datasource. -// #quoted_with_method_signature -// { -// let lookage_value = value.get_fk_column(#column) -// .expect(format!( -// "Column: {:?} not found in type: {:?}", #column, #table -// ).as_str()); - -// let stmt = format!( -// "SELECT * FROM {} WHERE {} = $1", -// #table_schema_data, -// format!("\"{}\"", #f_ident).as_str() -// ); - -// Ok(<#ty as canyon_sql::core::Transaction<#ty>>::query( -// stmt, -// &[lookage_value], -// datasource_name -// ).await?.into_results::<#ty>()) -// } -// }, -// )); -// } -// } - -// rev_fk_quotes -// } +/// Generates the TokenStream for build the search by foreign key feature, also as a method instance +/// of a T type of as an associated function of same T type, but wrapped as a Result, representing +/// a possible failure querying the database, a bad or missing FK annotation or a missed ForeignKeyable +/// derive macro on the parent side of the relation +pub fn generate_find_by_foreign_key_tokens( + macro_data: &MacroTokens<'_>, +) -> Vec<(TokenStream, TokenStream)> { + let mut fk_quotes: Vec<(TokenStream, TokenStream)> = Vec::new(); + + for (field_ident, fk_annot) in macro_data.get_fk_annotations().iter() { + if let EntityFieldAnnotation::ForeignKey(table, column) = fk_annot { + let method_name = "search_".to_owned() + table; + + // TODO this is not a good implementation. We must try to capture the + // related entity in some way, and compare it with something else + let fk_ty = database_table_name_to_struct_ident(table); + + // Generate and identifier for the method based on the convention of "search_related_types" + // where types is a placeholder for the plural name of the type referenced + let method_name_ident = + proc_macro2::Ident::new(&method_name, proc_macro2::Span::call_site()); + let method_name_ident_with = proc_macro2::Ident::new( + &format!("{}_with", &method_name), + proc_macro2::Span::call_site(), + ); + let quoted_method_signature: TokenStream = quote! { + async fn #method_name_ident<'a>(&self) -> + Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + }; + let quoted_with_method_signature: TokenStream = quote! { + async fn #method_name_ident_with<'a, I>(&self, input: I) -> + Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + where I: canyon_sql::core::DbConnection + Send + 'a + }; + + let stmt = format!( + "SELECT * FROM {} WHERE {} = $1", + table, + format!("\"{column}\"").as_str(), + ); + let result_handler = quote! { + match result { + n if n.len() == 0 => Ok(None), + _ => Ok(Some( + result.into_results::<#fk_ty>().remove(0) + )) + } + }; + + fk_quotes.push(( + quote! { #quoted_method_signature; }, + quote! { + /// Searches the parent entity (if exists) for this type + #quoted_method_signature { + let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( + #stmt, + &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], + "" + ).await?; + + #result_handler + } + }, + )); + + fk_quotes.push(( + quote! { #quoted_with_method_signature; }, + quote! { + /// Searches the parent entity (if exists) for this type with the specified datasource + #quoted_with_method_signature { + let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( + #stmt, + &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], + input + ).await?; + + #result_handler + } + }, + )); + } + } + + fk_quotes +} + +/// Generates the TokenStream for build the __search_by_foreign_key() CRUD +/// associated function, but wrapped as a Result, representing +/// a possible failure querying the database, a bad or missing FK annotation or a missed ForeignKeyable +/// derive macro on the parent side of the relation +pub fn generate_find_by_reverse_foreign_key_tokens( + macro_data: &MacroTokens<'_>, + table_schema_data: &str, +) -> Vec<(TokenStream, TokenStream)> { + let mut rev_fk_quotes: Vec<(TokenStream, TokenStream)> = Vec::new(); + let ty = macro_data.ty; + + for (field_ident, fk_annot) in macro_data.get_fk_annotations().iter() { + if let EntityFieldAnnotation::ForeignKey(table, column) = fk_annot { + let method_name = format!("search_{table}_childrens"); + + // Generate and identifier for the method based on the convention of "search_by__" (note the double underscore) + // plus the 'table_name' property of the ForeignKey annotation + let method_name_ident = + proc_macro2::Ident::new(&method_name, proc_macro2::Span::call_site()); + let method_name_ident_with = proc_macro2::Ident::new( + &format!("{}_with", &method_name), + proc_macro2::Span::call_site(), + ); + let quoted_method_signature: TokenStream = quote! { + async fn #method_name_ident<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send>(value: &F) -> + Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + }; + let quoted_with_method_signature: TokenStream = quote! { + async fn #method_name_ident_with<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send, I> + (value: &F, input: I) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + where I: canyon_sql::core::DbConnection + Send + 'a + }; + + let f_ident = field_ident.to_string(); + + rev_fk_quotes.push(( + quote! { #quoted_method_signature; }, + quote! { + /// Given a parent entity T annotated with the derive proc macro `ForeignKeyable`, + /// performs a search to find the children that belong to that concrete parent. + #quoted_method_signature + { + let lookage_value = value.get_fk_column(#column) + .expect(format!( + "Column: {:?} not found in type: {:?}", #column, #table + ).as_str()); + + let stmt = format!( + "SELECT * FROM {} WHERE {} = $1", + #table_schema_data, + format!("\"{}\"", #f_ident).as_str() + ); + + Ok(<#ty as canyon_sql::core::Transaction<#ty>>::query( + stmt, + &[lookage_value], + "" + ).await?.into_results::<#ty>()) + } + }, + )); + + rev_fk_quotes.push(( + quote! { #quoted_with_method_signature; }, + quote! { + /// Given a parent entity T annotated with the derive proc macro `ForeignKeyable`, + /// performns a search to find the children that belong to that concrete parent + /// with the specified datasource. + #quoted_with_method_signature + { + let lookage_value = value.get_fk_column(#column) + .expect(format!( + "Column: {:?} not found in type: {:?}", #column, #table + ).as_str()); + + let stmt = format!( + "SELECT * FROM {} WHERE {} = $1", + #table_schema_data, + format!("\"{}\"", #f_ident).as_str() + ); + + Ok(<#ty as canyon_sql::core::Transaction<#ty>>::query( + stmt, + &[lookage_value], + input + ).await?.into_results::<#ty>()) + } + }, + )); + } + } + + rev_fk_quotes +} diff --git a/tests/crud/foreign_key_operations.rs b/tests/crud/foreign_key_operations.rs index 52f81288..00f153e3 100644 --- a/tests/crud/foreign_key_operations.rs +++ b/tests/crud/foreign_key_operations.rs @@ -1,163 +1,163 @@ -// /// Integration tests for the CRUD operations available in `Canyon` that -// /// generates and executes *SELECT* statements based on a entity -// /// annotated with the `#[foreign_key(... args)]` annotation looking -// /// for the related data with some entity `U` that acts as is parent, where `U` -// /// impls `ForeignKeyable` (isn't required, but it won't unlock the -// /// reverse search features parent -> child, only the child -> parent ones). -// /// -// /// Names of the foreign key methods are autogenerated for the direct and -// /// reverse side of the implementations. -// /// For more info: TODO -> Link to the docs of the foreign key chapter -// use canyon_sql::crud::CrudOperations; - -// #[cfg(feature = "mssql")] -// use crate::constants::MYSQL_DS; -// #[cfg(feature = "mssql")] -// use crate::constants::SQL_SERVER_DS; - -// use crate::tests_models::league::*; -// use crate::tests_models::tournament::*; - -// /// Given an entity `T` which has some field declaring a foreign key relation -// /// with some another entity `U`, for example, performs a search to find -// /// what is the parent type `U` of `T` -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_by_foreign_key() { -// let some_tournament: Tournament = Tournament::find_by_pk(&1) -// .await -// .expect("Result variant of the query is err") -// .expect("No result found for the given parameter"); - -// // We can get the parent entity for the retrieved child instance -// let parent_entity: Option = some_tournament -// .search_league() -// .await -// .expect("Result variant of the query is err"); - -// if let Some(league) = parent_entity { -// assert_eq!(some_tournament.league, league.id) -// } else { -// assert_eq!(parent_entity, None) -// } -// } - -// /// Same as the search by foreign key, but with the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_by_foreign_key_with_mssql() { -// let some_tournament: Tournament = Tournament::find_by_pk_with(&10, SQL_SERVER_DS) -// .await -// .expect("Result variant of the query is err") -// .expect("No result found for the given parameter"); - -// // We can get the parent entity for the retrieved child instance -// let parent_entity: Option = some_tournament -// .search_league_with(SQL_SERVER_DS) -// .await -// .expect("Result variant of the query is err"); - -// // These are tests, and we could unwrap the result contained in the option, because -// // it always should exist that search for the data inserted when the docker starts. -// // But, just for change the style a little bit and offer more options about how to -// // handle things done with Canyon -// if let Some(league) = parent_entity { -// assert_eq!(some_tournament.league, league.id) -// } else { -// assert_eq!(parent_entity, None) -// } -// } - -// /// Same as the search by foreign key, but with the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_by_foreign_key_with_mysql() { -// let some_tournament: Tournament = Tournament::find_by_pk_with(&10, MYSQL_DS) -// .await -// .expect("Result variant of the query is err") -// .expect("No result found for the given parameter"); - -// // We can get the parent entity for the retrieved child instance -// let parent_entity: Option = some_tournament -// .search_league_with(MYSQL_DS) -// .await -// .expect("Result variant of the query is err"); - -// // These are tests, and we could unwrap the result contained in the option, because -// // it always should exist that search for the data inserted when the docker starts. -// // But, just for change the style a little bit and offer more options about how to -// // handle things done with Canyon -// if let Some(league) = parent_entity { -// assert_eq!(some_tournament.league, league.id) -// } else { -// assert_eq!(parent_entity, None) -// } -// } - -// /// Given an entity `U` that is know as the "parent" side of the relation with another -// /// entity `T`, for example, we can ask to the parent for the childrens that belongs -// /// to `U`. -// /// -// /// For this to work, `U`, the parent, must have derived the `ForeignKeyable` proc macro -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_reverse_side_foreign_key() { -// let some_league: League = League::find_by_pk(&1) -// .await -// .expect("Result variant of the query is err") -// .expect("No result found for the given parameter"); - -// // Computes how many tournaments are pointing to the retrieved league -// let child_tournaments: Vec = Tournament::search_league_childrens(&some_league) -// .await -// .expect("Result variant of the query is err"); - -// assert!(!child_tournaments.is_empty()); -// child_tournaments -// .iter() -// .for_each(|t| assert_eq!(t.league, some_league.id)); -// } - -// /// Same as the search by the reverse side of a foreign key relation -// /// but with the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_reverse_side_foreign_key_with_mssql() { -// let some_league: League = League::find_by_pk_with(&1, SQL_SERVER_DS) -// .await -// .expect("Result variant of the query is err") -// .expect("No result found for the given parameter"); - -// // Computes how many tournaments are pointing to the retrieved league -// let child_tournaments: Vec = -// Tournament::search_league_childrens_with(&some_league, SQL_SERVER_DS) -// .await -// .expect("Result variant of the query is err"); - -// assert!(!child_tournaments.is_empty()); -// child_tournaments -// .iter() -// .for_each(|t| assert_eq!(t.league, some_league.id)); -// } - -// /// Same as the search by the reverse side of a foreign key relation -// /// but with the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_reverse_side_foreign_key_with_mysql() { -// let some_league: League = League::find_by_pk_with(&1, MYSQL_DS) -// .await -// .expect("Result variant of the query is err") -// .expect("No result found for the given parameter"); - -// // Computes how many tournaments are pointing to the retrieved league -// let child_tournaments: Vec = -// Tournament::search_league_childrens_with(&some_league, MYSQL_DS) -// .await -// .expect("Result variant of the query is err"); - -// assert!(!child_tournaments.is_empty()); -// child_tournaments -// .iter() -// .for_each(|t| assert_eq!(t.league, some_league.id)); -// } +/// Integration tests for the CRUD operations available in `Canyon` that +/// generates and executes *SELECT* statements based on a entity +/// annotated with the `#[foreign_key(... args)]` annotation looking +/// for the related data with some entity `U` that acts as is parent, where `U` +/// impls `ForeignKeyable` (isn't required, but it won't unlock the +/// reverse search features parent -> child, only the child -> parent ones). +/// +/// Names of the foreign key methods are autogenerated for the direct and +/// reverse side of the implementations. +/// For more info: TODO -> Link to the docs of the foreign key chapter +use canyon_sql::crud::CrudOperations; + +#[cfg(feature = "mssql")] +use crate::constants::MYSQL_DS; +#[cfg(feature = "mssql")] +use crate::constants::SQL_SERVER_DS; + +use crate::tests_models::league::*; +use crate::tests_models::tournament::*; + +/// Given an entity `T` which has some field declaring a foreign key relation +/// with some another entity `U`, for example, performs a search to find +/// what is the parent type `U` of `T` +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_search_by_foreign_key() { + let some_tournament: Tournament = Tournament::find_by_pk(&1) + .await + .expect("Result variant of the query is err") + .expect("No result found for the given parameter"); + + // We can get the parent entity for the retrieved child instance + let parent_entity: Option = some_tournament + .search_league() + .await + .expect("Result variant of the query is err"); + + if let Some(league) = parent_entity { + assert_eq!(some_tournament.league, league.id) + } else { + assert_eq!(parent_entity, None) + } +} + +/// Same as the search by foreign key, but with the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_search_by_foreign_key_with_mssql() { + let some_tournament: Tournament = Tournament::find_by_pk_with(&10, SQL_SERVER_DS) + .await + .expect("Result variant of the query is err") + .expect("No result found for the given parameter"); + + // We can get the parent entity for the retrieved child instance + let parent_entity: Option = some_tournament + .search_league_with(SQL_SERVER_DS) + .await + .expect("Result variant of the query is err"); + + // These are tests, and we could unwrap the result contained in the option, because + // it always should exist that search for the data inserted when the docker starts. + // But, just for change the style a little bit and offer more options about how to + // handle things done with Canyon + if let Some(league) = parent_entity { + assert_eq!(some_tournament.league, league.id) + } else { + assert_eq!(parent_entity, None) + } +} + +/// Same as the search by foreign key, but with the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_search_by_foreign_key_with_mysql() { + let some_tournament: Tournament = Tournament::find_by_pk_with(&10, MYSQL_DS) + .await + .expect("Result variant of the query is err") + .expect("No result found for the given parameter"); + + // We can get the parent entity for the retrieved child instance + let parent_entity: Option = some_tournament + .search_league_with(MYSQL_DS) + .await + .expect("Result variant of the query is err"); + + // These are tests, and we could unwrap the result contained in the option, because + // it always should exist that search for the data inserted when the docker starts. + // But, just for change the style a little bit and offer more options about how to + // handle things done with Canyon + if let Some(league) = parent_entity { + assert_eq!(some_tournament.league, league.id) + } else { + assert_eq!(parent_entity, None) + } +} + +/// Given an entity `U` that is know as the "parent" side of the relation with another +/// entity `T`, for example, we can ask to the parent for the childrens that belongs +/// to `U`. +/// +/// For this to work, `U`, the parent, must have derived the `ForeignKeyable` proc macro +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_search_reverse_side_foreign_key() { + let some_league: League = League::find_by_pk(&1) + .await + .expect("Result variant of the query is err") + .expect("No result found for the given parameter"); + + // Computes how many tournaments are pointing to the retrieved league + let child_tournaments: Vec = Tournament::search_league_childrens(&some_league) + .await + .expect("Result variant of the query is err"); + + assert!(!child_tournaments.is_empty()); + child_tournaments + .iter() + .for_each(|t| assert_eq!(t.league, some_league.id)); +} + +/// Same as the search by the reverse side of a foreign key relation +/// but with the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_search_reverse_side_foreign_key_with_mssql() { + let some_league: League = League::find_by_pk_with(&1, SQL_SERVER_DS) + .await + .expect("Result variant of the query is err") + .expect("No result found for the given parameter"); + + // Computes how many tournaments are pointing to the retrieved league + let child_tournaments: Vec = + Tournament::search_league_childrens_with(&some_league, SQL_SERVER_DS) + .await + .expect("Result variant of the query is err"); + + assert!(!child_tournaments.is_empty()); + child_tournaments + .iter() + .for_each(|t| assert_eq!(t.league, some_league.id)); +} + +/// Same as the search by the reverse side of a foreign key relation +/// but with the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_search_reverse_side_foreign_key_with_mysql() { + let some_league: League = League::find_by_pk_with(&1, MYSQL_DS) + .await + .expect("Result variant of the query is err") + .expect("No result found for the given parameter"); + + // Computes how many tournaments are pointing to the retrieved league + let child_tournaments: Vec = + Tournament::search_league_childrens_with(&some_league, MYSQL_DS) + .await + .expect("Result variant of the query is err"); + + assert!(!child_tournaments.is_empty()); + child_tournaments + .iter() + .for_each(|t| assert_eq!(t.league, some_league.id)); +} From e70a9cac688b470a9bfffc3c7ab4453eda26f8c7 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 28 Jan 2025 09:50:28 +0100 Subject: [PATCH 054/155] feat: proc-macro hygiene on the implementation of the crud operations --- canyon_macros/src/lib.rs | 102 +++--------------- .../src/query_operations/foreign_key.rs | 60 +++++++++-- 2 files changed, 68 insertions(+), 94 deletions(-) diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index f55bdb5c..c793a4ec 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -27,10 +27,7 @@ use query_operations::{ insert::generate_insert_tokens, update::generate_update_tokens, delete::generate_delete_tokens, - foreign_key::{ - generate_find_by_foreign_key_tokens, - generate_find_by_reverse_foreign_key_tokens, - } + foreign_key::generate_find_by_fk_ops }; use utils::{function_parser::FunctionParser, helpers, macro_tokens::MacroTokens}; @@ -249,107 +246,38 @@ fn impl_crud_operations_trait_for_struct( macro_data: &MacroTokens<'_>, table_schema_data: String, ) -> proc_macro::TokenStream { + let mut crud_ops_tokens = TokenStream::new(); let ty = macro_data.ty; let read_operations_tokens = generate_read_operations_tokens(macro_data, &table_schema_data); let find_all_query_tokens = generate_find_all_query_tokens(macro_data, &table_schema_data); - - // Builds the insert() query let insert_tokens = generate_insert_tokens(macro_data, &table_schema_data); - // Builds the insert_multi() query - // let _insert_multi_tokens = generate_multiple_insert_tokens(macro_data, &table_schema_data); - - // Builds the update() queries let update_tokens = generate_update_tokens(macro_data, &table_schema_data); - - // Builds the delete() queries let delete_tokens = generate_delete_tokens(macro_data, &table_schema_data); - // Search by foreign (d) key as Vec, cause Canyon supports multiple fields having FK annotation - let _search_by_fk_tokens: Vec<(TokenStream, TokenStream)> = - generate_find_by_foreign_key_tokens(macro_data); - let fk_method_signatures = _search_by_fk_tokens.iter().map(|(sign, _)| sign); - let fk_method_implementations = _search_by_fk_tokens.iter().map(|(_, m_impl)| m_impl); - - // The tokens for generating the methods that enable Canyon to retrieve the child entities that are of T type - // given a parent entity U: ForeignKeyable, as an associated function for the child type (T) - let _search_by_revese_fk_tokens: Vec<(TokenStream, TokenStream)> = - generate_find_by_reverse_foreign_key_tokens(macro_data, &table_schema_data); - let rev_fk_method_signatures = _search_by_revese_fk_tokens.iter().map(|(sign, _)| sign); - let rev_fk_method_implementations = - _search_by_revese_fk_tokens.iter().map(|(_, m_impl)| m_impl); - - // The autogenerated name for the trait that holds the fk and rev fk searches - let fk_trait_ident = Ident::new( - &format!("{}FkOperations", &ty.to_string()), - proc_macro2::Span::call_site(), - ); - - let crud_operations_tokens = quote! { - // The find_all_result impl // TODO: they must be wrapped into only four, C-R-U-D + let crud_operations_tokens = quote! { // TODO: bring this directly from mod.rs or query_operations? #read_operations_tokens - - // The SELECT_QUERYBUILDER impl #find_all_query_tokens - - // The insert impl #insert_tokens - - // The update impl #update_tokens - - // The delete impl #delete_tokens }; - // let tokens = if !_search_by_fk_tokens.is_empty() { - let a: Vec = vec![]; - let tokens = if a.is_empty() { // TODO: push the tokens conditionally - quote! { - use canyon_sql::core::IntoResults; - - #[canyon_sql::macros::async_trait] // TODO: get rid of the async_trait - impl canyon_sql::crud::CrudOperations<#ty> for #ty { - #crud_operations_tokens - } - - impl canyon_sql::core::Transaction<#ty> for #ty {} - - /// Hidden trait for generate the foreign key operations available - /// in Canyon without have to define them beforehand in CrudOperations - /// because it's just impossible with the actual system (where the methods - /// are generated dynamically based on some properties of the `foreign_key` - /// annotation) - #[canyon_sql::macros::async_trait] - pub trait #fk_trait_ident<#ty> { - #(#fk_method_signatures)* - #(#rev_fk_method_signatures)* - } - #[canyon_sql::macros::async_trait] - impl #fk_trait_ident<#ty> for #ty - where #ty: - std::fmt::Debug + - canyon_sql::crud::CrudOperations<#ty> + - canyon_sql::core::RowMapper<#ty> - { - #(#fk_method_implementations)* - #(#rev_fk_method_implementations)* - } - } - } else { - quote! { - use canyon_sql::core::IntoResults; + crud_ops_tokens.extend(quote!{ + use canyon_sql::core::IntoResults; - #[canyon_sql::macros::async_trait] - impl canyon_sql::crud::CrudOperations<#ty> for #ty { - #crud_operations_tokens - } - - impl canyon_sql::core::Transaction<#ty> for #ty {} + #[canyon_sql::macros::async_trait] // TODO: get rid of the async_trait + impl canyon_sql::crud::CrudOperations<#ty> for #ty { + #crud_operations_tokens } - }; - tokens.into() + impl canyon_sql::core::Transaction<#ty> for #ty {} + }); + + let foreign_key_ops_tokens = generate_find_by_fk_ops(macro_data, &table_schema_data); + crud_ops_tokens.extend(quote!{ #foreign_key_ops_tokens }); + + crud_ops_tokens.into() } /// proc-macro for annotate struct fields that holds a foreign key relation. diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index 6d9530cc..811f41c9 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -1,19 +1,65 @@ -use proc_macro2::TokenStream; +use proc_macro2::{Ident, TokenStream}; use quote::quote; use canyon_entities::field_annotation::EntityFieldAnnotation; use crate::utils::helpers::database_table_name_to_struct_ident; use crate::utils::macro_tokens::MacroTokens; -// pub fn generate_find_by_fk_ops(macro_data: &MacroTokens<'_>, table_schema_data: &str) -> Vec<(TokenStream, TokenStream)> { -// let find_by_fk_ops_tokens = generate_find_by_foreign_key_tokens(macro_data); -// let find_by_reverse_fk_ops_tokens = generate_find_by_reverse_foreign_key_tokens(macro_data, table_schema_data); -// } +pub fn generate_find_by_fk_ops(macro_data: &MacroTokens<'_>, table_schema_data: &str) -> TokenStream { + let ty = ¯o_data.ty; + + // Search by foreign (d) key as Vec, cause Canyon supports multiple fields having FK annotation + let _search_by_fk_tokens: Vec<(TokenStream, TokenStream)> = + generate_find_by_foreign_key_tokens(macro_data); + let fk_method_signatures = _search_by_fk_tokens.iter().map(|(sign, _)| sign); + let fk_method_implementations = _search_by_fk_tokens.iter().map(|(_, m_impl)| m_impl); + + // The tokens for generating the methods that enable Canyon to retrieve the child entities that are of T type + // given a parent entity U: ForeignKeyable, as an associated function for the child type (T) + let search_by_reverse_fk_tokens: Vec<(TokenStream, TokenStream)> = + generate_find_by_reverse_foreign_key_tokens(macro_data, &table_schema_data); + let rev_fk_method_signatures = search_by_reverse_fk_tokens.iter().map(|(sign, _)| sign); + let rev_fk_method_implementations = + search_by_reverse_fk_tokens.iter().map(|(_, m_impl)| m_impl); + + // The autogenerated name for the trait that holds the fk and rev fk searches + let fk_trait_ident = Ident::new( + &format!("{}FkOperations", &ty.to_string()), + proc_macro2::Span::call_site(), + ); + + if search_by_reverse_fk_tokens.is_empty() { + return quote!{}; // early guard + } + + quote! { + /// Hidden trait for generate the foreign key operations available + /// in Canyon without have to define them beforehand in CrudOperations + /// because it's just impossible with the actual system (where the methods + /// are generated dynamically based on some properties of the `foreign_key` + /// annotation) + #[canyon_sql::macros::async_trait] + pub trait #fk_trait_ident<#ty> { + #(#fk_method_signatures)* + #(#rev_fk_method_signatures)* + } + #[canyon_sql::macros::async_trait] + impl #fk_trait_ident<#ty> for #ty + where #ty: + std::fmt::Debug + + canyon_sql::crud::CrudOperations<#ty> + + canyon_sql::core::RowMapper<#ty> + { + #(#fk_method_implementations)* + #(#rev_fk_method_implementations)* + } + } +} /// Generates the TokenStream for build the search by foreign key feature, also as a method instance /// of a T type of as an associated function of same T type, but wrapped as a Result, representing /// a possible failure querying the database, a bad or missing FK annotation or a missed ForeignKeyable /// derive macro on the parent side of the relation -pub fn generate_find_by_foreign_key_tokens( +fn generate_find_by_foreign_key_tokens( macro_data: &MacroTokens<'_>, ) -> Vec<(TokenStream, TokenStream)> { let mut fk_quotes: Vec<(TokenStream, TokenStream)> = Vec::new(); @@ -99,7 +145,7 @@ pub fn generate_find_by_foreign_key_tokens( /// associated function, but wrapped as a Result, representing /// a possible failure querying the database, a bad or missing FK annotation or a missed ForeignKeyable /// derive macro on the parent side of the relation -pub fn generate_find_by_reverse_foreign_key_tokens( +fn generate_find_by_reverse_foreign_key_tokens( macro_data: &MacroTokens<'_>, table_schema_data: &str, ) -> Vec<(TokenStream, TokenStream)> { From 088aba1faa90ecdf1c9409c9553a7ecb6fda8c1a Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 28 Jan 2025 09:58:40 +0100 Subject: [PATCH 055/155] feat: querybuilder read ops are hidden behind the same facade as the other read ops --- canyon_macros/src/lib.rs | 13 ++----------- canyon_macros/src/query_operations/mod.rs | 2 +- .../src/query_operations/{select.rs => read.rs} | 6 +++++- tests/crud/mod.rs | 2 +- .../{select_operations.rs => read_operations.rs} | 0 5 files changed, 9 insertions(+), 14 deletions(-) rename canyon_macros/src/query_operations/{select.rs => read.rs} (99%) rename tests/crud/{select_operations.rs => read_operations.rs} (100%) diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index c793a4ec..e732b4b5 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -20,10 +20,7 @@ use quote::quote; use syn::{DeriveInput, Fields, Type, Visibility}; use query_operations::{ - select::{ - generate_read_operations_tokens, - generate_find_all_query_tokens - }, + read::generate_read_operations_tokens, insert::generate_insert_tokens, update::generate_update_tokens, delete::generate_delete_tokens, @@ -165,8 +162,7 @@ pub fn canyon_entity( } // No errors detected on the parsing, so we can safely unwrap the parse result - let entity = entity_res.expect("Unexpected error parsing the struct"); - // Generate the bits of code that we should give back to the compiler + let entity = entity_res.unwrap(); let generated_user_struct = generate_user_struct(&entity); // The identifier of the entities @@ -250,14 +246,12 @@ fn impl_crud_operations_trait_for_struct( let ty = macro_data.ty; let read_operations_tokens = generate_read_operations_tokens(macro_data, &table_schema_data); - let find_all_query_tokens = generate_find_all_query_tokens(macro_data, &table_schema_data); let insert_tokens = generate_insert_tokens(macro_data, &table_schema_data); let update_tokens = generate_update_tokens(macro_data, &table_schema_data); let delete_tokens = generate_delete_tokens(macro_data, &table_schema_data); let crud_operations_tokens = quote! { // TODO: bring this directly from mod.rs or query_operations? #read_operations_tokens - #find_all_query_tokens #insert_tokens #update_tokens #delete_tokens @@ -350,9 +344,6 @@ pub fn implement_row_mapper_for_type(input: proc_macro::TokenStream) -> proc_mac } }); - // TODO: refactor the code below after the current bugfixes, to conditinally generate - // the required methods and populate the CanyonMapper trait dependencing on the cfg flags - // enabled with a more elegant solution (a fn for feature, for ex) #[cfg(feature = "postgres")] // Here it's where the incoming values of the DatabaseResult are wired into a new // instance, mapping the fields of the type against the columns diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index 1e2703cc..5aeb9d87 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -1,7 +1,7 @@ pub mod delete; pub mod foreign_key; pub mod insert; -pub mod select; +pub mod read; pub mod update; mod doc_comments; diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/read.rs similarity index 99% rename from canyon_macros/src/query_operations/select.rs rename to canyon_macros/src/query_operations/read.rs index 422dfb0d..1a66c6cd 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -30,6 +30,8 @@ pub fn generate_read_operations_tokens( let count_with = create_count_with_macro(ty, &count_stmt); let find_by_pk_complex_tokens = generate_find_by_pk_tokens(macro_data, table_schema_data); + + let read_querybuilder_ops = generate_find_all_query_tokens(macro_data, table_schema_data); quote! { #find_all @@ -41,10 +43,12 @@ pub fn generate_read_operations_tokens( #count_with #find_by_pk_complex_tokens + + #read_querybuilder_ops } } -pub fn generate_find_all_query_tokens( +fn generate_find_all_query_tokens( macro_data: &MacroTokens<'_>, table_schema_data: &String, ) -> TokenStream { diff --git a/tests/crud/mod.rs b/tests/crud/mod.rs index 407e727c..69ad58c3 100644 --- a/tests/crud/mod.rs +++ b/tests/crud/mod.rs @@ -6,5 +6,5 @@ pub mod foreign_key_operations; pub mod init_mssql; pub mod insert_operations; pub mod querybuilder_operations; -pub mod select_operations; +pub mod read_operations; pub mod update_operations; diff --git a/tests/crud/select_operations.rs b/tests/crud/read_operations.rs similarity index 100% rename from tests/crud/select_operations.rs rename to tests/crud/read_operations.rs From b19001dd163a8e8a4e388d33dccb0780ea1d8ca6 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 28 Jan 2025 10:30:08 +0100 Subject: [PATCH 056/155] chore: refactored the CanyonMapper macro into it's own file --- canyon_macros/src/canyon_mapper_macro.rs | 188 +++++++++++++++++++++ canyon_macros/src/lib.rs | 203 +---------------------- canyon_macros/src/utils/helpers.rs | 22 ++- 3 files changed, 213 insertions(+), 200 deletions(-) create mode 100644 canyon_macros/src/canyon_mapper_macro.rs diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs new file mode 100644 index 00000000..abfb5bbd --- /dev/null +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -0,0 +1,188 @@ +use std::iter::Map; +use std::slice::Iter; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; +use syn::{DeriveInput, Type, Visibility}; + +use crate::utils::helpers::{fields_with_types}; + +pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { + let ty = &ast.ident; + let mut impl_methods = TokenStream::new(); + + // Recovers the identifiers of the structs members + let fields = fields_with_types(match ast.data { + syn::Data::Struct(ref s) => &s.fields, + _ => { + return syn::Error::new(ast.ident.span(), "CanyonMapper only works with Structs") + .to_compile_error() + .into() + } + }); + + #[cfg(feature = "postgres")] + let pg_implementation = create_postgres_fields_mapping(&fields); + #[cfg(feature = "postgres")] + impl_methods.extend(quote! { + fn deserialize_postgresql(row: &canyon_sql::db_clients::tokio_postgres::Row) -> #ty { + Self { + #(#pg_implementation),* + } + } + }); + + #[cfg(feature = "mssql")] + let sqlserver_implementation = create_sqlserver_fields_mapping(&fields); + #[cfg(feature = "mssql")] + impl_methods.extend(quote! { + fn deserialize_sqlserver(row: &canyon_sql::db_clients::tiberius::Row) -> #ty { + Self { + #(#sqlserver_implementation),* + } + } + }); + + #[cfg(feature = "mysql")] + let mysql_implementation = create_mysql_fields_mapping(&fields); + #[cfg(feature = "mysql")] + impl_methods.extend(quote! { + fn deserialize_mysql(row: &canyon_sql::db_clients::mysql_async::Row) -> #ty { + Self { + #(#mysql_implementation),* + } + } + }); + + quote! { + impl canyon_sql::core::RowMapper for #ty { + #impl_methods + } + } +} + +#[cfg(feature = "postgres")] +fn create_postgres_fields_mapping(fields: &Vec<(Visibility, Ident, Type)>) -> Map, fn(&'_ (Visibility, Ident, Type)) -> TokenStream> { + fields.iter().map(|(_vis, ident, _ty)| { + let ident_name = ident.to_string(); + quote! { + #ident: row.try_get(#ident_name) // TODO: can we wrap RowMapper in a Result and propagate errors with ?\? + .expect(format!("Failed to retrieve the {} field", #ident_name).as_ref()) + } + }) +} + +#[cfg(feature = "mysql")] +fn create_mysql_fields_mapping(fields: &Vec<(Visibility, Ident, Type)>) -> Map, fn(&'_ (Visibility, Ident, Type)) -> TokenStream> { + fields.iter().map(|(_vis, ident, _ty)| { + let ident_name = ident.to_string(); + quote! { + #ident: row.get(#ident_name) + .expect(format!("Failed to retrieve the {} field", #ident_name).as_ref()) + } + }) +} + +#[cfg(feature = "mssql")] +fn create_sqlserver_fields_mapping(fields: &Vec<(Visibility, Ident, Type)>) -> Map, fn(&'_ (Visibility, Ident, Type)) -> TokenStream> { + fields.iter().map(|(_vis, ident, ty)| { + let ident_name = ident.to_string(); + + if get_field_type_as_string(ty) == "String" { + quote! { + #ident: row.get::<&str, &str>(#ident_name) + .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) + .to_string() + } + } else if get_field_type_as_string(ty).replace(' ', "") == "Option < i64 >" { + quote! { + #ident: row.get::(#ident_name) + } + } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { + quote! { + #ident: row.get::(#ident_name) + } + } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { + quote! { + #ident: row.get::(#ident_name) + } + } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { + quote! { + #ident: row.get::(#ident_name) + } + } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { + quote! { + #ident: row.get::(#ident_name) + } + } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { + quote! { + #ident: row.get::<&str, &str>(#ident_name) + .map( |x| x.to_owned() ) + } + } else if get_field_type_as_string(ty) == "NaiveDate" { + quote! { + #ident: row.get::(#ident_name) + .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) + } + } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { + quote! { + #ident: row.get::(#ident_name) + } + } else if get_field_type_as_string(ty) == "NaiveTime" { + quote! { + #ident: row.get::(#ident_name) + .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) + } + } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { + quote! { + #ident: row.get::(#ident_name) + } + } else if get_field_type_as_string(ty) == "NaiveDateTime" { + quote! { + #ident: row.get::(#ident_name) + .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) + } + } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { + quote! { + #ident: row.get::(#ident_name) + } + } else if get_field_type_as_string(ty) == "DateTime" { + quote! { + #ident: row.get::(#ident_name) + .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) + } + } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { + quote! { + #ident: row.get::(#ident_name) + } + } else { + quote! { + #ident: row.get::<#ty, &str>(#ident_name) + .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) + } + } + }) +} + +#[cfg(feature = "mssql")] +use quote::ToTokens; +#[cfg(feature = "mssql")] +fn get_field_type_as_string(typ: &Type) -> String { + match typ { + Type::Array(type_) => type_.to_token_stream().to_string(), + Type::BareFn(type_) => type_.to_token_stream().to_string(), + Type::Group(type_) => type_.to_token_stream().to_string(), + Type::ImplTrait(type_) => type_.to_token_stream().to_string(), + Type::Infer(type_) => type_.to_token_stream().to_string(), + Type::Macro(type_) => type_.to_token_stream().to_string(), + Type::Never(type_) => type_.to_token_stream().to_string(), + Type::Paren(type_) => type_.to_token_stream().to_string(), + Type::Path(type_) => type_.to_token_stream().to_string(), + Type::Ptr(type_) => type_.to_token_stream().to_string(), + Type::Reference(type_) => type_.to_token_stream().to_string(), + Type::Slice(type_) => type_.to_token_stream().to_string(), + Type::TraitObject(type_) => type_.to_token_stream().to_string(), + Type::Tuple(type_) => type_.to_token_stream().to_string(), + Type::Verbatim(type_) => type_.to_token_stream().to_string(), + _ => "".to_owned(), + } +} \ No newline at end of file diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index e732b4b5..75fce4ab 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -12,6 +12,7 @@ use canyon_macro::main_with_queries; mod canyon_macro; mod query_operations; mod utils; +mod canyon_mapper_macro; use canyon_entity_macro::parse_canyon_entity_proc_macro_attr; use proc_macro::TokenStream as CompilerTokenStream; @@ -36,6 +37,8 @@ use canyon_entities::{ register_types::{CanyonRegisterEntity, CanyonRegisterEntityField}, CANYON_REGISTER_ENTITIES, }; +use crate::canyon_mapper_macro::canyon_mapper_impl_tokens; +use crate::utils::helpers::filter_fields; /// Macro for handling the entry point to the program. /// @@ -218,10 +221,6 @@ pub fn canyon_entity( /// type, as defined in the `CrudOperations` + `Transaction` traits. #[proc_macro_derive(CanyonCrud)] pub fn crud_operations(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - // Construct a representation of Rust code as a syntax tree - // that we can manipulate - - // Calls the helper struct to build the tokens that generates the final CRUD methods let ast: DeriveInput = syn::parse(input).expect("Error parsing `Canyon Entity for generate the CRUD methods"); let macro_data = MacroTokens::new(&ast); @@ -331,201 +330,7 @@ pub fn implement_foreignkeyable_for_type( #[proc_macro_derive(CanyonMapper)] pub fn implement_row_mapper_for_type(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - // Gets the data from the AST let ast: DeriveInput = syn::parse(input).unwrap(); - - // Recovers the identifiers of the structs members - let fields = fields_with_types(match ast.data { - syn::Data::Struct(ref s) => &s.fields, - _ => { - return syn::Error::new(ast.ident.span(), "CanyonMapper only works with Structs") - .to_compile_error() - .into() - } - }); - - #[cfg(feature = "postgres")] - // Here it's where the incoming values of the DatabaseResult are wired into a new - // instance, mapping the fields of the type against the columns - let init_field_values = fields.iter().map(|(_vis, ident, _ty)| { - let ident_name = ident.to_string(); - quote! { - #ident: row.try_get(#ident_name) - .expect(format!("Failed to retrieve the {} field", #ident_name).as_ref()) - } - }); - - #[cfg(feature = "mssql")] - let init_field_values_sqlserver = fields.iter().map(|(_vis, ident, ty)| { - let ident_name = ident.to_string(); - - if get_field_type_as_string(ty) == "String" { - quote! { - #ident: row.get::<&str, &str>(#ident_name) - .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) - .to_string() - } - } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { - quote! { - #ident: row.get::(#ident_name) - } - } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { - quote! { - #ident: row.get::(#ident_name) - } - } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { - quote! { - #ident: row.get::(#ident_name) - } - } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { - quote! { - #ident: row.get::(#ident_name) - } - } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { - quote! { - #ident: row.get::(#ident_name) - } - } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { - quote! { - #ident: row.get::<&str, &str>(#ident_name) - .map( |x| x.to_owned() ) - } - } else if get_field_type_as_string(ty) == "NaiveDate" { - quote! { - #ident: row.get::(#ident_name) - .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) - } - } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { - quote! { - #ident: row.get::(#ident_name) - } - } else if get_field_type_as_string(ty) == "NaiveTime" { - quote! { - #ident: row.get::(#ident_name) - .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) - } - } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { - quote! { - #ident: row.get::(#ident_name) - } - } else if get_field_type_as_string(ty) == "NaiveDateTime" { - quote! { - #ident: row.get::(#ident_name) - .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) - } - } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { - quote! { - #ident: row.get::(#ident_name) - } - } else if get_field_type_as_string(ty) == "DateTime" { - quote! { - #ident: row.get::(#ident_name) - .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) - } - } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { - quote! { - #ident: row.get::(#ident_name) - } - } else { - quote! { - #ident: row.get::<#ty, &str>(#ident_name) - .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) - } - } - }); - - #[cfg(feature = "mysql")] - let init_field_values_mysql = fields.iter().map(|(_vis, ident, _ty)| { - let ident_name = ident.to_string(); - quote! { - #ident: row.get(#ident_name) - .expect(format!("Failed to retrieve the {} field", #ident_name).as_ref()) - } - }); - - // The type of the Struct - let ty = ast.ident; - - let mut impl_methods = quote! {}; // Collect methods conditionally - - #[cfg(feature = "postgres")] - impl_methods.extend(quote! { - fn deserialize_postgresql(row: &canyon_sql::db_clients::tokio_postgres::Row) -> #ty { - Self { - #(#init_field_values),* - } - } - }); - - #[cfg(feature = "mssql")] - impl_methods.extend(quote! { - fn deserialize_sqlserver(row: &canyon_sql::db_clients::tiberius::Row) -> #ty { - Self { - #(#init_field_values_sqlserver),* - } - } - }); - - #[cfg(feature = "mysql")] - impl_methods.extend(quote! { - fn deserialize_mysql(row: &canyon_sql::db_clients::mysql_async::Row) -> #ty { - Self { - #(#init_field_values_mysql),* - } - } - }); - - // Wrap everything in the shared `impl` block - let tokens = quote! { - impl canyon_sql::core::RowMapper for #ty { - #impl_methods - } - }; - - tokens.into() -} - -/// Helper for generate the fields data for the Custom Derives Macros -fn filter_fields(fields: &Fields) -> Vec<(Visibility, Ident)> { - fields - .iter() - .map(|field| (field.vis.clone(), field.ident.as_ref().unwrap().clone())) - .collect::>() -} - -fn fields_with_types(fields: &Fields) -> Vec<(Visibility, Ident, Type)> { - fields - .iter() - .map(|field| { - ( - field.vis.clone(), - field.ident.as_ref().unwrap().clone(), - field.ty.clone(), - ) - }) - .collect::>() + canyon_mapper_impl_tokens(ast).into() } -#[cfg(feature = "mssql")] -use quote::ToTokens; -#[cfg(feature = "mssql")] -fn get_field_type_as_string(typ: &Type) -> String { - match typ { - Type::Array(type_) => type_.to_token_stream().to_string(), - Type::BareFn(type_) => type_.to_token_stream().to_string(), - Type::Group(type_) => type_.to_token_stream().to_string(), - Type::ImplTrait(type_) => type_.to_token_stream().to_string(), - Type::Infer(type_) => type_.to_token_stream().to_string(), - Type::Macro(type_) => type_.to_token_stream().to_string(), - Type::Never(type_) => type_.to_token_stream().to_string(), - Type::Paren(type_) => type_.to_token_stream().to_string(), - Type::Path(type_) => type_.to_token_stream().to_string(), - Type::Ptr(type_) => type_.to_token_stream().to_string(), - Type::Reference(type_) => type_.to_token_stream().to_string(), - Type::Slice(type_) => type_.to_token_stream().to_string(), - Type::TraitObject(type_) => type_.to_token_stream().to_string(), - Type::Tuple(type_) => type_.to_token_stream().to_string(), - Type::Verbatim(type_) => type_.to_token_stream().to_string(), - _ => "".to_owned(), - } -} diff --git a/canyon_macros/src/utils/helpers.rs b/canyon_macros/src/utils/helpers.rs index 2db52be5..aa673823 100644 --- a/canyon_macros/src/utils/helpers.rs +++ b/canyon_macros/src/utils/helpers.rs @@ -1,8 +1,28 @@ use proc_macro2::{Ident, Span, TokenStream}; -use syn::{punctuated::Punctuated, MetaNameValue, Token}; +use syn::{punctuated::Punctuated, Fields, MetaNameValue, Token, Type, Visibility}; use super::macro_tokens::MacroTokens; +pub fn filter_fields(fields: &Fields) -> Vec<(Visibility, Ident)> { + fields + .iter() + .map(|field| (field.vis.clone(), field.ident.as_ref().unwrap().clone())) + .collect::>() +} + +pub fn fields_with_types(fields: &Fields) -> Vec<(Visibility, Ident, Type)> { + fields + .iter() + .map(|field| { + ( + field.vis.clone(), + field.ident.as_ref().unwrap().clone(), + field.ty.clone(), + ) + }) + .collect::>() +} + /// If the `canyon_entity` macro has valid attributes attached, and those attrs are the /// user's desired `table_name` and/or the `schema_name`, this method returns its /// correct form to be wired as the table name that the CRUD methods requires for generate From 381a974eb6bea0c265cc3c86e46d9c5d13b63515 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 28 Jan 2025 14:31:59 +0100 Subject: [PATCH 057/155] chore: handle_stupid_tiberius_sql_conversions --- Cargo.toml | 2 +- canyon_macros/Cargo.toml | 1 + canyon_macros/src/canyon_mapper_macro.rs | 161 ++++++++++++----------- canyon_macros/src/lib.rs | 1 + tests/tests_models/tournament.rs | 4 +- 5 files changed, 87 insertions(+), 82 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a9488d46..de5e7c65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ canyon_macros = { version = "0.5.1", path = "canyon_macros" } tokio = { version = "1.27.0", features = ["full"] } tokio-util = { version = "0.7.4", features = ["compat"] } tokio-postgres = { version = "0.7.2", features = ["with-chrono-0_4"] } -tiberius = { version = "0.12.1", features = ["tds73", "chrono", "integrated-auth-gssapi"] } +tiberius = { version = "0.12.3", features = ["tds73", "chrono", "integrated-auth-gssapi"] } mysql_async = { version = "0.32.2" } mysql_common = { version = "0.30.6", features = [ "chrono" ]} diff --git a/canyon_macros/Cargo.toml b/canyon_macros/Cargo.toml index bb7eb660..9f89caf0 100755 --- a/canyon_macros/Cargo.toml +++ b/canyon_macros/Cargo.toml @@ -18,6 +18,7 @@ quote = { workspace = true } proc-macro2 = { workspace = true } futures = { workspace = true } tokio = { workspace = true } +regex = { workspace = true } canyon_core = { workspace = true } canyon_crud = { workspace = true } diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index abfb5bbd..be79e35f 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -1,15 +1,18 @@ use std::iter::Map; use std::slice::Iter; -use proc_macro2::{Ident, TokenStream}; +use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::{DeriveInput, Type, Visibility}; - +use regex::Regex; use crate::utils::helpers::{fields_with_types}; +#[cfg(feature = "mssql")] +const BY_VALUE_CONVERSION_TARGETS: [&str; 1] = ["String"]; + pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { let ty = &ast.ident; let mut impl_methods = TokenStream::new(); - + // Recovers the identifiers of the structs members let fields = fields_with_types(match ast.data { syn::Data::Struct(ref s) => &s.fields, @@ -30,7 +33,7 @@ pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { } } }); - + #[cfg(feature = "mssql")] let sqlserver_implementation = create_sqlserver_fields_mapping(&fields); #[cfg(feature = "mssql")] @@ -41,7 +44,7 @@ pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { } } }); - + #[cfg(feature = "mysql")] let mysql_implementation = create_mysql_fields_mapping(&fields); #[cfg(feature = "mysql")] @@ -84,85 +87,70 @@ fn create_mysql_fields_mapping(fields: &Vec<(Visibility, Ident, Type)>) -> Map) -> Map, fn(&'_ (Visibility, Ident, Type)) -> TokenStream> { - fields.iter().map(|(_vis, ident, ty)| { + fields.into_iter().map(|(_vis, ident, ty)| { let ident_name = ident.to_string(); - if get_field_type_as_string(ty) == "String" { - quote! { - #ident: row.get::<&str, &str>(#ident_name) - .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) - .to_string() - } - } else if get_field_type_as_string(ty).replace(' ', "") == "Option < i64 >" { - quote! { - #ident: row.get::(#ident_name) - } - } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { - quote! { - #ident: row.get::(#ident_name) - } - } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { - quote! { - #ident: row.get::(#ident_name) - } - } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { - quote! { - #ident: row.get::(#ident_name) - } - } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { - quote! { - #ident: row.get::(#ident_name) - } - } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { - quote! { - #ident: row.get::<&str, &str>(#ident_name) - .map( |x| x.to_owned() ) - } - } else if get_field_type_as_string(ty) == "NaiveDate" { - quote! { - #ident: row.get::(#ident_name) - .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) - } - } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { - quote! { - #ident: row.get::(#ident_name) - } - } else if get_field_type_as_string(ty) == "NaiveTime" { - quote! { - #ident: row.get::(#ident_name) - .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) - } - } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { - quote! { - #ident: row.get::(#ident_name) - } - } else if get_field_type_as_string(ty) == "NaiveDateTime" { - quote! { - #ident: row.get::(#ident_name) - .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) - } - } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { - quote! { - #ident: row.get::(#ident_name) - } - } else if get_field_type_as_string(ty) == "DateTime" { - quote! { - #ident: row.get::(#ident_name) - .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) - } - } else if get_field_type_as_string(ty).replace(' ', "") == "Option" { - quote! { - #ident: row.get::(#ident_name) - } - } else { - quote! { - #ident: row.get::<#ty, &str>(#ident_name) - .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) - } + let target_field_type_str = get_field_type_as_string(ty); + let field_deserialize_impl = + handle_stupid_tiberius_sql_conversions(&target_field_type_str, &ident_name); + + quote!{ + #ident: #field_deserialize_impl } }) } +#[cfg(feature = "mssql")] +fn handle_stupid_tiberius_sql_conversions(target_type: &str, ident_name: &str) -> TokenStream { + println!("Handling type: {:?} for field: {:?}", target_type, ident_name); + let is_opt_type = target_type.contains("Option"); + let handle_opt = if !is_opt_type { + quote! { .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) } + } else { quote! {} }; + + let deserializing_type = get_deserializing_type(target_type); + let to_owned = if BY_VALUE_CONVERSION_TARGETS + .iter() + .any(|bv| target_type.contains(bv)) + { + if is_opt_type { + quote! { .map(|inner| inner.to_owned()) } + } else { + quote! { .to_owned() } + } + } else { quote! {} }; + + + quote! { + row.get::<#deserializing_type, &str>(#ident_name) + #handle_opt + #to_owned + } +} + +fn get_deserializing_type(target_type: &str) -> TokenStream { + let re = Regex::new(r"(?:Option\s*<\s*)?(?P&?\w+)(?:\s*>)?").unwrap(); + re + .captures(&*target_type) + .map(|inner| String::from(&inner["type"])) + .map(|tt| { + if BY_VALUE_CONVERSION_TARGETS.contains(&tt.as_str()) { + quote! { &str } + // potentially others on demand on the future + } else if tt.contains("Date") || tt.contains("Time") { + let dt = Ident::new( + tt.as_str(), + Span::call_site() + ); + quote! { canyon_sql::date_time::#dt } + } else { + let tt = Ident::new(tt.as_str(), Span::call_site()); + quote! { #tt } + } + }) + .expect(&format!("Unable to process type: {} on the given struct for SqlServer", target_type)) +} + #[cfg(feature = "mssql")] use quote::ToTokens; #[cfg(feature = "mssql")] @@ -185,4 +173,19 @@ fn get_field_type_as_string(typ: &Type) -> String { Type::Verbatim(type_) => type_.to_token_stream().to_string(), _ => "".to_owned(), } -} \ No newline at end of file +} + +#[cfg(test)] +mod mapper_macro_tests { + use crate::canyon_mapper_macro::get_deserializing_type; + + #[test] + fn test_regex_extraction_for_the_tiberius_target_types() { + assert_eq!("&str", get_deserializing_type("String").to_string()); + assert_eq!("&str", get_deserializing_type("Option").to_string()); + assert_eq!("i64", get_deserializing_type("i64").to_string()); + + assert_eq!("canyon_sql::date_time::DateTime", get_deserializing_type("DateTime").to_string()); + assert_eq!("canyon_sql::date_time::NaiveDateTime", get_deserializing_type("NaiveDateTime").to_string()); + } +} diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index 75fce4ab..b4ea3fd9 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -4,6 +4,7 @@ #![allow(unused_imports)] extern crate proc_macro; +extern crate regex; mod canyon_entity_macro; #[cfg(feature = "migrations")] diff --git a/tests/tests_models/tournament.rs b/tests/tests_models/tournament.rs index 880076f4..d21c61cb 100644 --- a/tests/tests_models/tournament.rs +++ b/tests/tests_models/tournament.rs @@ -6,10 +6,10 @@ use canyon_sql::{date_time::NaiveDate, macros::*}; pub struct Tournament { #[primary_key] id: i32, + #[foreign_key(table = "league", column = "id")] + league: i32, ext_id: i64, slug: String, start_date: NaiveDate, end_date: NaiveDate, - #[foreign_key(table = "league", column = "id")] - league: i32, } From 8b775e97ca0891a1141a68caf6a89ad32fa45630 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 28 Jan 2025 14:58:41 +0100 Subject: [PATCH 058/155] chore: moving proc macro implementations from the root lib file of canyon_macros to their own separate modules --- canyon_macros/src/canyon_entity_macro.rs | 80 ++++++- canyon_macros/src/canyon_mapper_macro.rs | 1 - canyon_macros/src/foreignkeyable_macro.rs | 49 ++++ canyon_macros/src/lib.rs | 223 +++--------------- canyon_macros/src/query_operations/delete.rs | 2 +- canyon_macros/src/query_operations/insert.rs | 1 - .../src/query_operations/macro_template.rs | 16 +- canyon_macros/src/query_operations/mod.rs | 46 ++++ canyon_macros/src/query_operations/read.rs | 5 +- canyon_macros/src/query_operations/update.rs | 4 +- tests/tests_models/tournament.rs | 4 +- 11 files changed, 220 insertions(+), 211 deletions(-) create mode 100644 canyon_macros/src/foreignkeyable_macro.rs diff --git a/canyon_macros/src/canyon_entity_macro.rs b/canyon_macros/src/canyon_entity_macro.rs index 483f8f8e..073b3802 100644 --- a/canyon_macros/src/canyon_entity_macro.rs +++ b/canyon_macros/src/canyon_entity_macro.rs @@ -1,7 +1,79 @@ +use proc_macro::TokenStream as CompilerTokenStream; use proc_macro2::{Span, TokenStream}; -use syn::NestedMeta; +use quote::quote; +use syn::{AttributeArgs, NestedMeta}; +use canyon_entities::CANYON_REGISTER_ENTITIES; +use canyon_entities::entity::CanyonEntity; +use canyon_entities::manager_builder::generate_user_struct; +use canyon_entities::register_types::{CanyonRegisterEntity, CanyonRegisterEntityField}; +use crate::utils::helpers; -pub(crate) fn parse_canyon_entity_proc_macro_attr( +pub fn generate_canyon_entity_tokens(attrs: AttributeArgs, input: CompilerTokenStream) -> TokenStream { + let (table_name, schema_name, parsing_attribute_error) = + parse_canyon_entity_proc_macro_attr(attrs); + + let entity_res = syn::parse::(input); + + if entity_res.is_err() { + return entity_res + .expect_err("Unexpected error parsing the struct") + .into_compile_error() + .into(); + } + + // No errors detected on the parsing, so we can safely unwrap the parse result + let entity = entity_res.unwrap(); + let generated_user_struct = generate_user_struct(&entity); + + // The identifier of the entities + let mut new_entity = CanyonRegisterEntity::default(); + let e = Box::leak(entity.struct_name.to_string().into_boxed_str()); + new_entity.entity_name = e; + new_entity.entity_db_table_name = table_name.unwrap_or(Box::leak( + helpers::default_database_table_name_from_entity_name(e).into_boxed_str(), + )); + new_entity.user_schema_name = schema_name; + + // The entity fields + for field in entity.fields.iter() { + let mut new_entity_field = CanyonRegisterEntityField { + field_name: field.name.to_string(), + field_type: field.get_field_type_as_string().replace(' ', ""), + ..Default::default() + }; + + field + .attributes + .iter() + .for_each(|attr| new_entity_field.annotations.push(attr.get_as_string())); + + new_entity.entity_fields.push(new_entity_field); + } + + // Fill the register with the data of the attached struct + CANYON_REGISTER_ENTITIES + .lock() + .expect("Error acquiring Mutex guard on Canyon Entity macro") + .push(new_entity); + + // Assemble everything + let tokens = quote! { + #generated_user_struct + }; + + // Pass the result back to the compiler + if let Some(macro_error) = parsing_attribute_error { + quote! { + #macro_error + #generated_user_struct + } + .into() + } else { + tokens.into() + } +} + +fn parse_canyon_entity_proc_macro_attr( attrs: Vec, ) -> ( Option<&'static str>, @@ -16,7 +88,7 @@ pub(crate) fn parse_canyon_entity_proc_macro_attr( // The parse of the available options to configure the Canyon Entity for element in attrs { match element { - syn::NestedMeta::Meta(m) => { + NestedMeta::Meta(m) => { match m { syn::Meta::NameValue(nv) => { let attr_arg_ident = nv @@ -62,7 +134,7 @@ pub(crate) fn parse_canyon_entity_proc_macro_attr( } } } - syn::NestedMeta::Lit(_) => { + NestedMeta::Lit(_) => { parsing_attribute_error = Some(syn::Error::new( Span::call_site(), "No literal values allowed on the `canyon_macros::canyon_entity` proc macro" diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index be79e35f..34ed2e4a 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -102,7 +102,6 @@ fn create_sqlserver_fields_mapping(fields: &Vec<(Visibility, Ident, Type)>) -> M #[cfg(feature = "mssql")] fn handle_stupid_tiberius_sql_conversions(target_type: &str, ident_name: &str) -> TokenStream { - println!("Handling type: {:?} for field: {:?}", target_type, ident_name); let is_opt_type = target_type.contains("Option"); let handle_opt = if !is_opt_type { quote! { .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) } diff --git a/canyon_macros/src/foreignkeyable_macro.rs b/canyon_macros/src/foreignkeyable_macro.rs new file mode 100644 index 00000000..2a2333ec --- /dev/null +++ b/canyon_macros/src/foreignkeyable_macro.rs @@ -0,0 +1,49 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::DeriveInput; +use crate::utils::helpers::filter_fields; + +pub fn foreignkeyable_impl_tokens(ast: DeriveInput) -> TokenStream { + let ty = ast.ident; + + // Recovers the identifiers of the structs members + let fields = filter_fields(match ast.data { + syn::Data::Struct(ref s) => &s.fields, + _ => { + return syn::Error::new(ty.span(), "ForeignKeyable only works with Structs") + .to_compile_error() + .into() + } + }); + + let field_idents = fields.iter().map(|(_vis, ident)| { + let i = ident.to_string(); + quote! { + #i => Some(&self.#ident as &dyn canyon_sql::core::QueryParameter<'_>) + } + }); + let field_idents_cloned = field_idents.clone(); + + quote! { + /// Implementation of the trait `ForeignKeyable` for the type + /// calling this derive proc macro + impl canyon_sql::crud::bounds::ForeignKeyable for #ty { + fn get_fk_column(&self, column: &str) -> Option<&dyn canyon_sql::core::QueryParameter<'_>> { + match column { + #(#field_idents),*, + _ => None + } + } + } + /// Implementation of the trait `ForeignKeyable` for a reference of this type + /// calling this derive proc macro + impl canyon_sql::crud::bounds::ForeignKeyable<&Self> for &#ty { + fn get_fk_column<'a>(&self, column: &'a str) -> Option<&dyn canyon_sql::core::QueryParameter<'_>> { + match column { + #(#field_idents_cloned),*, + _ => None + } + } + } + } +} \ No newline at end of file diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index b4ea3fd9..c7f4c436 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -1,45 +1,32 @@ -// TODO: remember to remove this allows -#![allow(dead_code)] -#![allow(unused_variables)] -#![allow(unused_imports)] - extern crate proc_macro; extern crate regex; -mod canyon_entity_macro; #[cfg(feature = "migrations")] use canyon_macro::main_with_queries; mod canyon_macro; +mod canyon_entity_macro; mod query_operations; mod utils; mod canyon_mapper_macro; +mod foreignkeyable_macro; -use canyon_entity_macro::parse_canyon_entity_proc_macro_attr; use proc_macro::TokenStream as CompilerTokenStream; -use proc_macro2::{Ident, TokenStream}; use quote::quote; -use syn::{DeriveInput, Fields, Type, Visibility}; - -use query_operations::{ - read::generate_read_operations_tokens, - insert::generate_insert_tokens, - update::generate_update_tokens, - delete::generate_delete_tokens, - foreign_key::generate_find_by_fk_ops -}; +use syn::DeriveInput; use utils::{function_parser::FunctionParser, helpers, macro_tokens::MacroTokens}; use canyon_entities::{ entity::CanyonEntity, manager_builder::{ - generate_enum_with_fields, generate_enum_with_fields_values, generate_user_struct, + generate_enum_with_fields, + generate_enum_with_fields_values, }, - register_types::{CanyonRegisterEntity, CanyonRegisterEntityField}, - CANYON_REGISTER_ENTITIES, }; +use crate::canyon_entity_macro::generate_canyon_entity_tokens; use crate::canyon_mapper_macro::canyon_mapper_impl_tokens; -use crate::utils::helpers::filter_fields; +use crate::foreignkeyable_macro::foreignkeyable_impl_tokens; +use crate::query_operations::impl_crud_operations_trait_for_struct; /// Macro for handling the entry point to the program. /// @@ -116,35 +103,10 @@ pub fn canyon_tokio_test( } } -/// Generates the enums that contains the `TypeFields` and `TypeFieldsValues` -/// that the query-builder requires for construct its queries -#[proc_macro_derive(Fields)] -pub fn querybuilder_fields(input: CompilerTokenStream) -> CompilerTokenStream { - let entity_res = syn::parse::(input); - - if entity_res.is_err() { - return entity_res - .expect_err("Unexpected error parsing the struct") - .into_compile_error() - .into(); - } - - // No errors detected on the parsing, so we can safely unwrap the parse result - let entity = entity_res.expect("Unexpected error parsing the struct"); - let _generated_enum_type_for_fields = generate_enum_with_fields(&entity); - let _generated_enum_type_for_fields_values = generate_enum_with_fields_values(&entity); - quote! { - use canyon_sql::core::QueryParameter; - #_generated_enum_type_for_fields - #_generated_enum_type_for_fields_values - } - .into() -} - /// Takes data from the struct annotated with the `canyon_entity` macro to fill the Canyon Register /// where lives the data that Canyon needs to work. /// -/// Also, it's the responsible of generate the tokens for all the `Crud` methods available over +/// Also, it's the responsible for generate the tokens for all the `Crud` methods available over /// your type #[proc_macro_attribute] pub fn canyon_entity( @@ -153,68 +115,9 @@ pub fn canyon_entity( ) -> CompilerTokenStream { let attrs = syn::parse_macro_input!(_meta as syn::AttributeArgs); - let (table_name, schema_name, parsing_attribute_error) = - parse_canyon_entity_proc_macro_attr(attrs); - - let entity_res = syn::parse::(input); - - if entity_res.is_err() { - return entity_res - .expect_err("Unexpected error parsing the struct") - .into_compile_error() - .into(); - } - - // No errors detected on the parsing, so we can safely unwrap the parse result - let entity = entity_res.unwrap(); - let generated_user_struct = generate_user_struct(&entity); - - // The identifier of the entities - let mut new_entity = CanyonRegisterEntity::default(); - let e = Box::leak(entity.struct_name.to_string().into_boxed_str()); - new_entity.entity_name = e; - new_entity.entity_db_table_name = table_name.unwrap_or(Box::leak( - helpers::default_database_table_name_from_entity_name(e).into_boxed_str(), - )); - new_entity.user_schema_name = schema_name; - - // The entity fields - for field in entity.fields.iter() { - let mut new_entity_field = CanyonRegisterEntityField { - field_name: field.name.to_string(), - field_type: field.get_field_type_as_string().replace(' ', ""), - ..Default::default() - }; - - field - .attributes - .iter() - .for_each(|attr| new_entity_field.annotations.push(attr.get_as_string())); - - new_entity.entity_fields.push(new_entity_field); - } - - // Fill the register with the data of the attached struct - CANYON_REGISTER_ENTITIES - .lock() - .expect("Error acquiring Mutex guard on Canyon Entity macro") - .push(new_entity); - - // Assemble everything - let tokens = quote! { - #generated_user_struct - }; + - // Pass the result back to the compiler - if let Some(macro_error) = parsing_attribute_error { - quote! { - #macro_error - #generated_user_struct - } - .into() - } else { - tokens.into() - } + generate_canyon_entity_tokens(attrs, input).into() } /// Allows the implementors to auto-derive the `CrudOperations` trait, which defines the methods @@ -238,42 +141,6 @@ pub fn crud_operations(input: proc_macro::TokenStream) -> proc_macro::TokenStrea impl_crud_operations_trait_for_struct(¯o_data, table_schema_data) } -fn impl_crud_operations_trait_for_struct( - macro_data: &MacroTokens<'_>, - table_schema_data: String, -) -> proc_macro::TokenStream { - let mut crud_ops_tokens = TokenStream::new(); - let ty = macro_data.ty; - - let read_operations_tokens = generate_read_operations_tokens(macro_data, &table_schema_data); - let insert_tokens = generate_insert_tokens(macro_data, &table_schema_data); - let update_tokens = generate_update_tokens(macro_data, &table_schema_data); - let delete_tokens = generate_delete_tokens(macro_data, &table_schema_data); - - let crud_operations_tokens = quote! { // TODO: bring this directly from mod.rs or query_operations? - #read_operations_tokens - #insert_tokens - #update_tokens - #delete_tokens - }; - - crud_ops_tokens.extend(quote!{ - use canyon_sql::core::IntoResults; - - #[canyon_sql::macros::async_trait] // TODO: get rid of the async_trait - impl canyon_sql::crud::CrudOperations<#ty> for #ty { - #crud_operations_tokens - } - - impl canyon_sql::core::Transaction<#ty> for #ty {} - }); - - let foreign_key_ops_tokens = generate_find_by_fk_ops(macro_data, &table_schema_data); - crud_ops_tokens.extend(quote!{ #foreign_key_ops_tokens }); - - crud_ops_tokens.into() -} - /// proc-macro for annotate struct fields that holds a foreign key relation. /// /// So basically, if you have some `ForeignKey` attribute, annotate the parent @@ -283,50 +150,8 @@ fn impl_crud_operations_trait_for_struct( pub fn implement_foreignkeyable_for_type( input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - // Gets the data from the AST let ast: DeriveInput = syn::parse(input).unwrap(); - let ty = ast.ident; - - // Recovers the identifiers of the structs members - let fields = filter_fields(match ast.data { - syn::Data::Struct(ref s) => &s.fields, - _ => { - return syn::Error::new(ty.span(), "ForeignKeyable only works with Structs") - .to_compile_error() - .into() - } - }); - - let field_idents = fields.iter().map(|(_vis, ident)| { - let i = ident.to_string(); - quote! { - #i => Some(&self.#ident as &dyn canyon_sql::core::QueryParameter<'_>) - } - }); - let field_idents_cloned = field_idents.clone(); - - quote! { - /// Implementation of the trait `ForeignKeyable` for the type - /// calling this derive proc macro - impl canyon_sql::crud::bounds::ForeignKeyable for #ty { - fn get_fk_column(&self, column: &str) -> Option<&dyn canyon_sql::core::QueryParameter<'_>> { - match column { - #(#field_idents),*, - _ => None - } - } - } - /// Implementation of the trait `ForeignKeyable` for a reference of this type - /// calling this derive proc macro - impl canyon_sql::crud::bounds::ForeignKeyable<&Self> for &#ty { - fn get_fk_column<'a>(&self, column: &'a str) -> Option<&dyn canyon_sql::core::QueryParameter<'_>> { - match column { - #(#field_idents_cloned),*, - _ => None - } - } - } - }.into() + foreignkeyable_impl_tokens(ast).into() } #[proc_macro_derive(CanyonMapper)] @@ -335,3 +160,27 @@ pub fn implement_row_mapper_for_type(input: proc_macro::TokenStream) -> proc_mac canyon_mapper_impl_tokens(ast).into() } +/// Generates the enums that contains the `TypeFields` and `TypeFieldsValues` +/// that the query-builder requires for construct its queries +#[proc_macro_derive(Fields)] +pub fn querybuilder_fields(input: CompilerTokenStream) -> CompilerTokenStream { + let entity_res = syn::parse::(input); + + if entity_res.is_err() { + return entity_res + .expect_err("Unexpected error parsing the struct") + .into_compile_error() + .into(); + } + + // No errors detected on the parsing, so we can safely unwrap the parse result + let entity = entity_res.expect("Unexpected error parsing the struct"); + let _generated_enum_type_for_fields = generate_enum_with_fields(&entity); + let _generated_enum_type_for_fields_values = generate_enum_with_fields_values(&entity); + quote! { + use canyon_sql::core::QueryParameter; + #_generated_enum_type_for_fields + #_generated_enum_type_for_fields_values + } + .into() +} \ No newline at end of file diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index d9995450..16b39804 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -88,7 +88,7 @@ fn generate_delete_query_tokens( } mod __details { - use proc_macro2::Span; + use crate::query_operations::doc_comments; use crate::query_operations::macro_template::MacroOperationBuilder; use super::*; diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 47caeebd..5b70b6e3 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -1,6 +1,5 @@ use proc_macro2::TokenStream; use quote::quote; -use canyon_entities::manager_builder::generate_user_struct; use crate::utils::macro_tokens::MacroTokens; /// Generates the TokenStream for the _insert_result() CRUD operation diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index 662ed04f..8144208e 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -1,11 +1,10 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; -use syn::{parse_quote, Type}; pub struct MacroOperationBuilder { fn_name: Option, user_type: Option, - lifetime: bool, // bool true always will generate <'a> + lifetime: bool, self_as_ref: bool, input_param: Option, input_fwd_arg: Option, @@ -119,7 +118,7 @@ impl MacroOperationBuilder { fn get_as_method(&self) -> TokenStream { if self.self_as_ref { let self_ident = Ident::new("self", Span::call_site()); - quote! { &#self_ident, } + quote! { &#self_ident } } else { quote!{} } } @@ -245,10 +244,8 @@ impl MacroOperationBuilder { } fn get_forwarded_parameters(&self) -> TokenStream { - let forwarded_parameters = &self.forwarded_parameters; - if let Some(fwd_params) = &self.forwarded_parameters { - quote! { #forwarded_parameters } + quote! { #fwd_params } } else { quote! { &[] } } @@ -299,7 +296,7 @@ impl MacroOperationBuilder { } /// Generates the final `quote!` tokens for this operation - pub fn generate_tokens(&self) -> proc_macro2::TokenStream { + pub fn generate_tokens(&self) -> TokenStream { let doc_comments = &self .doc_comments .iter() @@ -335,13 +332,13 @@ impl MacroOperationBuilder { if !self.disable_mapping { base_body_tokens.extend(quote! { .into_results::<#ty>() }) }; - if self.with_no_result_value { // TODO: should we validate some combiantions? in the future, some of them can be hard to reason about + if self.with_no_result_value { // TODO: should we validate some combinations? in the future, some of them can be hard to reason about // like transaction_as_variable and with_no_result_value, they can't coexist base_body_tokens.extend(quote! {; Ok(()) }) } let body_tokens = if let Some(direct_err_return) = &self.direct_error_return { - let err = &self.direct_error_return; + let err = direct_err_return; quote! { Err( std::io::Error::new( @@ -367,6 +364,7 @@ impl MacroOperationBuilder { #(#doc_comments)* async fn #fn_name #generics( #as_method + #separate_self_params #fn_parameters #separate_params #input_param diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index 5aeb9d87..8e2ea720 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -1,3 +1,12 @@ +use proc_macro2::TokenStream; +use quote::quote; +use crate::query_operations::delete::generate_delete_tokens; +use crate::query_operations::foreign_key::generate_find_by_fk_ops; +use crate::query_operations::insert::generate_insert_tokens; +use crate::query_operations::read::generate_read_operations_tokens; +use crate::query_operations::update::generate_update_tokens; +use crate::utils::macro_tokens::MacroTokens; + pub mod delete; pub mod foreign_key; pub mod insert; @@ -7,3 +16,40 @@ pub mod update; mod doc_comments; mod macro_template; mod consts; + + +pub fn impl_crud_operations_trait_for_struct( + macro_data: &MacroTokens<'_>, + table_schema_data: String, +) -> proc_macro::TokenStream { + let mut crud_ops_tokens = TokenStream::new(); + let ty = macro_data.ty; + + let read_operations_tokens = generate_read_operations_tokens(macro_data, &table_schema_data); + let insert_tokens = generate_insert_tokens(macro_data, &table_schema_data); + let update_tokens = generate_update_tokens(macro_data, &table_schema_data); + let delete_tokens = generate_delete_tokens(macro_data, &table_schema_data); + + let crud_operations_tokens = quote! { + #read_operations_tokens + #insert_tokens + #update_tokens + #delete_tokens + }; + + crud_ops_tokens.extend(quote!{ + use canyon_sql::core::IntoResults; + + #[canyon_sql::macros::async_trait] // TODO: get rid of the async_trait + impl canyon_sql::crud::CrudOperations<#ty> for #ty { + #crud_operations_tokens + } + + impl canyon_sql::core::Transaction<#ty> for #ty {} + }); + + let foreign_key_ops_tokens = generate_find_by_fk_ops(macro_data, &table_schema_data); + crud_ops_tokens.extend(quote!{ #foreign_key_ops_tokens }); + + crud_ops_tokens.into() +} \ No newline at end of file diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 1a66c6cd..d2733951 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -1,10 +1,7 @@ -use canyon_entities::field_annotation::EntityFieldAnnotation; -use proc_macro2::{Span, TokenStream}; +use proc_macro2::TokenStream; use quote::quote; -use crate::query_operations::macro_template::MacroOperationBuilder; -use crate::utils::helpers::*; use crate::utils::macro_tokens::MacroTokens; // The API for export to the real macro implementation the generated macros for the READ operations diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index c619f458..72ea18f9 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -124,8 +124,8 @@ fn generate_update_query_tokens( } mod __details { - use quote::quote; - use proc_macro2::TokenStream; + + use crate::query_operations::consts::VOID_RET_TY; use crate::query_operations::doc_comments; use crate::query_operations::macro_template::MacroOperationBuilder; diff --git a/tests/tests_models/tournament.rs b/tests/tests_models/tournament.rs index d21c61cb..880076f4 100644 --- a/tests/tests_models/tournament.rs +++ b/tests/tests_models/tournament.rs @@ -6,10 +6,10 @@ use canyon_sql::{date_time::NaiveDate, macros::*}; pub struct Tournament { #[primary_key] id: i32, - #[foreign_key(table = "league", column = "id")] - league: i32, ext_id: i64, slug: String, start_date: NaiveDate, end_date: NaiveDate, + #[foreign_key(table = "league", column = "id")] + league: i32, } From 6b43556de3b8e990806a793b158074cc0f169161 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 28 Jan 2025 16:15:05 +0100 Subject: [PATCH 059/155] chore: moving DbConnection to db_connector:: --- .../src/connection/db_clients/mssql.rs | 5 +- .../src/connection/db_clients/mysql.rs | 5 +- .../src/connection/db_clients/postgresql.rs | 5 +- canyon_core/src/connection/db_connector.rs | 54 +++++++++++--- canyon_core/src/lib.rs | 2 +- canyon_core/src/query.rs | 70 ------------------- canyon_core/src/transaction.rs | 27 +++++++ canyon_crud/src/bounds.rs | 2 +- canyon_crud/src/crud.rs | 6 +- .../src/query_elements/query_builder.rs | 8 +-- canyon_macros/src/query_operations/read.rs | 7 +- canyon_migrations/src/migrations/handler.rs | 2 +- canyon_migrations/src/migrations/memory.rs | 2 +- canyon_migrations/src/migrations/processor.rs | 2 +- src/lib.rs | 4 +- 15 files changed, 96 insertions(+), 105 deletions(-) delete mode 100644 canyon_core/src/query.rs create mode 100644 canyon_core/src/transaction.rs diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs index 4ff727bb..e475c09b 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -2,9 +2,10 @@ use std::error::Error; #[cfg(feature = "mssql")] use async_std::net::TcpStream; -use crate::{query::DbConnection, query_parameters::QueryParameter, rows::CanyonRows}; +use crate::{query_parameters::QueryParameter, rows::CanyonRows}; use tiberius::Query; use crate::connection::database_type::DatabaseType; +use crate::connection::db_connector::DbConnection; /// A connection with a `SqlServer` database #[cfg(feature = "mssql")] @@ -18,7 +19,7 @@ impl DbConnection for SqlServerConnection { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> impl std::future::Future< - Output = Result>, + Output = Result>, > + Send { sqlserver_query_launcher::launch(stmt, params, self) } diff --git a/canyon_core/src/connection/db_clients/mysql.rs b/canyon_core/src/connection/db_clients/mysql.rs index 404071a6..6627d83b 100644 --- a/canyon_core/src/connection/db_clients/mysql.rs +++ b/canyon_core/src/connection/db_clients/mysql.rs @@ -2,11 +2,12 @@ use std::error::Error; #[cfg(feature = "mysql")] use mysql_async::Pool; -use crate::{query::DbConnection, query_parameters::QueryParameter, rows::CanyonRows}; +use crate::{query_parameters::QueryParameter, rows::CanyonRows}; use mysql_async::Row; use mysql_common::constants::ColumnType; use mysql_common::row; use crate::connection::database_type::DatabaseType; +use crate::connection::db_connector::DbConnection; /// A connection with a `Mysql` database #[cfg(feature = "mysql")] @@ -20,7 +21,7 @@ impl DbConnection for MysqlConnection { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> impl std::future::Future< - Output = Result>, + Output = Result>, > + Send { mysql_query_launcher::launch(stmt, params, self) } diff --git a/canyon_core/src/connection/db_clients/postgresql.rs b/canyon_core/src/connection/db_clients/postgresql.rs index 7eb17a97..88c90f64 100644 --- a/canyon_core/src/connection/db_clients/postgresql.rs +++ b/canyon_core/src/connection/db_clients/postgresql.rs @@ -1,9 +1,10 @@ use std::error::Error; -use crate::{query::DbConnection, query_parameters::QueryParameter, rows::CanyonRows}; +use crate::{query_parameters::QueryParameter, rows::CanyonRows}; #[cfg(feature = "postgres")] use tokio_postgres::Client; use crate::connection::database_type::DatabaseType; +use crate::connection::db_connector::DbConnection; /// A connection with a `PostgreSQL` database #[cfg(feature = "postgres")] @@ -18,7 +19,7 @@ impl DbConnection for PostgreSqlConnection { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> impl std::future::Future< - Output = Result>, + Output = Result>, > + Send { postgres_query_launcher::launch(stmt, params, self) } diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index a5a1d147..2ff6d69e 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -1,14 +1,50 @@ use std::error::Error; +use std::future::Future; use crate::connection::database_type::DatabaseType; use crate::connection::datasources::DatasourceConfig; use crate::connection::db_clients::mssql::SqlServerConnection; use crate::connection::db_clients::mysql::MysqlConnection; use crate::connection::db_clients::postgresql::PostgreSqlConnection; - -use crate::query::DbConnection; +use crate::connection::{find_datasource_by_name_or_try_default, get_database_connection_by_ds}; use crate::query_parameters::QueryParameter; use crate::rows::CanyonRows; + +pub trait DbConnection { + // TODO: guess that this is the trait that must remain sealed + fn launch<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> impl Future>> + Send; + + fn get_database_type(&self) -> Result>; +} + +/// This impl of [` DbConnection` ] for [`&str`] allows the client to use the exposed input types +/// on the public API that works with a generic parameter to refer to a database connection +/// directly with an [`&str`] that must match one of the datasources defined +/// within the user config file +impl DbConnection for &str { + fn launch<'a>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) + -> impl Future>> + Send + { + async move { + let sane_ds_name = if !self.is_empty() { + Some(*self) + } else { + None + }; + let conn = get_database_connection_by_ds(sane_ds_name).await?; + conn.launch(stmt, params).await + } + } + + fn get_database_type(&self) -> Result> { + Ok(find_datasource_by_name_or_try_default(Some(*self))?.get_db_type()) + } +} + /// The Canyon database connection handler. When the client's program /// starts, Canyon gets the information about the desired datasources, /// process them and generates a pool of 1 to 1 database connection for @@ -29,7 +65,7 @@ impl DbConnection for DatabaseConnection { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> impl std::future::Future< - Output = Result>, + Output = Result>, > + Send { async move { match self { @@ -55,9 +91,7 @@ impl DbConnection for &mut DatabaseConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl std::future::Future< - Output = Result>, - > + Send { + ) -> impl Future>> + Send { async move { match self { #[cfg(feature = "postgres")] @@ -146,7 +180,7 @@ mod connection_helpers { #[cfg(feature = "postgres")] pub async fn create_postgres_connection( datasource: &DatasourceConfig, - ) -> Result> { + ) -> Result> { let (user, password) = auth::extract_postgres_auth(&datasource.auth)?; let url = connection_string(user, password, datasource); @@ -168,7 +202,7 @@ mod connection_helpers { #[cfg(feature = "mssql")] pub async fn create_sqlserver_connection( datasource: &DatasourceConfig, - ) -> Result> { + ) -> Result> { use async_std::net::TcpStream; let mut tiberius_config = tiberius::Config::new(); @@ -179,7 +213,7 @@ mod connection_helpers { let auth_config = auth::extract_mssql_auth(&datasource.auth)?; tiberius_config.authentication(auth_config); - tiberius_config.trust_cert(); // TODO: this should be specificaly set via user input + tiberius_config.trust_cert(); // TODO: this should be specifically set via user input let tcp = TcpStream::connect(tiberius_config.get_addr()).await?; tcp.set_nodelay(true)?; @@ -194,7 +228,7 @@ mod connection_helpers { #[cfg(feature = "mysql")] pub async fn create_mysql_connection( datasource: &DatasourceConfig, - ) -> Result> { + ) -> Result> { use mysql_async::Pool; let (user, password) = auth::extract_mysql_auth(&datasource.auth)?; diff --git a/canyon_core/src/lib.rs b/canyon_core/src/lib.rs index be2260c1..4b78498c 100644 --- a/canyon_core/src/lib.rs +++ b/canyon_core/src/lib.rs @@ -14,7 +14,7 @@ pub extern crate lazy_static; pub mod column; pub mod connection; pub mod mapper; -pub mod query; +pub mod transaction; pub mod query_parameters; pub mod row; pub mod rows; diff --git a/canyon_core/src/query.rs b/canyon_core/src/query.rs deleted file mode 100644 index 1097d3e9..00000000 --- a/canyon_core/src/query.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::{ - connection::{ - datasources::DatasourceConfig, db_connector::DatabaseConnection, - get_database_connection_by_ds, - }, - query_parameters::QueryParameter, - rows::CanyonRows, -}; -use std::{fmt::Display, future::Future}; -use std::error::Error; -use crate::connection::database_type::DatabaseType; -use crate::connection::find_datasource_by_name_or_try_default; -// TODO: in order to avoid the tiberius transmute, we should define other method that takes the db_conn as a mut ref - -pub trait DbConnection { - // TODO: guess that this is the trait that must remain sealed - fn launch<'a>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send; - - // TODO: the querybuilder needs to know the underlying db type associated with self, so provide - // a method to obtain it - fn get_database_type(&self) -> Result>; -} - -/// This impl of [` DbConnection` ] for [`&str`] allows the client to use the exposed input types -/// on the public API that works with a generic parameter to refer to a database connection -/// directly with an [`&str`] that must match one of the datasources defined -/// within the user config file -impl DbConnection for &str { - fn launch<'a>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) - -> impl Future>> + Send - { - async move { - let sane_ds_name = if !self.is_empty() { - Some(*self) - } else { - None - }; - let conn = get_database_connection_by_ds(sane_ds_name).await?; - conn.launch(stmt, params).await - } - } - - fn get_database_type(&self) -> Result> { - Ok(find_datasource_by_name_or_try_default(Some(*self))?.get_db_type()) - } -} - -pub trait Transaction { - // provisional name - /// Performs a query against the targeted database by the selected or - /// the defaulted datasource, wrapping the resultant collection of entities - /// in [`super::rows::CanyonRows`] - fn query<'a, S, Z>( - stmt: S, - params: Z, - input: impl DbConnection + Send + 'a, - ) -> impl Future>> + Send - where - S: AsRef + Display + Sync + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a - { - async move { - input.launch(stmt.as_ref(), params.as_ref()).await - } - } -} diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs new file mode 100644 index 00000000..b6fe5a1e --- /dev/null +++ b/canyon_core/src/transaction.rs @@ -0,0 +1,27 @@ +use crate::{ + query_parameters::QueryParameter, + rows::CanyonRows, +}; +use std::{fmt::Display, future::Future}; +use std::error::Error; +use crate::connection::db_connector::DbConnection; + +pub trait Transaction { + // provisional name + /// Performs a query against the targeted database by the selected or + /// the defaulted datasource, wrapping the resultant collection of entities + /// in [`super::rows::CanyonRows`] + fn query<'a, S, Z>( + stmt: S, + params: Z, + input: impl DbConnection + Send + 'a, + ) -> impl Future>> + Send + where + S: AsRef + Display + Sync + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a + { + async move { + input.launch(stmt.as_ref(), params.as_ref()).await + } + } +} diff --git a/canyon_crud/src/bounds.rs b/canyon_crud/src/bounds.rs index 08be5e25..a15d4e4d 100644 --- a/canyon_crud/src/bounds.rs +++ b/canyon_crud/src/bounds.rs @@ -1,4 +1,4 @@ -use canyon_core::{mapper::RowMapper, query::Transaction, query_parameters::QueryParameter}; +use canyon_core::{mapper::RowMapper, transaction::Transaction, query_parameters::QueryParameter}; use crate::crud::CrudOperations; diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 8cd0a03a..c8dafd91 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -1,10 +1,10 @@ use crate::query_elements::query_builder::{ - DeleteQueryBuilder, SelectQueryBuilder, UpdateQueryBuilder, + SelectQueryBuilder, UpdateQueryBuilder, DeleteQueryBuilder }; use async_trait::async_trait; -use canyon_core::query::DbConnection; use canyon_core::query_parameters::QueryParameter; -use canyon_core::{mapper::RowMapper, query::Transaction}; +use canyon_core::{mapper::RowMapper, transaction::Transaction}; +use canyon_core::connection::db_connector::DbConnection; /// *CrudOperations* it's the core part of Canyon-SQL. /// diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index b3bbf284..7b758bbc 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -4,16 +4,16 @@ use crate::{ query_elements::query::Query, Operator, }; -use canyon_core::connection::{database_type::DatabaseType, get_database_config, DATASOURCES}; -use canyon_core::query::DbConnection; -use canyon_core::{mapper::RowMapper, query::Transaction, query_parameters::QueryParameter}; +use canyon_core::connection::database_type::DatabaseType; +use canyon_core::{mapper::RowMapper, transaction::Transaction, query_parameters::QueryParameter}; use std::fmt::Debug; use std::marker::PhantomData; +use canyon_core::connection::db_connector::DbConnection; /// Contains the elements that makes part of the formal declaration /// of the behaviour of the Canyon-SQL QueryBuilder pub mod ops { - use canyon_core::{mapper::RowMapper, query::Transaction, query_parameters::QueryParameter}; + use canyon_core::{mapper::RowMapper, transaction::Transaction, query_parameters::QueryParameter}; use crate::crud::CrudOperations; diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index d2733951..2a6cf33a 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -1,4 +1,3 @@ - use proc_macro2::TokenStream; use quote::quote; @@ -239,7 +238,7 @@ mod __details { MacroOperationBuilder::new() .fn_name("count") .user_type(ty) - .return_type(&syn::Ident::new("i64", Span::call_site())) // TODO: into ident or take by value + .return_type(&Ident::new("i64", Span::call_site())) // TODO: into ident or take by value .add_doc_comment( "Performs a COUNT(*) query over the table related to the entity T'", ) @@ -262,7 +261,7 @@ mod __details { .fn_name("count_with") .user_type(ty) .with_input_param() - .return_type(&syn::Ident::new("i64", Span::call_site())) // TODO: into ident or take by value + .return_type(&Ident::new("i64", Span::call_site())) // TODO: into ident or take by value .add_doc_comment( "Performs a COUNT(*) query over the table related to the entity T'", ) @@ -340,9 +339,7 @@ mod __details { mod macro_builder_read_ops_tests { use super::__details::{count_generators::*, find_all_generators::*, pk_generators::*}; use crate::query_operations::consts::*; - use proc_macro2::Span; use quote::quote; - use syn::Ident; const SELECT_ALL_STMT: &str = "SELECT * FROM public.user"; // TODO: introduce the const_format crate const COUNT_STMT: &str = "SELECT COUNT(*) FROM public.user"; diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index 44ceee04..a83593ba 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -3,7 +3,7 @@ use canyon_core::{ connection::{ datasources::Migrations as MigrationsStatus, db_connector::DatabaseConnection, DATASOURCES, }, - query::Transaction, + transaction::Transaction, row::{Row, RowOperations}, rows::CanyonRows, }; diff --git a/canyon_migrations/src/migrations/memory.rs b/canyon_migrations/src/migrations/memory.rs index 351d6aa9..e82e0d70 100644 --- a/canyon_migrations/src/migrations/memory.rs +++ b/canyon_migrations/src/migrations/memory.rs @@ -1,6 +1,6 @@ use crate::constants; use canyon_core::connection::db_connector::DatabaseConnection; -use canyon_core::query::Transaction; +use canyon_core::transaction::Transaction; use canyon_crud::{DatabaseType, DatasourceConfig}; use regex::Regex; use std::collections::HashMap; diff --git a/canyon_migrations/src/migrations/processor.rs b/canyon_migrations/src/migrations/processor.rs index ff0e9bc0..35ee477f 100644 --- a/canyon_migrations/src/migrations/processor.rs +++ b/canyon_migrations/src/migrations/processor.rs @@ -1,7 +1,7 @@ //! File that contains all the datatypes and logic to perform the migrations //! over a target database use async_trait::async_trait; -use canyon_core::query::Transaction; +use canyon_core::transaction::Transaction; use canyon_crud::DatabaseType; use regex::Regex; use std::collections::HashMap; diff --git a/src/lib.rs b/src/lib.rs index 6c085d9b..493e1056 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,8 +42,8 @@ pub mod connection { pub mod core { pub use canyon_core::mapper::*; - pub use canyon_core::query::DbConnection; - pub use canyon_core::query::Transaction; + pub use canyon_core::connection::db_connector::DbConnection; + pub use canyon_core::transaction::Transaction; pub use canyon_core::query_parameters::QueryParameter; pub use canyon_core::rows::CanyonRows; } From dc50e8d0164f6618e16c2b633936feaebd17e693 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 28 Jan 2025 17:36:56 +0100 Subject: [PATCH 060/155] feat: removed #[async_trait] from all the codebase in favour of the impl Future --- Cargo.toml | 1 - canyon_core/Cargo.toml | 1 - canyon_crud/Cargo.toml | 1 - canyon_crud/src/crud.rs | 54 ++++---- canyon_crud/src/lib.rs | 2 - canyon_macros/src/query_operations/consts.rs | 2 + .../src/query_operations/doc_comments.rs | 54 ++++---- .../src/query_operations/foreign_key.rs | 130 +++++++++--------- .../src/query_operations/macro_template.rs | 16 ++- canyon_macros/src/query_operations/mod.rs | 1 - canyon_migrations/Cargo.toml | 3 - canyon_migrations/src/migrations/processor.rs | 120 ++++++++-------- src/lib.rs | 1 - 13 files changed, 199 insertions(+), 187 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index de5e7c65..4fd57ff5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,6 @@ indexmap = "1.9.1" async-std = "1.12.0" lazy_static = "1.4.0" toml = "0.7.3" -async-trait = "0.1.68" walkdir = "2.3.3" regex = "1.9.3" partialdebug = "0.2.0" diff --git a/canyon_core/Cargo.toml b/canyon_core/Cargo.toml index 0ed42be7..12f7c7bb 100644 --- a/canyon_core/Cargo.toml +++ b/canyon_core/Cargo.toml @@ -17,7 +17,6 @@ mysql_common = { workspace = true, optional = true } chrono = { workspace = true } async-std = { workspace = true, optional = true } -async-trait = { workspace = true } regex = { workspace = true } tokio = { workspace = true } diff --git a/canyon_crud/Cargo.toml b/canyon_crud/Cargo.toml index b774a91d..a4c37b0a 100644 --- a/canyon_crud/Cargo.toml +++ b/canyon_crud/Cargo.toml @@ -18,7 +18,6 @@ mysql_async = { workspace = true, optional = true } mysql_common = { workspace = true, optional = true } chrono = { workspace = true } -async-trait = { workspace = true } regex = { workspace = true } [features] diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index c8dafd91..d193390c 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -1,7 +1,8 @@ +use std::error::Error; +use std::future::Future; use crate::query_elements::query_builder::{ SelectQueryBuilder, UpdateQueryBuilder, DeleteQueryBuilder }; -use async_trait::async_trait; use canyon_core::query_parameters::QueryParameter; use canyon_core::{mapper::RowMapper, transaction::Transaction}; use canyon_core::connection::db_connector::DbConnection; @@ -21,22 +22,21 @@ use canyon_core::connection::db_connector::DbConnection; /// See it's definition and docs to see the implementations. /// Also, you can find the written macro-code that performs the auto-mapping /// in the *canyon_sql_root::canyon_macros* crates, on the root of this project. -#[async_trait] pub trait CrudOperations: Transaction where T: CrudOperations + RowMapper, { - async fn find_all() -> Result, Box<(dyn std::error::Error + Send + Sync)>>; + fn find_all() -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send; - async fn find_all_with<'a, I>( + fn find_all_with<'a, I>( input: I, - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send where I: DbConnection + Send + 'a; - async fn find_all_unchecked() -> Vec; + fn find_all_unchecked() -> impl Future> + Send; - async fn find_all_unchecked_with<'a, I>(input: I) -> Vec + fn find_all_unchecked_with<'a, I>(input: I) -> impl Future> + Send where I: DbConnection + Send + 'a; @@ -46,51 +46,51 @@ where where I: DbConnection + Send + 'a; - async fn count() -> Result>; + fn count() -> impl Future>> + Send; - async fn count_with<'a, I>( + fn count_with<'a, I>( input: I, - ) -> Result> + ) -> impl Future>> + Send where I: DbConnection + Send + 'a; - async fn find_by_pk<'a>( + fn find_by_pk<'a>( value: &'a dyn QueryParameter<'a>, - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>>; + ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send; - async fn find_by_pk_with<'a, I>( + fn find_by_pk_with<'a, I>( value: &'a dyn QueryParameter<'a>, input: I, - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send where I: DbConnection + Send + 'a; - async fn insert(&mut self) -> Result<(), Box>; + fn insert(&mut self) -> impl Future>> + Send; - async fn insert_with<'a, I>( + fn insert_with<'a, I>( &mut self, input: I, - ) -> Result<(), Box> + ) -> impl Future>> + Send where I: DbConnection + Send + 'a; - async fn multi_insert<'a>( + fn multi_insert<'a>( instances: &'a mut [&'a mut T], - ) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'a)>>; + ) -> impl Future>> + Send; - async fn multi_insert_with<'a, I>( + fn multi_insert_with<'a, I>( instances: &'a mut [&'a mut T], input: I, - ) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'a)>> + ) -> impl Future>> + Send where I: DbConnection + Send + 'a; - async fn update(&self) -> Result<(), Box>; + fn update(&self) -> impl Future>> + Send; - async fn update_with<'a, I>( + fn update_with<'a, I>( &self, input: I, - ) -> Result<(), Box> + ) -> impl Future>> + Send where I: DbConnection + Send + 'a; @@ -100,12 +100,12 @@ where where I: DbConnection + Send + 'a; - async fn delete(&self) -> Result<(), Box>; + fn delete(&self) -> impl Future>> + Send; - async fn delete_with<'a, I>( + fn delete_with<'a, I>( &self, input: I, - ) -> Result<(), Box> + ) -> impl Future>> + Send where I: DbConnection + Send + 'a; diff --git a/canyon_crud/src/lib.rs b/canyon_crud/src/lib.rs index dfee5da3..89695fc7 100644 --- a/canyon_crud/src/lib.rs +++ b/canyon_crud/src/lib.rs @@ -1,5 +1,3 @@ -pub extern crate async_trait; - pub mod bounds; pub mod crud; pub mod query_elements; diff --git a/canyon_macros/src/query_operations/consts.rs b/canyon_macros/src/query_operations/consts.rs index 83616a53..39dd64b6 100644 --- a/canyon_macros/src/query_operations/consts.rs +++ b/canyon_macros/src/query_operations/consts.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use std::cell::RefCell; use proc_macro2::{Span, TokenStream}; diff --git a/canyon_macros/src/query_operations/doc_comments.rs b/canyon_macros/src/query_operations/doc_comments.rs index 322417a4..fc184efc 100644 --- a/canyon_macros/src/query_operations/doc_comments.rs +++ b/canyon_macros/src/query_operations/doc_comments.rs @@ -1,35 +1,37 @@ +#![allow(dead_code)] + pub const SELECT_ALL_BASE_DOC_COMMENT: &str = - "/// Performs a `SELECT * FROM table_name`, where `table_name` it's \ - /// the name of your entity but converted to the corresponding \ - /// database convention. P.ej. PostgreSQL prefers table names declared \ - /// with snake_case identifiers."; + "Performs a `SELECT * FROM table_name`, where `table_name` it's \ + the name of your entity but converted to the corresponding \ + database convention. P.ej. PostgreSQL prefers table names declared \ + with snake_case identifiers."; pub const SELECT_QUERYBUILDER_DOC_COMMENT: &str = - "/// Generates a [`canyon_sql::query::SelectQueryBuilder`] \ - /// that allows you to customize the query by adding parameters and constrains dynamically. \ - /// \ - /// It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your \ - /// entity but converted to the corresponding database convention, \ - /// unless concrete values are set on the available parameters of the \ - /// `canyon_macro => table_name = \"table_name\", schema = \"schema\")`"; + "Generates a [`canyon_sql::query::SelectQueryBuilder`] \ + that allows you to customize the query by adding parameters and constrains dynamically. \ + \ + It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your \ + entity but converted to the corresponding database convention, \ + unless concrete values are set on the available parameters of the \ + `canyon_macro => table_name = \"table_name\", schema = \"schema\")`"; -pub const FIND_BY_PK: &str = "/// Finds an element on the queried table that matches the \ - /// value of the field annotated with the `primary_key` attribute, \ - /// filtering by the column that it's declared as the primary \ - /// key on the database. \ - /// \ - /// *NOTE:* This operation it's only available if the [`CanyonEntity`] contains \ - /// some field declared as primary key. \ - /// \ - /// *returns:* a [`Result, Error>`], wrapping a possible failure \ - /// querying the database, or, if no errors happens, a success containing \ - /// and Option with the data found wrapped in the Some(T) variant, \ - /// or None if the value isn't found on the table."; +pub const FIND_BY_PK: &str = "Finds an element on the queried table that matches the \ + value of the field annotated with the `primary_key` attribute, \ + filtering by the column that it's declared as the primary \ + key on the database. \ + \ + *NOTE:* This operation it's only available if the [`CanyonEntity`] contains \ + some field declared as primary key. \ + \ + *returns:* a [`Result, Error>`], wrapping a possible failure \ + querying the database, or, if no errors happens, a success containing \ + and Option with the data found wrapped in the Some(T) variant, \ + or None if the value isn't found on the table."; pub const DS_ADVERTISING: &str = - "/// The query it's made against the database with the configured datasource \ - /// described in the configuration file, and selected with the [`&str`] \ - /// passed as parameter."; + "The query it's made against the database with the configured datasource \ + described in the configuration file, and selected with the [`&str`] \ + passed as parameter."; pub const DELETE: &str = "Deletes from a database entity the row that matches the current instance of a T type based on the actual value of the primary diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index 811f41c9..016d13d5 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -6,7 +6,7 @@ use crate::utils::macro_tokens::MacroTokens; pub fn generate_find_by_fk_ops(macro_data: &MacroTokens<'_>, table_schema_data: &str) -> TokenStream { let ty = ¯o_data.ty; - + // Search by foreign (d) key as Vec, cause Canyon supports multiple fields having FK annotation let _search_by_fk_tokens: Vec<(TokenStream, TokenStream)> = generate_find_by_foreign_key_tokens(macro_data); @@ -26,7 +26,7 @@ pub fn generate_find_by_fk_ops(macro_data: &MacroTokens<'_>, table_schema_data: &format!("{}FkOperations", &ty.to_string()), proc_macro2::Span::call_site(), ); - + if search_by_reverse_fk_tokens.is_empty() { return quote!{}; // early guard } @@ -37,12 +37,10 @@ pub fn generate_find_by_fk_ops(macro_data: &MacroTokens<'_>, table_schema_data: /// because it's just impossible with the actual system (where the methods /// are generated dynamically based on some properties of the `foreign_key` /// annotation) - #[canyon_sql::macros::async_trait] pub trait #fk_trait_ident<#ty> { #(#fk_method_signatures)* #(#rev_fk_method_signatures)* } - #[canyon_sql::macros::async_trait] impl #fk_trait_ident<#ty> for #ty where #ty: std::fmt::Debug + @@ -75,18 +73,18 @@ fn generate_find_by_foreign_key_tokens( // Generate and identifier for the method based on the convention of "search_related_types" // where types is a placeholder for the plural name of the type referenced let method_name_ident = - proc_macro2::Ident::new(&method_name, proc_macro2::Span::call_site()); - let method_name_ident_with = proc_macro2::Ident::new( + Ident::new(&method_name, proc_macro2::Span::call_site()); + let method_name_ident_with = Ident::new( &format!("{}_with", &method_name), proc_macro2::Span::call_site(), ); let quoted_method_signature: TokenStream = quote! { - async fn #method_name_ident<'a>(&self) -> - Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + fn #method_name_ident<'a>(&self) -> + impl std::future::Future, Box<(dyn std::error::Error + Sync + Send + 'a)>>> + Send }; let quoted_with_method_signature: TokenStream = quote! { - async fn #method_name_ident_with<'a, I>(&self, input: I) -> - Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + fn #method_name_ident_with<'a, I>(&self, input: I) -> + impl std::future::Future, Box<(dyn std::error::Error + Sync + Send + 'a)>>> + Send where I: canyon_sql::core::DbConnection + Send + 'a }; @@ -109,13 +107,15 @@ fn generate_find_by_foreign_key_tokens( quote! { /// Searches the parent entity (if exists) for this type #quoted_method_signature { - let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( - #stmt, - &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], - "" - ).await?; - - #result_handler + async move { + let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( + #stmt, + &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], + "" + ).await?; + + #result_handler + } } }, )); @@ -125,13 +125,15 @@ fn generate_find_by_foreign_key_tokens( quote! { /// Searches the parent entity (if exists) for this type with the specified datasource #quoted_with_method_signature { - let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( - #stmt, - &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], - input - ).await?; - - #result_handler + async move { + let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( + #stmt, + &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], + input + ).await?; + + #result_handler + } } }, )); @@ -159,18 +161,18 @@ fn generate_find_by_reverse_foreign_key_tokens( // Generate and identifier for the method based on the convention of "search_by__" (note the double underscore) // plus the 'table_name' property of the ForeignKey annotation let method_name_ident = - proc_macro2::Ident::new(&method_name, proc_macro2::Span::call_site()); - let method_name_ident_with = proc_macro2::Ident::new( + Ident::new(&method_name, proc_macro2::Span::call_site()); + let method_name_ident_with = Ident::new( &format!("{}_with", &method_name), proc_macro2::Span::call_site(), ); let quoted_method_signature: TokenStream = quote! { - async fn #method_name_ident<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send>(value: &F) -> - Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + fn #method_name_ident<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send>(value: &F) -> + impl std::future::Future, Box<(dyn std::error::Error + Sync + Send + 'a)>>> + Send }; let quoted_with_method_signature: TokenStream = quote! { - async fn #method_name_ident_with<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send, I> - (value: &F, input: I) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + fn #method_name_ident_with<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send, I> (value: &F, input: I) + -> impl std::future::Future, Box<(dyn std::error::Error + Sync + Send + 'a)>>> + Send where I: canyon_sql::core::DbConnection + Send + 'a }; @@ -183,22 +185,24 @@ fn generate_find_by_reverse_foreign_key_tokens( /// performs a search to find the children that belong to that concrete parent. #quoted_method_signature { - let lookage_value = value.get_fk_column(#column) - .expect(format!( - "Column: {:?} not found in type: {:?}", #column, #table - ).as_str()); - - let stmt = format!( - "SELECT * FROM {} WHERE {} = $1", - #table_schema_data, - format!("\"{}\"", #f_ident).as_str() - ); - - Ok(<#ty as canyon_sql::core::Transaction<#ty>>::query( - stmt, - &[lookage_value], - "" - ).await?.into_results::<#ty>()) + async move { + let lookage_value = value.get_fk_column(#column) + .expect(format!( + "Column: {:?} not found in type: {:?}", #column, #table + ).as_str()); + + let stmt = format!( + "SELECT * FROM {} WHERE {} = $1", + #table_schema_data, + format!("\"{}\"", #f_ident).as_str() + ); + + Ok(<#ty as canyon_sql::core::Transaction<#ty>>::query( + stmt, + &[lookage_value], + "" + ).await?.into_results::<#ty>()) + } } }, )); @@ -211,22 +215,24 @@ fn generate_find_by_reverse_foreign_key_tokens( /// with the specified datasource. #quoted_with_method_signature { - let lookage_value = value.get_fk_column(#column) - .expect(format!( - "Column: {:?} not found in type: {:?}", #column, #table - ).as_str()); - - let stmt = format!( - "SELECT * FROM {} WHERE {} = $1", - #table_schema_data, - format!("\"{}\"", #f_ident).as_str() - ); - - Ok(<#ty as canyon_sql::core::Transaction<#ty>>::query( - stmt, - &[lookage_value], - input - ).await?.into_results::<#ty>()) + async move { + let lookage_value = value.get_fk_column(#column) + .expect(format!( + "Column: {:?} not found in type: {:?}", #column, #table + ).as_str()); + + let stmt = format!( + "SELECT * FROM {} WHERE {} = $1", + #table_schema_data, + format!("\"{}\"", #f_ident).as_str() + ); + + Ok(<#ty as canyon_sql::core::Transaction<#ty>>::query( + stmt, + &[lookage_value], + input + ).await?.into_results::<#ty>()) + } } }, )); diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index 8144208e..466b991b 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -177,8 +177,8 @@ impl MacroOperationBuilder { quote! { #container_ret_type<#organic_ret_type> } }; - match &self.with_unwrap { - // TODO: distinguish collection from 1 results + let expected_data = match &self.with_unwrap { + // TODO: distinguish collection from rows with only results true => quote! { #ret_type }, false => { let err_variant = if self.lifetime { @@ -189,7 +189,9 @@ impl MacroOperationBuilder { quote! { Result<#ret_type, #err_variant> } } - } + }; + + quote! { impl std::future::Future + Send } } fn get_where_clause_bounds(&self) -> TokenStream { @@ -362,7 +364,7 @@ impl MacroOperationBuilder { quote! { #(#doc_comments)* - async fn #fn_name #generics( + fn #fn_name #generics( #as_method #separate_self_params #fn_parameters @@ -371,8 +373,10 @@ impl MacroOperationBuilder { ) -> #return_type #where_clause { - #body_tokens - #unwrap + async move { + #body_tokens + #unwrap + } } } } diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index 8e2ea720..a53a4489 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -40,7 +40,6 @@ pub fn impl_crud_operations_trait_for_struct( crud_ops_tokens.extend(quote!{ use canyon_sql::core::IntoResults; - #[canyon_sql::macros::async_trait] // TODO: get rid of the async_trait impl canyon_sql::crud::CrudOperations<#ty> for #ty { #crud_operations_tokens } diff --git a/canyon_migrations/Cargo.toml b/canyon_migrations/Cargo.toml index b1b9c915..18c79aa4 100644 --- a/canyon_migrations/Cargo.toml +++ b/canyon_migrations/Cargo.toml @@ -20,9 +20,6 @@ tiberius = { workspace = true, optional = true } mysql_async = { workspace = true, optional = true } mysql_common = { workspace = true, optional = true } - -async-trait = { workspace = true } - regex = { workspace = true } partialdebug = { workspace = true } walkdir = { workspace = true } diff --git a/canyon_migrations/src/migrations/processor.rs b/canyon_migrations/src/migrations/processor.rs index 35ee477f..267be2bf 100644 --- a/canyon_migrations/src/migrations/processor.rs +++ b/canyon_migrations/src/migrations/processor.rs @@ -1,11 +1,11 @@ //! File that contains all the datatypes and logic to perform the migrations //! over a target database -use async_trait::async_trait; use canyon_core::transaction::Transaction; use canyon_crud::DatabaseType; use regex::Regex; use std::collections::HashMap; use std::fmt::Debug; +use std::future::Future; use std::ops::Not; use crate::canyon_crud::DatasourceConfig; @@ -24,10 +24,13 @@ use canyon_entities::register_types::{CanyonRegisterEntity, CanyonRegisterEntity /// Rust source code managed by Canyon, for successfully make the migrations #[derive(Debug, Default)] pub struct MigrationsProcessor { - operations: Vec>, - set_primary_key_operations: Vec>, - drop_primary_key_operations: Vec>, - constraints_operations: Vec>, + table_operations: Vec, + column_operations: Vec, + set_primary_key_operations: Vec, + drop_primary_key_operations: Vec, + constraints_table_operations: Vec, + constraints_column_operations: Vec, + constraints_sequence_operations: Vec, } impl Transaction for MigrationsProcessor {} @@ -67,7 +70,7 @@ impl MigrationsProcessor { db_type, ); - // For each field (column) on the this canyon register entity + // For each field (column) on the canyon register entity for canyon_register_field in canyon_register_entity.entity_fields { let current_column_metadata = MigrationsHelper::get_current_column_metadata( canyon_register_field.field_name.clone(), @@ -107,7 +110,10 @@ impl MigrationsProcessor { } } - for operation in &self.operations { + for operation in &self.table_operations { + operation.generate_sql(datasource).await; // This should be moved again to runtime + } + for operation in &self.column_operations { operation.generate_sql(datasource).await; // This should be moved again to runtime } for operation in &self.drop_primary_key_operations { @@ -116,7 +122,13 @@ impl MigrationsProcessor { for operation in &self.set_primary_key_operations { operation.generate_sql(datasource).await; // This should be moved again to runtime } - for operation in &self.constraints_operations { + for operation in &self.constraints_table_operations { + operation.generate_sql(datasource).await; // This should be moved again to runtime + } + for operation in &self.constraints_column_operations { + operation.generate_sql(datasource).await; // This should be moved again to runtime + } + for operation in &self.constraints_sequence_operations { operation.generate_sql(datasource).await; // This should be moved again to runtime } // TODO Still pending to decouple de executions of cargo check to skip the process if this @@ -154,19 +166,19 @@ impl MigrationsProcessor { /// Generates a database agnostic query to change the name of a table fn create_table(&mut self, table_name: String, entity_fields: Vec) { - self.operations.push(Box::new(TableOperation::CreateTable( + self.table_operations.push(TableOperation::CreateTable( table_name, entity_fields, - ))); + )); } /// Generates a database agnostic query to change the name of a table fn table_rename(&mut self, old_table_name: String, new_table_name: String) { - self.operations - .push(Box::new(TableOperation::AlterTableName( + self.table_operations + .push(TableOperation::AlterTableName( old_table_name, new_table_name, - ))); + )); } // Creates or modify (currently only datatype) a column for a given canyon register entity field @@ -216,7 +228,7 @@ impl MigrationsProcessor { canyon_register_entity_field: CanyonRegisterEntityField, current_column_metadata: Option<&ColumnMetadata>, ) { - // If we do not retrieve data for this database column, it does not exist yet + // If we do not retrieve data for this database column, it does not exist yet, // and therefore it has to be created if current_column_metadata.is_none() { self.create_column( @@ -246,10 +258,10 @@ impl MigrationsProcessor { } fn delete_column(&mut self, table_name: &str, column_name: String) { - self.operations.push(Box::new(ColumnOperation::DeleteColumn( + self.column_operations.push(ColumnOperation::DeleteColumn( table_name.to_string(), column_name, - ))); + )); } #[cfg(feature = "mssql")] @@ -259,38 +271,38 @@ impl MigrationsProcessor { column_name: String, column_datatype: String, ) { - self.operations - .push(Box::new(ColumnOperation::DropNotNullBeforeDropColumn( + self.column_operations + .push(ColumnOperation::DropNotNullBeforeDropColumn( table_name.to_string(), column_name, column_datatype, - ))); + )); } fn create_column(&mut self, table_name: String, field: CanyonRegisterEntityField) { - self.operations - .push(Box::new(ColumnOperation::CreateColumn(table_name, field))); + self.column_operations + .push(ColumnOperation::CreateColumn(table_name, field)); } fn change_column_datatype(&mut self, table_name: String, field: CanyonRegisterEntityField) { - self.operations - .push(Box::new(ColumnOperation::AlterColumnType( + self.column_operations + .push(ColumnOperation::AlterColumnType( table_name, field, - ))); + )); } fn set_not_null(&mut self, table_name: String, field: CanyonRegisterEntityField) { - self.operations - .push(Box::new(ColumnOperation::AlterColumnSetNotNull( + self.column_operations + .push(ColumnOperation::AlterColumnSetNotNull( table_name, field, - ))); + )); } fn drop_not_null(&mut self, table_name: String, field: CanyonRegisterEntityField) { - self.operations - .push(Box::new(ColumnOperation::AlterColumnDropNotNull( + self.column_operations + .push(ColumnOperation::AlterColumnDropNotNull( table_name, field, - ))); + )); } fn add_constraints( @@ -342,14 +354,14 @@ impl MigrationsProcessor { column_to_reference: String, canyon_register_entity_field: &CanyonRegisterEntityField, ) { - self.constraints_operations - .push(Box::new(TableOperation::AddTableForeignKey( + self.constraints_table_operations + .push(TableOperation::AddTableForeignKey( entity_name.to_string(), foreign_key_name, canyon_register_entity_field.field_name.clone(), table_to_reference, column_to_reference, - ))); + )); } fn add_primary_key( @@ -358,25 +370,25 @@ impl MigrationsProcessor { canyon_register_entity_field: CanyonRegisterEntityField, ) { self.set_primary_key_operations - .push(Box::new(TableOperation::AddTablePrimaryKey( + .push(TableOperation::AddTablePrimaryKey( entity_name.to_string(), canyon_register_entity_field, - ))); + )); } #[cfg(feature = "postgres")] fn add_identity(&mut self, entity_name: &str, field: CanyonRegisterEntityField) { - self.constraints_operations - .push(Box::new(ColumnOperation::AlterColumnAddIdentity( + self.constraints_column_operations + .push(ColumnOperation::AlterColumnAddIdentity( entity_name.to_string(), field.clone(), - ))); + )); - self.constraints_operations - .push(Box::new(SequenceOperation::ModifySequence( + self.constraints_sequence_operations + .push(SequenceOperation::ModifySequence( entity_name.to_string(), field, - ))); + )); } fn add_modify_or_remove_constraints( @@ -420,7 +432,7 @@ impl MigrationsProcessor { } } } - // Case when field doesn't contains a primary key annotation, but there is one in the database column + // Case when field doesn't contain a primary key annotation, but there is one in the database column else if !field_is_primary_key && current_column_metadata.primary_key_info.is_some() { Self::drop_primary_key( self, @@ -543,10 +555,10 @@ impl MigrationsProcessor { fn drop_primary_key(&mut self, entity_name: &str, primary_key_name: String) { self.drop_primary_key_operations - .push(Box::new(TableOperation::DeleteTablePrimaryKey( + .push(TableOperation::DeleteTablePrimaryKey( entity_name.to_string(), primary_key_name, - ))); + )); } #[cfg(feature = "postgres")] @@ -555,20 +567,20 @@ impl MigrationsProcessor { entity_name: &str, canyon_register_entity_field: CanyonRegisterEntityField, ) { - self.constraints_operations - .push(Box::new(ColumnOperation::AlterColumnDropIdentity( + self.constraints_column_operations + .push(ColumnOperation::AlterColumnDropIdentity( entity_name.to_string(), canyon_register_entity_field, - ))); + )); } fn delete_foreign_key(&mut self, entity_name: &str, constrain_name: String) { - self.constraints_operations - .push(Box::new(TableOperation::DeleteTableForeignKey( + self.constraints_table_operations + .push(TableOperation::DeleteTableForeignKey( // table_with_foreign_key,constrain_name entity_name.to_string(), constrain_name, - ))); + )); } /// Make the detected migrations for the next Canyon-SQL run @@ -755,10 +767,9 @@ mod migrations_helper_tests { } } -/// Trait that enables implementors to generate the migration queries -#[async_trait] + trait DatabaseOperation: Debug { - async fn generate_sql(&self, datasource: &DatasourceConfig); + fn generate_sql(&self, datasource: &DatasourceConfig) -> impl Future; } /// Helper to relate the operations that Canyon should do when it's managing a schema @@ -780,7 +791,6 @@ enum TableOperation { impl Transaction for TableOperation {} -#[async_trait] impl DatabaseOperation for TableOperation { async fn generate_sql(&self, datasource: &DatasourceConfig) { let db_type = datasource.get_db_type(); @@ -933,7 +943,6 @@ enum ColumnOperation { impl Transaction for ColumnOperation {} -#[async_trait] impl DatabaseOperation for ColumnOperation { async fn generate_sql(&self, datasource: &DatasourceConfig) { let db_type = datasource.get_db_type(); @@ -1040,7 +1049,6 @@ enum SequenceOperation { impl Transaction for SequenceOperation {} #[cfg(feature = "postgres")] -#[async_trait] impl DatabaseOperation for SequenceOperation { async fn generate_sql(&self, datasource: &DatasourceConfig) { let stmt = match self { diff --git a/src/lib.rs b/src/lib.rs index 493e1056..166d0abf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,6 @@ pub use canyon_macros::main; /// Public API for the `Canyon-SQL` proc-macros, and for the external ones pub mod macros { - pub use canyon_crud::async_trait::*; pub use canyon_macros::*; } From 1510a7fced4cfc74d8143af3fdf2fd9cb88e9d39 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 31 Jan 2025 17:23:38 +0100 Subject: [PATCH 061/155] fix: update pk operations chore: getting concrete column value from row on CanyonRows --- canyon_core/Cargo.toml | 1 + .../src/connection/db_clients/mssql.rs | 36 ++++---- .../src/connection/db_clients/mysql.rs | 11 ++- .../src/connection/db_clients/postgresql.rs | 11 ++- canyon_core/src/connection/db_connector.rs | 31 +++---- canyon_core/src/connection/mod.rs | 5 +- canyon_core/src/lib.rs | 3 +- canyon_core/src/query_parameters.rs | 2 +- canyon_core/src/rows.rs | 89 ++++++++++++++++++- canyon_core/src/transaction.rs | 15 ++-- canyon_crud/src/bounds.rs | 2 +- canyon_crud/src/crud.rs | 12 +-- .../src/query_elements/query_builder.rs | 69 +++++--------- canyon_macros/src/canyon_entity_macro.rs | 17 ++-- canyon_macros/src/canyon_mapper_macro.rs | 57 +++++++----- canyon_macros/src/foreignkeyable_macro.rs | 6 +- canyon_macros/src/lib.rs | 29 +++--- canyon_macros/src/query_operations/consts.rs | 3 +- canyon_macros/src/query_operations/delete.rs | 59 +++++++----- .../src/query_operations/doc_comments.rs | 7 +- .../src/query_operations/foreign_key.rs | 19 ++-- canyon_macros/src/query_operations/insert.rs | 86 ++++++------------ .../src/query_operations/macro_template.rs | 17 ++-- canyon_macros/src/query_operations/mod.rs | 13 ++- canyon_macros/src/query_operations/read.rs | 24 ++--- canyon_macros/src/query_operations/update.rs | 38 ++++---- canyon_macros/src/utils/macro_tokens.rs | 2 +- canyon_migrations/src/migrations/handler.rs | 2 +- canyon_migrations/src/migrations/processor.rs | 28 ++---- src/lib.rs | 20 ++--- tests/crud/querybuilder_operations.rs | 39 ++++---- tests/crud/update_operations.rs | 8 +- 32 files changed, 397 insertions(+), 364 deletions(-) diff --git a/canyon_core/Cargo.toml b/canyon_core/Cargo.toml index 12f7c7bb..d1829270 100644 --- a/canyon_core/Cargo.toml +++ b/canyon_core/Cargo.toml @@ -28,6 +28,7 @@ lazy_static = { workspace = true } toml = { workspace = true } serde = { workspace = true } walkdir = { workspace = true } +cfg-if = "1.0.0" [features] postgres = ["tokio-postgres"] diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs index e475c09b..81f03840 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -1,11 +1,11 @@ -use std::error::Error; #[cfg(feature = "mssql")] use async_std::net::TcpStream; +use std::error::Error; -use crate::{query_parameters::QueryParameter, rows::CanyonRows}; -use tiberius::Query; use crate::connection::database_type::DatabaseType; use crate::connection::db_connector::DbConnection; +use crate::{query_parameters::QueryParameter, rows::CanyonRows}; +use tiberius::Query; /// A connection with a `SqlServer` database #[cfg(feature = "mssql")] @@ -18,9 +18,8 @@ impl DbConnection for SqlServerConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl std::future::Future< - Output = Result>, - > + Send { + ) -> impl std::future::Future>> + Send + { sqlserver_query_launcher::launch(stmt, params, self) } @@ -42,18 +41,19 @@ pub(crate) mod sqlserver_query_launcher { ) -> Result> { // Re-generate de insert statement to adequate it to the SQL SERVER syntax to retrieve the PK value(s) after insert // TODO: redo this branch into the generated queries, before the MACROS - // if stmt.contains("RETURNING") { - // let c = stmt.clone(); - // let temp = c.split_once("RETURNING").unwrap(); - // let temp2 = temp.0.split_once("VALUES").unwrap(); - // - // *stmt = format!( - // "{} OUTPUT inserted.{} VALUES {}", - // temp2.0.trim(), - // temp.1.trim(), - // temp2.1.trim() - // ); - // } + let mut stmt = String::from(stmt); + if stmt.contains("RETURNING") { + let c = stmt.clone(); + let temp = c.split_once("RETURNING").unwrap(); + let temp2 = temp.0.split_once("VALUES").unwrap(); + + stmt = format!( + "{} OUTPUT inserted.{} VALUES {}", + temp2.0.trim(), + temp.1.trim(), + temp2.1.trim() + ); + } // TODO: We must address the query generation. Look at the returning example, or the // replace below. We may use our own type Query to address this concerns when the query diff --git a/canyon_core/src/connection/db_clients/mysql.rs b/canyon_core/src/connection/db_clients/mysql.rs index 6627d83b..75dc4027 100644 --- a/canyon_core/src/connection/db_clients/mysql.rs +++ b/canyon_core/src/connection/db_clients/mysql.rs @@ -1,13 +1,13 @@ -use std::error::Error; #[cfg(feature = "mysql")] use mysql_async::Pool; +use std::error::Error; +use crate::connection::database_type::DatabaseType; +use crate::connection::db_connector::DbConnection; use crate::{query_parameters::QueryParameter, rows::CanyonRows}; use mysql_async::Row; use mysql_common::constants::ColumnType; use mysql_common::row; -use crate::connection::database_type::DatabaseType; -use crate::connection::db_connector::DbConnection; /// A connection with a `Mysql` database #[cfg(feature = "mysql")] @@ -20,9 +20,8 @@ impl DbConnection for MysqlConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl std::future::Future< - Output = Result>, - > + Send { + ) -> impl std::future::Future>> + Send + { mysql_query_launcher::launch(stmt, params, self) } diff --git a/canyon_core/src/connection/db_clients/postgresql.rs b/canyon_core/src/connection/db_clients/postgresql.rs index 88c90f64..1764ef29 100644 --- a/canyon_core/src/connection/db_clients/postgresql.rs +++ b/canyon_core/src/connection/db_clients/postgresql.rs @@ -1,10 +1,10 @@ -use std::error::Error; use crate::{query_parameters::QueryParameter, rows::CanyonRows}; +use std::error::Error; -#[cfg(feature = "postgres")] -use tokio_postgres::Client; use crate::connection::database_type::DatabaseType; use crate::connection::db_connector::DbConnection; +#[cfg(feature = "postgres")] +use tokio_postgres::Client; /// A connection with a `PostgreSQL` database #[cfg(feature = "postgres")] @@ -18,9 +18,8 @@ impl DbConnection for PostgreSqlConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl std::future::Future< - Output = Result>, - > + Send { + ) -> impl std::future::Future>> + Send + { postgres_query_launcher::launch(stmt, params, self) } diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index 2ff6d69e..756e2960 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -1,17 +1,18 @@ -use std::error::Error; -use std::future::Future; use crate::connection::database_type::DatabaseType; use crate::connection::datasources::DatasourceConfig; +#[cfg(feature = "mssql")] use crate::connection::db_clients::mssql::SqlServerConnection; +#[cfg(feature = "mysql")] use crate::connection::db_clients::mysql::MysqlConnection; +#[cfg(feature = "postgres")] use crate::connection::db_clients::postgresql::PostgreSqlConnection; use crate::connection::{find_datasource_by_name_or_try_default, get_database_connection_by_ds}; use crate::query_parameters::QueryParameter; use crate::rows::CanyonRows; - +use std::error::Error; +use std::future::Future; pub trait DbConnection { - // TODO: guess that this is the trait that must remain sealed fn launch<'a>( &self, stmt: &str, @@ -26,15 +27,13 @@ pub trait DbConnection { /// directly with an [`&str`] that must match one of the datasources defined /// within the user config file impl DbConnection for &str { - fn launch<'a>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) - -> impl Future>> + Send - { + fn launch<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> impl Future>> + Send { async move { - let sane_ds_name = if !self.is_empty() { - Some(*self) - } else { - None - }; + let sane_ds_name = if !self.is_empty() { Some(*self) } else { None }; let conn = get_database_connection_by_ds(sane_ds_name).await?; conn.launch(stmt, params).await } @@ -64,9 +63,7 @@ impl DbConnection for DatabaseConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl std::future::Future< - Output = Result>, - > + Send { + ) -> impl Future>> + Send { async move { match self { #[cfg(feature = "postgres")] @@ -138,9 +135,9 @@ impl DatabaseConnection { match self { #[cfg(feature = "postgres")] DatabaseConnection::Postgres(_) => DatabaseType::PostgreSql, - #[cfg(feature = "postgres")] + #[cfg(feature = "mssql")] DatabaseConnection::SqlServer(_) => DatabaseType::SqlServer, - #[cfg(feature = "postgres")] + #[cfg(feature = "mysql")] DatabaseConnection::MySQL(_) => DatabaseType::MySQL, } } diff --git a/canyon_core/src/connection/mod.rs b/canyon_core/src/connection/mod.rs index b378dfb5..ac3b66d3 100644 --- a/canyon_core/src/connection/mod.rs +++ b/canyon_core/src/connection/mod.rs @@ -108,9 +108,8 @@ pub async fn get_database_connection_by_ds<'a>( pub fn find_datasource_by_name_or_try_default<'a>( datasource_name: Option<&str>, // TODO: with the new inputs, we don't want anymore this as Option ) -> Result<&DatasourceConfig, DatasourceNotFound> { - let datasource_name = datasource_name - .filter(|&ds_name| !ds_name.is_empty()); - + let datasource_name = datasource_name.filter(|&ds_name| !ds_name.is_empty()); + datasource_name .map_or_else( || DATASOURCES.first(), diff --git a/canyon_core/src/lib.rs b/canyon_core/src/lib.rs index 4b78498c..a49602d8 100644 --- a/canyon_core/src/lib.rs +++ b/canyon_core/src/lib.rs @@ -10,11 +10,12 @@ pub extern crate tiberius; pub extern crate mysql_async; pub extern crate lazy_static; +// extern crate cfg_if; pub mod column; pub mod connection; pub mod mapper; -pub mod transaction; pub mod query_parameters; pub mod row; pub mod rows; +pub mod transaction; diff --git a/canyon_core/src/query_parameters.rs b/canyon_core/src/query_parameters.rs index dde75109..cbdf2487 100644 --- a/canyon_core/src/query_parameters.rs +++ b/canyon_core/src/query_parameters.rs @@ -16,7 +16,7 @@ pub trait QueryParameter<'a>: std::fmt::Debug + Sync + Send { #[cfg(feature = "mssql")] fn as_sqlserver_param(&self) -> ColumnData<'_>; #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue; + fn as_mysql_param(&self) -> &dyn ToValue; } /// The implementation of the [`canyon_core::connection::tiberius`] [`IntoSql`] for the diff --git a/canyon_core/src/rows.rs b/canyon_core/src/rows.rs index 0180b8bb..9a85fad3 100644 --- a/canyon_core/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -6,13 +6,41 @@ use tiberius::{self}; use tokio_postgres::{self}; use crate::mapper::{CanyonError, IntoResults, RowMapper}; +use crate::row::Row; +use std::error::Error; + +use cfg_if::cfg_if; + +// Helper macro to conditionally add trait bounds +// these are the hacky intermediate traits +cfg_if! { + // if #[cfg(feature = "postgres")] { + // trait FromSql<'a, T> where T: tokio_postgres::types::FromSql<'a> { } + // } else if #[cfg(feature = "mssql")] { + // trait FromSql<'a, T> where T: tiberius::FromSql<'a> { } + // } else if #[cfg(feature = "mysql")] { + // trait FromSql<'a, T> where T: mysql_async::types::FromSql<'a> { } + // } + if #[cfg(feature = "postgres")] { + pub trait FromSql<'a, T>: tokio_postgres::types::FromSql<'a> + + tiberius::FromSql<'a> + + mysql_async::prelude::FromValue {} + impl<'a, T> FromSql<'a, T> for T where T: + tokio_postgres::types::FromSql<'a> + + tiberius::FromSql<'a> + + mysql_async::prelude::FromValue + {} + } + // TODO: missing combinations else +} /// Lightweight wrapper over the collection of results of the different crates /// supported by Canyon-SQL. /// /// Even tho the wrapping seems meaningless, this allows us to provide internal -/// operations that are too difficult or to ugly to implement in the macros that +/// operations that are too difficult or too ugly to implement in the macros that /// will call the query method of Crud. +#[derive(Debug)] pub enum CanyonRows { #[cfg(feature = "postgres")] Postgres(Vec), @@ -68,6 +96,65 @@ impl CanyonRows { } } + /// Returns the entity at the given index for the returned rows + /// + /// This is just a wrapper get operation over the [Vec::get] operation + pub fn get_row_at(&self, index: usize) -> Option<&dyn Row> { + match self { + #[cfg(feature = "postgres")] + Self::Postgres(v) => v.get(index).map(|inner| inner as &dyn Row), + #[cfg(feature = "mssql")] + Self::Tiberius(v) => v.get(index).map(|inner| inner as &dyn Row), + #[cfg(feature = "mysql")] + Self::MySQL(v) => v.get(index).map(|inner| inner as &dyn Row), + } + } + + pub fn get_column_at_row<'a, C: FromSql<'a, C>>( + &'a self, + column_name: &str, + index: usize, + ) -> Result> { + let row_extraction_failure = || { + format!( + "{:?} - Failure getting the row: {} at index: {}", + self, column_name, index + ) + }; + + match self { + #[cfg(feature = "postgres")] + Self::Postgres(v) => Ok(v + .get(index) + .ok_or_else(row_extraction_failure)? + .get::<&str, C>(column_name)), + #[cfg(feature = "mssql")] + Self::Tiberius(ref v) => v + .get(index) + .ok_or_else(row_extraction_failure)? + .get::(column_name) + .ok_or_else(|| { + format!( + "{:?} - Failed to obtain the RETURNING value for an insert operation", + self + ) + .into() + }), + #[cfg(feature = "mysql")] + Self::MySQL(ref v) => v + .get(index) + .ok_or_else(row_extraction_failure)? + .get::(0) + .ok_or_else(|| { + format!( + "{:?} - Failed to obtain the RETURNING value for an insert operation", + self + ) + .into() + }), + } + } + /// Returns the number of elements present on the wrapped collection pub fn len(&self) -> usize { match self { diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index b6fe5a1e..c776da0d 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -1,10 +1,7 @@ -use crate::{ - query_parameters::QueryParameter, - rows::CanyonRows, -}; -use std::{fmt::Display, future::Future}; -use std::error::Error; use crate::connection::db_connector::DbConnection; +use crate::{query_parameters::QueryParameter, rows::CanyonRows}; +use std::error::Error; +use std::{fmt::Display, future::Future}; pub trait Transaction { // provisional name @@ -18,10 +15,8 @@ pub trait Transaction { ) -> impl Future>> + Send where S: AsRef + Display + Sync + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, { - async move { - input.launch(stmt.as_ref(), params.as_ref()).await - } + async move { input.launch(stmt.as_ref(), params.as_ref()).await } } } diff --git a/canyon_crud/src/bounds.rs b/canyon_crud/src/bounds.rs index a15d4e4d..f6965195 100644 --- a/canyon_crud/src/bounds.rs +++ b/canyon_crud/src/bounds.rs @@ -1,4 +1,4 @@ -use canyon_core::{mapper::RowMapper, transaction::Transaction, query_parameters::QueryParameter}; +use canyon_core::{mapper::RowMapper, query_parameters::QueryParameter, transaction::Transaction}; use crate::crud::CrudOperations; diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index d193390c..4bc86341 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -1,11 +1,11 @@ -use std::error::Error; -use std::future::Future; use crate::query_elements::query_builder::{ - SelectQueryBuilder, UpdateQueryBuilder, DeleteQueryBuilder + DeleteQueryBuilder, SelectQueryBuilder, UpdateQueryBuilder, }; +use canyon_core::connection::db_connector::DbConnection; use canyon_core::query_parameters::QueryParameter; use canyon_core::{mapper::RowMapper, transaction::Transaction}; -use canyon_core::connection::db_connector::DbConnection; +use std::error::Error; +use std::future::Future; /// *CrudOperations* it's the core part of Canyon-SQL. /// @@ -65,7 +65,9 @@ where where I: DbConnection + Send + 'a; - fn insert(&mut self) -> impl Future>> + Send; + fn insert<'a>( + &'a mut self, + ) -> impl Future>> + Send; fn insert_with<'a, I>( &mut self, diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index 7b758bbc..7d8389e0 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -5,15 +5,17 @@ use crate::{ Operator, }; use canyon_core::connection::database_type::DatabaseType; -use canyon_core::{mapper::RowMapper, transaction::Transaction, query_parameters::QueryParameter}; +use canyon_core::connection::db_connector::DbConnection; +use canyon_core::{mapper::RowMapper, query_parameters::QueryParameter, transaction::Transaction}; use std::fmt::Debug; use std::marker::PhantomData; -use canyon_core::connection::db_connector::DbConnection; /// Contains the elements that makes part of the formal declaration /// of the behaviour of the Canyon-SQL QueryBuilder pub mod ops { - use canyon_core::{mapper::RowMapper, transaction::Transaction, query_parameters::QueryParameter}; + use canyon_core::{ + mapper::RowMapper, query_parameters::QueryParameter, transaction::Transaction, + }; use crate::crud::CrudOperations; @@ -71,11 +73,7 @@ pub mod ops { /// column name and the value for the filter /// * `op` - Any element that implements [`Operator`] for create the comparison /// or equality binary operator - fn r#where>( - self, - column: Z, - op: impl Operator, - ) -> Self + fn r#where>(self, column: Z, op: impl Operator) -> Self where T: Debug + CrudOperations + Transaction + RowMapper; @@ -85,11 +83,7 @@ pub mod ops { /// column name and the value for the filter /// * `op` - Any element that implements [`Operator`] for create the comparison /// or equality binary operator - fn and>( - self, - column: Z, - op: impl Operator, - ) -> Self; + fn and>(self, column: Z, op: impl Operator) -> Self; /// Generates an `AND` SQL clause for constraint the query that will create /// the filter in conjunction with an `IN` operator that will ac @@ -123,8 +117,7 @@ pub mod ops { /// column name and the value for the filter /// * `op` - Any element that implements [`Operator`] for create the comparison /// or equality binary operator - fn or>(self, column: Z, op: impl Operator) - -> Self; + fn or>(self, column: Z, op: impl Operator) -> Self; /// Generates a `ORDER BY` SQL clause for constraint the query. /// @@ -149,20 +142,20 @@ where unsafe impl<'a, T, I> Send for QueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: DbConnection + Send + 'a + I: DbConnection + Send + 'a, { } unsafe impl<'a, T, I> Sync for QueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: DbConnection + Send + 'a + I: DbConnection + Send + 'a, { } impl<'a, T, I> QueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: DbConnection + Send + 'a + I: DbConnection + Send + 'a, { /// Returns a new instance of the [`QueryBuilder`] pub fn new(query: Query<'a>, input: I) -> Self { @@ -303,7 +296,7 @@ where pub struct SelectQueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: DbConnection + Send + 'a + I: DbConnection + Send + 'a, { _inner: QueryBuilder<'a, T, I>, } @@ -311,7 +304,7 @@ where impl<'a, T, I> SelectQueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: DbConnection + Send + 'a + I: DbConnection + Send + 'a, { /// Generates a new public instance of the [`SelectQueryBuilder`] pub fn new(table_schema_data: &str, input: I) -> Self { @@ -398,7 +391,7 @@ where impl<'a, T, I> ops::QueryBuilder<'a, T> for SelectQueryBuilder<'a, T, I> where T: Debug + CrudOperations + Transaction + RowMapper + Send, - I: DbConnection + Send + 'a + I: DbConnection + Send + 'a, { #[inline] fn read_sql(&'a self) -> &'a str { @@ -411,11 +404,7 @@ where } #[inline] - fn r#where>( - mut self, - r#where: Z, - op: impl Operator, - ) -> Self { + fn r#where>(mut self, r#where: Z, op: impl Operator) -> Self { self._inner.r#where(r#where, op); self } @@ -466,7 +455,7 @@ where pub struct UpdateQueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: DbConnection + Send + 'a + I: DbConnection + Send + 'a, { _inner: QueryBuilder<'a, T, I>, } @@ -474,7 +463,7 @@ where impl<'a, T, I> UpdateQueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: DbConnection + Send + 'a + I: DbConnection + Send + 'a, { /// Generates a new public instance of the [`UpdateQueryBuilder`] pub fn new(table_schema_data: &str, input: I) -> Self { @@ -489,9 +478,7 @@ where /// Launches the generated query to the database pointed by the /// selected datasource #[inline] - pub async fn query( - self, - ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> { + pub async fn query(self) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> { self._inner.query().await } @@ -538,7 +525,7 @@ where impl<'a, T, I> ops::QueryBuilder<'a, T> for UpdateQueryBuilder<'a, T, I> where T: Debug + CrudOperations + Transaction + RowMapper + Send, - I: DbConnection + Send + 'a + I: DbConnection + Send + 'a, { #[inline] fn read_sql(&'a self) -> &'a str { @@ -551,11 +538,7 @@ where } #[inline] - fn r#where>( - mut self, - r#where: Z, - op: impl Operator, - ) -> Self { + fn r#where>(mut self, r#where: Z, op: impl Operator) -> Self { self._inner.r#where(r#where, op); self } @@ -607,7 +590,7 @@ where pub struct DeleteQueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: DbConnection + Send + 'a + I: DbConnection + Send + 'a, { _inner: QueryBuilder<'a, T, I>, } @@ -615,7 +598,7 @@ where impl<'a, T, I> DeleteQueryBuilder<'a, T, I> where T: CrudOperations + Transaction + RowMapper, - I: DbConnection + Send + 'a + I: DbConnection + Send + 'a, { /// Generates a new public instance of the [`DeleteQueryBuilder`] pub fn new(table_schema_data: &str, input: I) -> Self { @@ -638,7 +621,7 @@ where impl<'a, T, I> ops::QueryBuilder<'a, T> for DeleteQueryBuilder<'a, T, I> where T: Debug + CrudOperations + Transaction + RowMapper + Send, - I: DbConnection + Send + 'a + I: DbConnection + Send + 'a, { #[inline] fn read_sql(&'a self) -> &'a str { @@ -651,11 +634,7 @@ where } #[inline] - fn r#where>( - mut self, - r#where: Z, - op: impl Operator, - ) -> Self { + fn r#where>(mut self, r#where: Z, op: impl Operator) -> Self { self._inner.r#where(r#where, op); self } diff --git a/canyon_macros/src/canyon_entity_macro.rs b/canyon_macros/src/canyon_entity_macro.rs index 073b3802..0d428900 100644 --- a/canyon_macros/src/canyon_entity_macro.rs +++ b/canyon_macros/src/canyon_entity_macro.rs @@ -1,14 +1,17 @@ +use crate::utils::helpers; +use canyon_entities::entity::CanyonEntity; +use canyon_entities::manager_builder::generate_user_struct; +use canyon_entities::register_types::{CanyonRegisterEntity, CanyonRegisterEntityField}; +use canyon_entities::CANYON_REGISTER_ENTITIES; use proc_macro::TokenStream as CompilerTokenStream; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{AttributeArgs, NestedMeta}; -use canyon_entities::CANYON_REGISTER_ENTITIES; -use canyon_entities::entity::CanyonEntity; -use canyon_entities::manager_builder::generate_user_struct; -use canyon_entities::register_types::{CanyonRegisterEntity, CanyonRegisterEntityField}; -use crate::utils::helpers; -pub fn generate_canyon_entity_tokens(attrs: AttributeArgs, input: CompilerTokenStream) -> TokenStream { +pub fn generate_canyon_entity_tokens( + attrs: AttributeArgs, + input: CompilerTokenStream, +) -> TokenStream { let (table_name, schema_name, parsing_attribute_error) = parse_canyon_entity_proc_macro_attr(attrs); @@ -20,7 +23,7 @@ pub fn generate_canyon_entity_tokens(attrs: AttributeArgs, input: CompilerTokenS .into_compile_error() .into(); } - + // No errors detected on the parsing, so we can safely unwrap the parse result let entity = entity_res.unwrap(); let generated_user_struct = generate_user_struct(&entity); diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index 34ed2e4a..263c72f5 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -1,10 +1,10 @@ -use std::iter::Map; -use std::slice::Iter; +use crate::utils::helpers::fields_with_types; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use syn::{DeriveInput, Type, Visibility}; use regex::Regex; -use crate::utils::helpers::{fields_with_types}; +use std::iter::Map; +use std::slice::Iter; +use syn::{DeriveInput, Type, Visibility}; #[cfg(feature = "mssql")] const BY_VALUE_CONVERSION_TARGETS: [&str; 1] = ["String"]; @@ -64,7 +64,9 @@ pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { } #[cfg(feature = "postgres")] -fn create_postgres_fields_mapping(fields: &Vec<(Visibility, Ident, Type)>) -> Map, fn(&'_ (Visibility, Ident, Type)) -> TokenStream> { +fn create_postgres_fields_mapping( + fields: &Vec<(Visibility, Ident, Type)>, +) -> Map, fn(&'_ (Visibility, Ident, Type)) -> TokenStream> { fields.iter().map(|(_vis, ident, _ty)| { let ident_name = ident.to_string(); quote! { @@ -75,7 +77,9 @@ fn create_postgres_fields_mapping(fields: &Vec<(Visibility, Ident, Type)>) -> Ma } #[cfg(feature = "mysql")] -fn create_mysql_fields_mapping(fields: &Vec<(Visibility, Ident, Type)>) -> Map, fn(&'_ (Visibility, Ident, Type)) -> TokenStream> { +fn create_mysql_fields_mapping( + fields: &Vec<(Visibility, Ident, Type)>, +) -> Map, fn(&'_ (Visibility, Ident, Type)) -> TokenStream> { fields.iter().map(|(_vis, ident, _ty)| { let ident_name = ident.to_string(); quote! { @@ -86,7 +90,9 @@ fn create_mysql_fields_mapping(fields: &Vec<(Visibility, Ident, Type)>) -> Map) -> Map, fn(&'_ (Visibility, Ident, Type)) -> TokenStream> { +fn create_sqlserver_fields_mapping( + fields: &Vec<(Visibility, Ident, Type)>, +) -> Map, fn(&'_ (Visibility, Ident, Type)) -> TokenStream> { fields.into_iter().map(|(_vis, ident, ty)| { let ident_name = ident.to_string(); @@ -94,7 +100,7 @@ fn create_sqlserver_fields_mapping(fields: &Vec<(Visibility, Ident, Type)>) -> M let field_deserialize_impl = handle_stupid_tiberius_sql_conversions(&target_field_type_str, &ident_name); - quote!{ + quote! { #ident: #field_deserialize_impl } }) @@ -105,7 +111,9 @@ fn handle_stupid_tiberius_sql_conversions(target_type: &str, ident_name: &str) - let is_opt_type = target_type.contains("Option"); let handle_opt = if !is_opt_type { quote! { .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) } - } else { quote! {} }; + } else { + quote! {} + }; let deserializing_type = get_deserializing_type(target_type); let to_owned = if BY_VALUE_CONVERSION_TARGETS @@ -117,8 +125,9 @@ fn handle_stupid_tiberius_sql_conversions(target_type: &str, ident_name: &str) - } else { quote! { .to_owned() } } - } else { quote! {} }; - + } else { + quote! {} + }; quote! { row.get::<#deserializing_type, &str>(#ident_name) @@ -127,27 +136,27 @@ fn handle_stupid_tiberius_sql_conversions(target_type: &str, ident_name: &str) - } } +#[cfg(feature = "mssql")] fn get_deserializing_type(target_type: &str) -> TokenStream { let re = Regex::new(r"(?:Option\s*<\s*)?(?P&?\w+)(?:\s*>)?").unwrap(); - re - .captures(&*target_type) + re.captures(&*target_type) .map(|inner| String::from(&inner["type"])) .map(|tt| { if BY_VALUE_CONVERSION_TARGETS.contains(&tt.as_str()) { quote! { &str } // potentially others on demand on the future } else if tt.contains("Date") || tt.contains("Time") { - let dt = Ident::new( - tt.as_str(), - Span::call_site() - ); + let dt = Ident::new(tt.as_str(), Span::call_site()); quote! { canyon_sql::date_time::#dt } } else { let tt = Ident::new(tt.as_str(), Span::call_site()); - quote! { #tt } + quote! { #tt } } }) - .expect(&format!("Unable to process type: {} on the given struct for SqlServer", target_type)) + .expect(&format!( + "Unable to process type: {} on the given struct for SqlServer", + target_type + )) } #[cfg(feature = "mssql")] @@ -184,7 +193,13 @@ mod mapper_macro_tests { assert_eq!("&str", get_deserializing_type("Option").to_string()); assert_eq!("i64", get_deserializing_type("i64").to_string()); - assert_eq!("canyon_sql::date_time::DateTime", get_deserializing_type("DateTime").to_string()); - assert_eq!("canyon_sql::date_time::NaiveDateTime", get_deserializing_type("NaiveDateTime").to_string()); + assert_eq!( + "canyon_sql::date_time::DateTime", + get_deserializing_type("DateTime").to_string() + ); + assert_eq!( + "canyon_sql::date_time::NaiveDateTime", + get_deserializing_type("NaiveDateTime").to_string() + ); } } diff --git a/canyon_macros/src/foreignkeyable_macro.rs b/canyon_macros/src/foreignkeyable_macro.rs index 2a2333ec..3e838b71 100644 --- a/canyon_macros/src/foreignkeyable_macro.rs +++ b/canyon_macros/src/foreignkeyable_macro.rs @@ -1,11 +1,11 @@ +use crate::utils::helpers::filter_fields; use proc_macro2::TokenStream; use quote::quote; use syn::DeriveInput; -use crate::utils::helpers::filter_fields; pub fn foreignkeyable_impl_tokens(ast: DeriveInput) -> TokenStream { let ty = ast.ident; - + // Recovers the identifiers of the structs members let fields = filter_fields(match ast.data { syn::Data::Struct(ref s) => &s.fields, @@ -46,4 +46,4 @@ pub fn foreignkeyable_impl_tokens(ast: DeriveInput) -> TokenStream { } } } -} \ No newline at end of file +} diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index c7f4c436..792466a0 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -4,29 +4,26 @@ extern crate regex; #[cfg(feature = "migrations")] use canyon_macro::main_with_queries; -mod canyon_macro; mod canyon_entity_macro; -mod query_operations; -mod utils; +mod canyon_macro; mod canyon_mapper_macro; mod foreignkeyable_macro; +mod query_operations; +mod utils; use proc_macro::TokenStream as CompilerTokenStream; use quote::quote; use syn::DeriveInput; use utils::{function_parser::FunctionParser, helpers, macro_tokens::MacroTokens}; -use canyon_entities::{ - entity::CanyonEntity, - manager_builder::{ - generate_enum_with_fields, - generate_enum_with_fields_values, - }, -}; use crate::canyon_entity_macro::generate_canyon_entity_tokens; use crate::canyon_mapper_macro::canyon_mapper_impl_tokens; use crate::foreignkeyable_macro::foreignkeyable_impl_tokens; use crate::query_operations::impl_crud_operations_trait_for_struct; +use canyon_entities::{ + entity::CanyonEntity, + manager_builder::{generate_enum_with_fields, generate_enum_with_fields_values}, +}; /// Macro for handling the entry point to the program. /// @@ -109,14 +106,8 @@ pub fn canyon_tokio_test( /// Also, it's the responsible for generate the tokens for all the `Crud` methods available over /// your type #[proc_macro_attribute] -pub fn canyon_entity( - _meta: CompilerTokenStream, - input: CompilerTokenStream, -) -> CompilerTokenStream { - let attrs = syn::parse_macro_input!(_meta as syn::AttributeArgs); - - - +pub fn canyon_entity(meta: CompilerTokenStream, input: CompilerTokenStream) -> CompilerTokenStream { + let attrs = syn::parse_macro_input!(meta as syn::AttributeArgs); generate_canyon_entity_tokens(attrs, input).into() } @@ -183,4 +174,4 @@ pub fn querybuilder_fields(input: CompilerTokenStream) -> CompilerTokenStream { #_generated_enum_type_for_fields_values } .into() -} \ No newline at end of file +} diff --git a/canyon_macros/src/query_operations/consts.rs b/canyon_macros/src/query_operations/consts.rs index 39dd64b6..7038b9e9 100644 --- a/canyon_macros/src/query_operations/consts.rs +++ b/canyon_macros/src/query_operations/consts.rs @@ -33,5 +33,4 @@ pub const MAPS_TO: &str = "into_results :: < User > ()"; pub const LT_CONSTRAINT: &str = "< 'a "; pub const INPUT_PARAM: &str = "input : I"; -pub const WITH_WHERE_BOUNDS: &str = - "where I : canyon_sql :: core :: DbConnection + Send + 'a "; \ No newline at end of file +pub const WITH_WHERE_BOUNDS: &str = "where I : canyon_sql :: core :: DbConnection + Send + 'a "; diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index 16b39804..8586df2a 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -1,8 +1,11 @@ +use crate::query_operations::delete::__details::{ + create_delete_err_macro, create_delete_err_with_macro, create_delete_macro, + create_delete_with_macro, +}; +use crate::utils::macro_tokens::MacroTokens; use proc_macro2::{Ident, TokenStream}; use quote::quote; use syn::Type; -use crate::query_operations::delete::__details::{create_delete_err_macro, create_delete_err_with_macro, create_delete_macro, create_delete_with_macro}; -use crate::utils::macro_tokens::MacroTokens; /// Generates the TokenStream for the __delete() CRUD operation /// returning a result, indicating a possible failure querying the database @@ -14,7 +17,7 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let pk = macro_data.get_primary_key_annotation(); let ret_ty: Type = syn::parse_str("()").expect("Failed to parse unit type"); - let q_ret_ty: TokenStream = quote!{#ret_ty}; + let q_ret_ty: TokenStream = quote! {#ret_ty}; if let Some(primary_key) = pk { let pk_field = fields @@ -26,11 +29,14 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let pk_field_value = quote! { &self.#pk_field as &dyn canyon_sql::core::QueryParameter<'_> }; - let stmt = format!("DELETE FROM {} WHERE {:?} = $1", table_schema_data, primary_key); - + let stmt = format!( + "DELETE FROM {} WHERE {:?} = $1", + table_schema_data, primary_key + ); + let delete_tokens = create_delete_macro(ty, &stmt, &pk_field_value, &q_ret_ty); let delete_with_tokens = create_delete_with_macro(ty, &stmt, &pk_field_value, &q_ret_ty); - + delete_ops_tokens.extend(quote! { #delete_tokens #delete_with_tokens @@ -38,7 +44,7 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri } else { let delete_err_tokens = create_delete_err_macro(ty, &q_ret_ty); let delete_err_with_tokens = create_delete_err_with_macro(ty, &q_ret_ty); - + delete_ops_tokens.extend(quote! { #delete_err_tokens #delete_err_with_tokens @@ -53,10 +59,7 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri /// Generates the TokenStream for the __delete() CRUD operation as a /// [`query_elements::query_builder::QueryBuilder<'a, #ty>`] -fn generate_delete_query_tokens( - ty: &Ident, - table_schema_data: &str, -) -> TokenStream { +fn generate_delete_query_tokens(ty: &Ident, table_schema_data: &str) -> TokenStream { quote! { /// Generates a [`canyon_sql::query::DeleteQueryBuilder`] /// that allows you to customize the query by adding parameters and constrains dynamically. @@ -88,12 +91,17 @@ fn generate_delete_query_tokens( } mod __details { - + + use super::*; use crate::query_operations::doc_comments; use crate::query_operations::macro_template::MacroOperationBuilder; - use super::*; - pub fn create_delete_macro(ty: &Ident, stmt: &str, pk_field_value: &TokenStream, ret_ty: &TokenStream) -> MacroOperationBuilder { + pub fn create_delete_macro( + ty: &Ident, + stmt: &str, + pk_field_value: &TokenStream, + ret_ty: &TokenStream, + ) -> MacroOperationBuilder { MacroOperationBuilder::new() .fn_name("delete") .with_self_as_ref() @@ -102,14 +110,19 @@ mod __details { .raw_return() .add_doc_comment(doc_comments::DELETE) .query_string(stmt) - .forwarded_parameters(quote!{&[#pk_field_value]}) + .forwarded_parameters(quote! {&[#pk_field_value]}) .propagate_transaction_result() .disable_mapping() .raw_return() .with_no_result_value() } - pub fn create_delete_with_macro(ty: &Ident, stmt: &str, pk_field_value: &TokenStream, ret_ty: &TokenStream) -> MacroOperationBuilder { + pub fn create_delete_with_macro( + ty: &Ident, + stmt: &str, + pk_field_value: &TokenStream, + ret_ty: &TokenStream, + ) -> MacroOperationBuilder { MacroOperationBuilder::new() .fn_name("delete_with") .with_self_as_ref() @@ -120,7 +133,7 @@ mod __details { .add_doc_comment(doc_comments::DELETE) .add_doc_comment(doc_comments::DS_ADVERTISING) .query_string(stmt) - .forwarded_parameters(quote!{&[#pk_field_value]}) + .forwarded_parameters(quote! {&[#pk_field_value]}) .propagate_transaction_result() .disable_mapping() .raw_return() @@ -153,8 +166,8 @@ mod __details { #[cfg(test)] mod delete_tests { - use crate::query_operations::consts::*; use super::__details::*; + use crate::query_operations::consts::*; use proc_macro2::Span; use quote::quote; use syn::Ident; @@ -167,7 +180,7 @@ mod delete_tests { &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), DELETE_MOCK_STMT, &PK_MOCK_FIELD_VALUE.with(|pk_field_mock_value| pk_field_mock_value.borrow().clone()), - &VOID_RET_TY.with(|void_ret_ty| void_ret_ty.borrow().clone()) + &VOID_RET_TY.with(|void_ret_ty| void_ret_ty.borrow().clone()), ); let delete = delete_builder.generate_tokens().to_string(); @@ -181,7 +194,7 @@ mod delete_tests { &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), DELETE_MOCK_STMT, &PK_MOCK_FIELD_VALUE.with(|pk_field_mock_value| pk_field_mock_value.borrow().clone()), - &VOID_RET_TY.with(|void_ret_ty| void_ret_ty.borrow().clone()) + &VOID_RET_TY.with(|void_ret_ty| void_ret_ty.borrow().clone()), ); let delete_with = delete_builder.generate_tokens().to_string(); @@ -195,7 +208,7 @@ mod delete_tests { fn test_macro_builder_delete_err() { let delete_err_builder = create_delete_err_macro( &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - &VOID_RET_TY.with(|void_ret_ty| void_ret_ty.borrow().clone()) + &VOID_RET_TY.with(|void_ret_ty| void_ret_ty.borrow().clone()), ); let delete_err = delete_err_builder.generate_tokens().to_string(); @@ -207,7 +220,7 @@ mod delete_tests { fn test_macro_builder_delete_err_with() { let delete_err_with_builder = create_delete_err_with_macro( &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - &VOID_RET_TY.with(|void_ret_ty| void_ret_ty.borrow().clone()) + &VOID_RET_TY.with(|void_ret_ty| void_ret_ty.borrow().clone()), ); let delete_err_with = delete_err_with_builder.generate_tokens().to_string(); @@ -216,4 +229,4 @@ mod delete_tests { assert!(delete_err_with.contains(LT_CONSTRAINT)); assert!(delete_err_with.contains(INPUT_PARAM)); } -} \ No newline at end of file +} diff --git a/canyon_macros/src/query_operations/doc_comments.rs b/canyon_macros/src/query_operations/doc_comments.rs index fc184efc..c7d6f5a6 100644 --- a/canyon_macros/src/query_operations/doc_comments.rs +++ b/canyon_macros/src/query_operations/doc_comments.rs @@ -6,7 +6,7 @@ pub const SELECT_ALL_BASE_DOC_COMMENT: &str = database convention. P.ej. PostgreSQL prefers table names declared \ with snake_case identifiers."; -pub const SELECT_QUERYBUILDER_DOC_COMMENT: &str = +pub const SELECT_QUERYBUILDER_DOC_COMMENT: &str = "Generates a [`canyon_sql::query::SelectQueryBuilder`] \ that allows you to customize the query by adding parameters and constrains dynamically. \ \ @@ -38,6 +38,7 @@ pub const DELETE: &str = "Deletes from a database entity the row that matches key field, returning a result indicating a possible failure querying the database."; -pub const UNAVAILABLE_CRUD_OP_ON_INSTANCE: &str = "Operation is unavailable. T doesn't contain a #[primary_key]\ +pub const UNAVAILABLE_CRUD_OP_ON_INSTANCE: &str = + "Operation is unavailable. T doesn't contain a #[primary_key]\ annotation. You must construct the query with the QueryBuilder type\ - (_query method for the CrudOperations implementors"; \ No newline at end of file + (_query method for the CrudOperations implementors"; diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index 016d13d5..0eda45b6 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -1,10 +1,13 @@ -use proc_macro2::{Ident, TokenStream}; -use quote::quote; -use canyon_entities::field_annotation::EntityFieldAnnotation; use crate::utils::helpers::database_table_name_to_struct_ident; use crate::utils::macro_tokens::MacroTokens; +use canyon_entities::field_annotation::EntityFieldAnnotation; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; -pub fn generate_find_by_fk_ops(macro_data: &MacroTokens<'_>, table_schema_data: &str) -> TokenStream { +pub fn generate_find_by_fk_ops( + macro_data: &MacroTokens<'_>, + table_schema_data: &str, +) -> TokenStream { let ty = ¯o_data.ty; // Search by foreign (d) key as Vec, cause Canyon supports multiple fields having FK annotation @@ -28,7 +31,7 @@ pub fn generate_find_by_fk_ops(macro_data: &MacroTokens<'_>, table_schema_data: ); if search_by_reverse_fk_tokens.is_empty() { - return quote!{}; // early guard + return quote! {}; // early guard } quote! { @@ -72,8 +75,7 @@ fn generate_find_by_foreign_key_tokens( // Generate and identifier for the method based on the convention of "search_related_types" // where types is a placeholder for the plural name of the type referenced - let method_name_ident = - Ident::new(&method_name, proc_macro2::Span::call_site()); + let method_name_ident = Ident::new(&method_name, proc_macro2::Span::call_site()); let method_name_ident_with = Ident::new( &format!("{}_with", &method_name), proc_macro2::Span::call_site(), @@ -160,8 +162,7 @@ fn generate_find_by_reverse_foreign_key_tokens( // Generate and identifier for the method based on the convention of "search_by__" (note the double underscore) // plus the 'table_name' property of the ForeignKey annotation - let method_name_ident = - Ident::new(&method_name, proc_macro2::Span::call_site()); + let method_name_ident = Ident::new(&method_name, proc_macro2::Span::call_site()); let method_name_ident_with = Ident::new( &format!("{}_with", &method_name), proc_macro2::Span::call_site(), diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 5b70b6e3..663ca8f4 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -1,13 +1,13 @@ +use crate::utils::macro_tokens::MacroTokens; use proc_macro2::TokenStream; use quote::quote; -use crate::utils::macro_tokens::MacroTokens; /// Generates the TokenStream for the _insert_result() CRUD operation pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &String) -> TokenStream { let mut insert_ops_tokens = TokenStream::new(); - + let ty = macro_data.ty; - + // Retrieves the fields of the Struct as a collection of Strings, already parsed // the condition of remove the primary key if it's present and it's autoincremental let insert_columns = macro_data.get_column_names_pk_parsed().join(", "); @@ -31,8 +31,13 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri quote! {} }; + let stmt = format!( + "INSERT INTO {} ({}) VALUES ({})", + table_schema_data, insert_columns, placeholders + ); + let pk_ident_type = macro_data - ._fields_with_types() + .fields_with_types() .into_iter() .find(|(i, _t)| Some(i.to_string()) == primary_key); let insert_transaction = if let Some(pk_data) = &pk_ident_type { @@ -42,62 +47,21 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri quote! { #remove_pk_value_from_fn_entry; - let stmt = format!( - "INSERT INTO {} ({}) VALUES ({}) RETURNING {}", - #table_schema_data, - #insert_columns, - #placeholders, - #primary_key - ); + let stmt = format!("{} RETURNING {}", #stmt , #primary_key); - let rows = <#ty as canyon_sql::core::Transaction<#ty>>::query( + self.#pk_ident = <#ty as canyon_sql::core::Transaction<#ty>>::query( stmt, values, input - ).await?; + ).await? + .get_column_at_row::<#pk_type>(#primary_key, 0)?; - match rows { - #[cfg(feature = "postgres")] - canyon_sql::core::CanyonRows::Postgres(mut v) => { - self.#pk_ident = v - .get(0) - .ok_or("Failed getting the returned IDs for an insert")? - .get::<&str, #pk_type>(#primary_key); - Ok(()) - }, - #[cfg(feature = "mssql")] - canyon_sql::core::CanyonRows::Tiberius(mut v) => { - self.#pk_ident = v - .get(0) - .ok_or("Failed getting the returned IDs for a multi insert")? - .get::<#pk_type, &str>(#primary_key) - .ok_or("SQL Server primary key type failed to be set as value")?; - Ok(()) - }, - #[cfg(feature = "mysql")] - canyon_sql::core::CanyonRows::MySQL(mut v) => { - self.#pk_ident = v - .get(0) - .ok_or("Failed getting the returned IDs for a multi insert")? - .get::<#pk_type,usize>(0) - .ok_or("MYSQL primary key type failed to be set as value")?; - Ok(()) - }, - _ => panic!("Reached the panic match arm of insert for the DatabaseConnection type") // TODO remove when the generics will be refactored - } + Ok(()) } } else { quote! { - let stmt = format!( - "INSERT INTO {} ({}) VALUES ({})", - #table_schema_data, - #insert_columns, - #placeholders, - #primary_key - ); - <#ty as canyon_sql::core::Transaction<#ty>>::query( - stmt, + #stmt, values, input ).await?; @@ -145,8 +109,8 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri /// } /// ``` /// - async fn insert(&mut self) - -> Result<(), Box> + async fn insert<'a>(&'a mut self) + -> Result<(), Box> { let input = ""; let mut values: Vec<&dyn canyon_sql::core::QueryParameter<'_>> = vec![#(#insert_values),*]; @@ -201,7 +165,7 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri } }); - + let multi_insert_tokens = generate_multiple_insert_tokens(macro_data, table_schema_data); insert_ops_tokens.extend(multi_insert_tokens); @@ -232,7 +196,7 @@ fn generate_multiple_insert_tokens( let pk = macro_data.get_primary_key_annotation().unwrap_or_default(); let pk_ident_type = macro_data - ._fields_with_types() + .fields_with_types() .into_iter() .find(|(i, _t)| *i == pk); @@ -450,24 +414,24 @@ fn generate_multiple_insert_tokens( ) { use canyon_sql::core::QueryParameter; let input = ""; - + let mut final_values: Vec>> = Vec::new(); for instance in instances.iter() { let intermediate: &[&dyn QueryParameter<'_>] = &[#(#macro_fields),*]; - + let mut longer_lived: Vec<&dyn QueryParameter<'_>> = Vec::new(); for value in intermediate.into_iter() { longer_lived.push(*value) } - + final_values.push(longer_lived) } - + let mut mapped_fields: String = String::new(); - + #multi_insert_transaction } - + /// Inserts multiple instances of some type `T` into its related table with the specified /// datasource by its `datasource name`, defined in the configuration file. /// diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index 466b991b..c102bbc7 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -106,7 +106,7 @@ impl MacroOperationBuilder { quote! {} } } - + fn compose_params_separator(&self) -> TokenStream { if self.input_parameters.is_some() && self.input_param.is_some() { quote! {, } @@ -119,7 +119,9 @@ impl MacroOperationBuilder { if self.self_as_ref { let self_ident = Ident::new("self", Span::call_site()); quote! { &#self_ident } - } else { quote!{} } + } else { + quote! {} + } } fn get_input_param(&self) -> TokenStream { @@ -162,9 +164,9 @@ impl MacroOperationBuilder { quote! { #rt_ts } } else { let rt = &self.return_type; - quote!{ #rt } + quote! { #rt } }; - + let container_ret_type = if self.single_result { quote! { Option } } else { @@ -190,7 +192,7 @@ impl MacroOperationBuilder { quote! { Result<#ret_type, #err_variant> } } }; - + quote! { impl std::future::Future + Send } } @@ -334,11 +336,12 @@ impl MacroOperationBuilder { if !self.disable_mapping { base_body_tokens.extend(quote! { .into_results::<#ty>() }) }; - if self.with_no_result_value { // TODO: should we validate some combinations? in the future, some of them can be hard to reason about + if self.with_no_result_value { + // TODO: should we validate some combinations? in the future, some of them can be hard to reason about // like transaction_as_variable and with_no_result_value, they can't coexist base_body_tokens.extend(quote! {; Ok(()) }) } - + let body_tokens = if let Some(direct_err_return) = &self.direct_error_return { let err = direct_err_return; quote! { diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index a53a4489..9bc17de0 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -1,11 +1,11 @@ -use proc_macro2::TokenStream; -use quote::quote; use crate::query_operations::delete::generate_delete_tokens; use crate::query_operations::foreign_key::generate_find_by_fk_ops; use crate::query_operations::insert::generate_insert_tokens; use crate::query_operations::read::generate_read_operations_tokens; use crate::query_operations::update::generate_update_tokens; use crate::utils::macro_tokens::MacroTokens; +use proc_macro2::TokenStream; +use quote::quote; pub mod delete; pub mod foreign_key; @@ -13,10 +13,9 @@ pub mod insert; pub mod read; pub mod update; +mod consts; mod doc_comments; mod macro_template; -mod consts; - pub fn impl_crud_operations_trait_for_struct( macro_data: &MacroTokens<'_>, @@ -37,7 +36,7 @@ pub fn impl_crud_operations_trait_for_struct( #delete_tokens }; - crud_ops_tokens.extend(quote!{ + crud_ops_tokens.extend(quote! { use canyon_sql::core::IntoResults; impl canyon_sql::crud::CrudOperations<#ty> for #ty { @@ -48,7 +47,7 @@ pub fn impl_crud_operations_trait_for_struct( }); let foreign_key_ops_tokens = generate_find_by_fk_ops(macro_data, &table_schema_data); - crud_ops_tokens.extend(quote!{ #foreign_key_ops_tokens }); + crud_ops_tokens.extend(quote! { #foreign_key_ops_tokens }); crud_ops_tokens.into() -} \ No newline at end of file +} diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 2a6cf33a..e9fe0ff9 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -26,7 +26,7 @@ pub fn generate_read_operations_tokens( let count_with = create_count_with_macro(ty, &count_stmt); let find_by_pk_complex_tokens = generate_find_by_pk_tokens(macro_data, table_schema_data); - + let read_querybuilder_ops = generate_find_all_query_tokens(macro_data, table_schema_data); quote! { @@ -39,7 +39,7 @@ pub fn generate_read_operations_tokens( #count_with #find_by_pk_complex_tokens - + #read_querybuilder_ops } } @@ -349,7 +349,7 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_find_all() { let find_all_builder = create_find_all_macro( &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - SELECT_ALL_STMT + SELECT_ALL_STMT, ); let find_all = find_all_builder.generate_tokens().to_string(); @@ -361,7 +361,7 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_find_all_with() { let find_all_builder = create_find_all_with_macro( &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - SELECT_ALL_STMT + SELECT_ALL_STMT, ); let find_all_with = find_all_builder.generate_tokens().to_string(); @@ -375,7 +375,7 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_find_all_unchecked() { let find_all_unc_builder = create_find_all_unchecked_macro( &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - SELECT_ALL_STMT + SELECT_ALL_STMT, ); let find_all_unc = find_all_unc_builder.generate_tokens().to_string(); @@ -387,7 +387,7 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_find_all_unchecked_with() { let find_all_unc_with_builder = create_find_all_unchecked_with_macro( &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - SELECT_ALL_STMT + SELECT_ALL_STMT, ); let find_all_unc_with = find_all_unc_with_builder.generate_tokens().to_string(); @@ -401,7 +401,7 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_count() { let count_builder = create_count_macro( &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - COUNT_STMT + COUNT_STMT, ); let count = count_builder.generate_tokens().to_string(); @@ -413,7 +413,7 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_count_with() { let count_with_builder = create_count_with_macro( &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - COUNT_STMT + COUNT_STMT, ); let count_with = count_with_builder.generate_tokens().to_string(); @@ -427,8 +427,8 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_find_by_pk() { let find_by_pk_builder = create_find_by_pk_macro( &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - FIND_BY_PK_STMT, - "e! {} + FIND_BY_PK_STMT, + "e! {}, ); let find_by_pk = find_by_pk_builder.generate_tokens().to_string(); @@ -441,8 +441,8 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_find_by_pk_with() { let find_by_pk_with_builder = create_find_by_pk_with( &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - FIND_BY_PK_STMT, - "e! {} + FIND_BY_PK_STMT, + "e! {}, ); let find_by_pk_with = find_by_pk_with_builder.generate_tokens().to_string(); diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index 72ea18f9..516564cc 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -1,8 +1,8 @@ -use proc_macro2::{Ident, TokenStream}; +use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use crate::utils::macro_tokens::MacroTokens; use crate::query_operations::update::__details::*; +use crate::utils::macro_tokens::MacroTokens; /// Generates the TokenStream for the __update() CRUD operation pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &String) -> TokenStream { @@ -26,6 +26,8 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let update_values_cloned = update_values.clone(); if let Some(primary_key) = macro_data.get_primary_key_annotation() { + let pk_ident = Ident::new(&primary_key, Span::call_site()); // TODO get it + // from macro_data in the future, saving operations let pk_index = macro_data .get_pk_index() .expect("Update method failed to retrieve the index of the primary key"); @@ -37,7 +39,7 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri async fn update(&self) -> Result<(), Box> { let stmt = format!( "UPDATE {} SET {} WHERE {} = ${:?}", - #table_schema_data, #str_columns_values, #primary_key, #pk_index + 1 + #table_schema_data, #str_columns_values, #primary_key, &self.#pk_ident ); let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = &[#(#update_values),*]; @@ -47,7 +49,7 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri Ok(()) } - + /// Updates a database record that matches /// the current instance of a T type, returning a result /// indicating a possible failure querying the database with the @@ -80,19 +82,16 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri #update_err_with_tokens }); } - + let querybuilder_update_tokens = generate_update_query_tokens(ty, table_schema_data); update_ops_tokens.extend(querybuilder_update_tokens); - + update_ops_tokens } /// Generates the TokenStream for the __update() CRUD operation /// being the query generated with the [`QueryBuilder`] -fn generate_update_query_tokens( - ty: &Ident, - table_schema_data: &String, -) -> TokenStream { +fn generate_update_query_tokens(ty: &Ident, table_schema_data: &String) -> TokenStream { quote! { /// Generates a [`canyon_sql::query::UpdateQueryBuilder`] /// that allows you to customize the query by adding parameters and constrains dynamically. @@ -115,7 +114,7 @@ fn generate_update_query_tokens( /// /// The query it's made against the database with the configured datasource /// described in the configuration file, and selected with the input parameter - fn update_query_with<'a, I>(input: I) -> canyon_sql::query::UpdateQueryBuilder<'a, #ty, I> + fn update_query_with<'a, I>(input: I) -> canyon_sql::query::UpdateQueryBuilder<'a, #ty, I> where I: canyon_sql::core::DbConnection + Send + 'a { canyon_sql::query::UpdateQueryBuilder::new(#table_schema_data, input) @@ -124,8 +123,7 @@ fn generate_update_query_tokens( } mod __details { - - + use crate::query_operations::consts::VOID_RET_TY; use crate::query_operations::doc_comments; use crate::query_operations::macro_template::MacroOperationBuilder; @@ -156,13 +154,17 @@ mod __details { #[cfg(test)] mod update_tokens_tests { - use crate::query_operations::consts::{INPUT_PARAM, LT_CONSTRAINT, RES_VOID_RET_TY, RES_VOID_RET_TY_LT, USER_MOCK_TY, VOID_RET_TY}; - use crate::query_operations::update::__details::{create_update_err_macro, create_update_err_with_macro}; + use crate::query_operations::consts::{ + INPUT_PARAM, LT_CONSTRAINT, RES_VOID_RET_TY, RES_VOID_RET_TY_LT, USER_MOCK_TY, VOID_RET_TY, + }; + use crate::query_operations::update::__details::{ + create_update_err_macro, create_update_err_with_macro, + }; #[test] fn test_macro_builder_update_err() { let update_err_builder = create_update_err_macro( - &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()) + &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), ); let update_err = update_err_builder.generate_tokens().to_string(); @@ -173,7 +175,7 @@ mod update_tokens_tests { #[test] fn test_macro_builder_update_err_with() { let update_err_with_builder = create_update_err_with_macro( - &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()) + &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), ); let update_err_with = update_err_with_builder.generate_tokens().to_string(); @@ -182,4 +184,4 @@ mod update_tokens_tests { assert!(update_err_with.contains(LT_CONSTRAINT)); assert!(update_err_with.contains(INPUT_PARAM)); } -} \ No newline at end of file +} diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index 415d9ccc..7eec7875 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -46,7 +46,7 @@ impl<'a> MacroTokens<'a> { /// Gives a Vec of tuples that contains the name and /// the type of every field on a Struct - pub fn _fields_with_types(&self) -> Vec<(Ident, Type)> { + pub fn fields_with_types(&self) -> Vec<(Ident, Type)> { self.fields .iter() .map(|field| (field.ident.as_ref().unwrap().clone(), field.ty.clone())) diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index a83593ba..13ab444a 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -3,9 +3,9 @@ use canyon_core::{ connection::{ datasources::Migrations as MigrationsStatus, db_connector::DatabaseConnection, DATASOURCES, }, - transaction::Transaction, row::{Row, RowOperations}, rows::CanyonRows, + transaction::Transaction, }; use canyon_entities::CANYON_REGISTER_ENTITIES; use partialdebug::placeholder::PartialDebug; diff --git a/canyon_migrations/src/migrations/processor.rs b/canyon_migrations/src/migrations/processor.rs index 267be2bf..4a9a9a74 100644 --- a/canyon_migrations/src/migrations/processor.rs +++ b/canyon_migrations/src/migrations/processor.rs @@ -166,19 +166,16 @@ impl MigrationsProcessor { /// Generates a database agnostic query to change the name of a table fn create_table(&mut self, table_name: String, entity_fields: Vec) { - self.table_operations.push(TableOperation::CreateTable( - table_name, - entity_fields, - )); + self.table_operations + .push(TableOperation::CreateTable(table_name, entity_fields)); } /// Generates a database agnostic query to change the name of a table fn table_rename(&mut self, old_table_name: String, new_table_name: String) { - self.table_operations - .push(TableOperation::AlterTableName( - old_table_name, - new_table_name, - )); + self.table_operations.push(TableOperation::AlterTableName( + old_table_name, + new_table_name, + )); } // Creates or modify (currently only datatype) a column for a given canyon register entity field @@ -286,23 +283,17 @@ impl MigrationsProcessor { fn change_column_datatype(&mut self, table_name: String, field: CanyonRegisterEntityField) { self.column_operations - .push(ColumnOperation::AlterColumnType( - table_name, field, - )); + .push(ColumnOperation::AlterColumnType(table_name, field)); } fn set_not_null(&mut self, table_name: String, field: CanyonRegisterEntityField) { self.column_operations - .push(ColumnOperation::AlterColumnSetNotNull( - table_name, field, - )); + .push(ColumnOperation::AlterColumnSetNotNull(table_name, field)); } fn drop_not_null(&mut self, table_name: String, field: CanyonRegisterEntityField) { self.column_operations - .push(ColumnOperation::AlterColumnDropNotNull( - table_name, field, - )); + .push(ColumnOperation::AlterColumnDropNotNull(table_name, field)); } fn add_constraints( @@ -767,7 +758,6 @@ mod migrations_helper_tests { } } - trait DatabaseOperation: Debug { fn generate_sql(&self, datasource: &DatasourceConfig) -> impl Future; } diff --git a/src/lib.rs b/src/lib.rs index 166d0abf..ef7aa9fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,24 +27,19 @@ pub mod macros { /// connection module serves to reexport the public elements of the `canyon_connection` crate, /// exposing them through the public API pub mod connection { - #[cfg(feature = "postgres")] - pub use canyon_core::connection::db_connector::DatabaseConnection::Postgres; - - #[cfg(feature = "mssql")] - pub use canyon_core::connection::db_connector::DatabaseConnection::SqlServer; - - #[cfg(feature = "mysql")] - pub use canyon_core::connection::db_connector::DatabaseConnection::MySQL; - - pub use canyon_core::connection::*; + pub use canyon_core::connection::database_type::DatabaseType; + pub use canyon_core::connection::db_connector::DatabaseConnection; + pub use canyon_core::connection::get_database_config; + pub use canyon_core::connection::get_database_connection; + pub use canyon_core::connection::get_database_connection_by_ds; } pub mod core { - pub use canyon_core::mapper::*; pub use canyon_core::connection::db_connector::DbConnection; - pub use canyon_core::transaction::Transaction; + pub use canyon_core::mapper::*; pub use canyon_core::query_parameters::QueryParameter; pub use canyon_core::rows::CanyonRows; + pub use canyon_core::transaction::Transaction; } /// Crud module serves to reexport the public elements of the `canyon_crud` crate, @@ -52,7 +47,6 @@ pub mod core { pub mod crud { pub use canyon_crud::bounds; pub use canyon_crud::crud::*; - pub use canyon_crud::DatabaseType; } /// Re-exports the query elements from the `crud`crate diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index 10f0657b..5ef2bda6 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -66,8 +66,8 @@ fn test_crud_find_with_querybuilder() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_fulllike() { // Find all the leagues with "LC" in their name - let filtered_leagues_result = League::select_query() - .r#where(LeagueFieldValue::name(&"LC"), Like::Full); + let filtered_leagues_result = + League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Full); assert_eq!( filtered_leagues_result.read_sql(), @@ -81,8 +81,8 @@ fn test_crud_find_with_querybuilder_and_fulllike() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_fulllike_with_mssql() { // Find all the leagues with "LC" in their name - let filtered_leagues_result = League::select_query_with(SQL_SERVER_DS) - .r#where(LeagueFieldValue::name(&"LC"), Like::Full); + let filtered_leagues_result = + League::select_query_with(SQL_SERVER_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Full); assert_eq!( filtered_leagues_result.read_sql(), @@ -96,8 +96,8 @@ fn test_crud_find_with_querybuilder_and_fulllike_with_mssql() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_fulllike_with_mysql() { // Find all the leagues with "LC" in their name - let filtered_leagues_result = League::select_query_with(MYSQL_DS) - .r#where(LeagueFieldValue::name(&"LC"), Like::Full); + let filtered_leagues_result = + League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Full); assert_eq!( filtered_leagues_result.read_sql(), @@ -111,8 +111,8 @@ fn test_crud_find_with_querybuilder_and_fulllike_with_mysql() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_leftlike() { // Find all the leagues whose name ends with "CK" - let filtered_leagues_result = League::select_query() - .r#where(LeagueFieldValue::name(&"CK"), Like::Left); + let filtered_leagues_result = + League::select_query().r#where(LeagueFieldValue::name(&"CK"), Like::Left); assert_eq!( filtered_leagues_result.read_sql(), @@ -126,8 +126,8 @@ fn test_crud_find_with_querybuilder_and_leftlike() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_leftlike_with_mssql() { // Find all the leagues whose name ends with "CK" - let filtered_leagues_result = League::select_query() - .r#where(LeagueFieldValue::name(&"CK"), Like::Left); + let filtered_leagues_result = + League::select_query().r#where(LeagueFieldValue::name(&"CK"), Like::Left); assert_eq!( filtered_leagues_result.read_sql(), @@ -141,8 +141,8 @@ fn test_crud_find_with_querybuilder_and_leftlike_with_mssql() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_leftlike_with_mysql() { // Find all the leagues whose name ends with "CK" - let filtered_leagues_result = League::select_query_with(MYSQL_DS) - .r#where(LeagueFieldValue::name(&"CK"), Like::Left); + let filtered_leagues_result = + League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"CK"), Like::Left); assert_eq!( filtered_leagues_result.read_sql(), @@ -156,8 +156,8 @@ fn test_crud_find_with_querybuilder_and_leftlike_with_mysql() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_rightlike() { // Find all the leagues whose name starts with "LC" - let filtered_leagues_result = League::select_query() - .r#where(LeagueFieldValue::name(&"LC"), Like::Right); + let filtered_leagues_result = + League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Right); assert_eq!( filtered_leagues_result.read_sql(), @@ -186,8 +186,8 @@ fn test_crud_find_with_querybuilder_and_rightlike_with_mssql() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_rightlike_with_mysql() { // Find all the leagues whose name starts with "LC" - let filtered_leagues_result = League::select_query_with(MYSQL_DS) - .r#where(LeagueFieldValue::name(&"LC"), Like::Right); + let filtered_leagues_result = + League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Right); assert_eq!( filtered_leagues_result.read_sql(), @@ -233,8 +233,8 @@ fn test_crud_update_with_querybuilder() { (LeagueField::slug, "Updated with the QueryBuilder"), (LeagueField::name, "Random"), ]) - .r#where(LeagueFieldValue::id(&1), Comp::Gt) - .and(LeagueFieldValue::id(&8), Comp::Lt); + .r#where(LeagueFieldValue::id(&1), Comp::Gt) + .and(LeagueFieldValue::id(&8), Comp::Lt); /* NOTE: Family of QueryBuilders are clone, useful in case of need to read the generated SQL let qpr = q.clone(); @@ -378,8 +378,7 @@ fn test_crud_delete_with_querybuilder_with_mysql() { /// WHERE clause #[canyon_sql::macros::canyon_tokio_test] fn test_where_clause() { - let l = League::select_query() - .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq); + let l = League::select_query().r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq); assert_eq!(l.read_sql(), "SELECT * FROM league WHERE name = $1") } diff --git a/tests/crud/update_operations.rs b/tests/crud/update_operations.rs index 4c3fffb0..6904d34f 100644 --- a/tests/crud/update_operations.rs +++ b/tests/crud/update_operations.rs @@ -30,7 +30,7 @@ fn test_crud_update_method_operation() { // The ext_id field value is extracted from the sql scripts under the // docker/sql folder. We are retrieving the first entity inserted at the - // wake up time of the database, and now checking some of its properties. + // wake-up time of the database, and now checking some of its properties. assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); // Modify the value, and perform the update @@ -49,13 +49,13 @@ fn test_crud_update_method_operation() { assert_eq!(updt_entity.ext_id, updt_value); - // We rollback the changes to the initial value to don't broke other tests + // We roll back the changes to the initial value to don't broke other tests // the next time that will run updt_candidate.ext_id = 100695891328981122_i64; updt_candidate .update() .await - .expect("Failed the restablish initial value update operation"); + .expect("Failed to restore the initial value in the psql update operation"); } /// Same as the above test, but with the specified datasource. @@ -71,7 +71,7 @@ fn test_crud_update_with_mssql_method_operation() { // The ext_id field value is extracted from the sql scripts under the // docker/sql folder. We are retrieving the first entity inserted at the - // wake up time of the database, and now checking some of its properties. + // wake-up time of the database, and now checking some of its properties. assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); // Modify the value, and perform the update From b2f0d1b18894716a39a70179eb5470996ec8e4d6 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 4 Feb 2025 12:55:48 +0100 Subject: [PATCH 062/155] feat: impl of single row result operations --- .../src/connection/db_clients/mssql.rs | 63 ++++++++++- .../src/connection/db_clients/mysql.rs | 31 ++++-- .../src/connection/db_clients/postgresql.rs | 37 +++++-- canyon_core/src/connection/db_connector.rs | 101 ++++++++++++------ canyon_core/src/rows.rs | 11 ++ 5 files changed, 190 insertions(+), 53 deletions(-) diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs index 81f03840..9c59f6ba 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -1,11 +1,12 @@ #[cfg(feature = "mssql")] use async_std::net::TcpStream; use std::error::Error; - +use std::future::Future; use crate::connection::database_type::DatabaseType; use crate::connection::db_connector::DbConnection; use crate::{query_parameters::QueryParameter, rows::CanyonRows}; use tiberius::Query; +use crate::mapper::RowMapper; /// A connection with a `SqlServer` database #[cfg(feature = "mssql")] @@ -18,11 +19,19 @@ impl DbConnection for SqlServerConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl std::future::Future>> + Send + ) -> impl Future>> + Send { sqlserver_query_launcher::launch(stmt, params, self) } + fn query_one<'a, R>(&self, stmt: &str, params: &[&'a (dyn QueryParameter<'a>)]) + -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send + where + R: RowMapper + { + sqlserver_query_launcher::query_one(stmt, params, self) + } + fn get_database_type(&self) -> Result> { Ok(DatabaseType::SqlServer) } @@ -30,6 +39,10 @@ impl DbConnection for SqlServerConnection { #[cfg(feature = "mssql")] pub(crate) mod sqlserver_query_launcher { + use std::future::Future; + use std::io::ErrorKind; + use tiberius::Row; + use crate::mapper::RowMapper; use super::*; #[inline(always)] @@ -38,7 +51,7 @@ pub(crate) mod sqlserver_query_launcher { stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &SqlServerConnection, - ) -> Result> { + ) -> Result> { // Re-generate de insert statement to adequate it to the SQL SERVER syntax to retrieve the PK value(s) after insert // TODO: redo this branch into the generated queries, before the MACROS let mut stmt = String::from(stmt); @@ -74,4 +87,48 @@ pub(crate) mod sqlserver_query_launcher { _results.into_iter().flatten().collect(), )) } + + pub(crate) async fn query_one<'a, R>( + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + conn: &SqlServerConnection, + ) + -> Result, Box<(dyn Error + Send + Sync)>> + where + R: RowMapper + { + let mut stmt = String::from(stmt); + if stmt.contains("RETURNING") { + let c = stmt.clone(); + let temp = c.split_once("RETURNING").unwrap(); + let temp2 = temp.0.split_once("VALUES").unwrap(); + + stmt = format!( + "{} OUTPUT inserted.{} VALUES {}", + temp2.0.trim(), + temp.1.trim(), + temp2.1.trim() + ); + } + + // TODO: We must address the query generation. Look at the returning example, or the + // replace below. We may use our own type Query to address this concerns when the query + // is generated + let mut mssql_query = Query::new(stmt.to_owned().replace('$', "@P")); + params.iter().for_each(|param| mssql_query.bind(*param)); + + #[allow(mutable_transmutes)] // TODO: pls solve this elegantly someday :( + let sqlservconn = + unsafe { std::mem::transmute::<&SqlServerConnection, &mut SqlServerConnection>(conn) }; + let result = mssql_query + .query(sqlservconn.client) + .await? + .into_row() + .await?; + + match result { + Some(r) => { Ok(Some(R::deserialize_sqlserver(&r))) } + None => { Ok(None) } + } + } } diff --git a/canyon_core/src/connection/db_clients/mysql.rs b/canyon_core/src/connection/db_clients/mysql.rs index 75dc4027..4ea500f6 100644 --- a/canyon_core/src/connection/db_clients/mysql.rs +++ b/canyon_core/src/connection/db_clients/mysql.rs @@ -1,13 +1,14 @@ #[cfg(feature = "mysql")] use mysql_async::Pool; use std::error::Error; - +use std::future::Future; use crate::connection::database_type::DatabaseType; use crate::connection::db_connector::DbConnection; use crate::{query_parameters::QueryParameter, rows::CanyonRows}; use mysql_async::Row; use mysql_common::constants::ColumnType; use mysql_common::row; +use crate::mapper::RowMapper; /// A connection with a `Mysql` database #[cfg(feature = "mysql")] @@ -20,9 +21,15 @@ impl DbConnection for MysqlConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl std::future::Future>> + Send + ) -> impl Future>> + Send + { + mysql_query_launcher::query(stmt, params, self) + } + + fn query_one<'a, R: RowMapper>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) + -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send { - mysql_query_launcher::launch(stmt, params, self) + mysql_query_launcher::query_one(stmt, params, self) } fn get_database_type(&self) -> Result> { @@ -45,13 +52,25 @@ pub(crate) mod mysql_query_launcher { use regex::Regex; use std::sync::Arc; - #[inline(always)] + #[inline(always)] // TODO: very provisional implementation! care! + // TODO: would be better to launch a simple query for the last id? + pub(crate) async fn query_one<'a, R: RowMapper>( + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + conn: &MysqlConnection, + ) -> Result, Box<(dyn Error + Sync + Send)>> { + Ok(query(stmt, params, conn) + .await? + .first_row() + ) + } - pub(crate) async fn launch<'a>( + #[inline(always)] + pub(crate) async fn query<'a>( stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &MysqlConnection, - ) -> Result> { + ) -> Result> { let mysql_connection = conn.client.get_conn().await?; let stmt_with_escape_characters = regex::escape(stmt); diff --git a/canyon_core/src/connection/db_clients/postgresql.rs b/canyon_core/src/connection/db_clients/postgresql.rs index 1764ef29..df1b6a70 100644 --- a/canyon_core/src/connection/db_clients/postgresql.rs +++ b/canyon_core/src/connection/db_clients/postgresql.rs @@ -1,10 +1,11 @@ use crate::{query_parameters::QueryParameter, rows::CanyonRows}; use std::error::Error; - +use std::future::Future; use crate::connection::database_type::DatabaseType; use crate::connection::db_connector::DbConnection; #[cfg(feature = "postgres")] use tokio_postgres::Client; +use crate::mapper::RowMapper; /// A connection with a `PostgreSQL` database #[cfg(feature = "postgres")] @@ -18,11 +19,19 @@ impl DbConnection for PostgreSqlConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl std::future::Future>> + Send + ) -> impl Future>> + Send { - postgres_query_launcher::launch(stmt, params, self) + postgres_query_launcher::query(stmt, params, self) } + fn query_one<'a, R>(&self, stmt: &str, params: &[&'a (dyn QueryParameter<'a>)]) + -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send + where + R: RowMapper + { + postgres_query_launcher::query_one(stmt, params, self) + } + fn get_database_type(&self) -> Result> { Ok(DatabaseType::PostgreSql) } @@ -33,18 +42,24 @@ pub(crate) mod postgres_query_launcher { use super::*; #[inline(always)] - pub(crate) async fn launch<'a>( + pub(crate) async fn query<'a>( stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &PostgreSqlConnection, - ) -> Result> { - let mut m_params = Vec::new(); - for param in params { - m_params.push((*param).as_postgres_param()); - } - + ) -> Result> { + let m_params: Vec<_> = params.iter().map(|param| param.as_postgres_param()).collect(); let r = conn.client.query(stmt, m_params.as_slice()).await?; - Ok(CanyonRows::Postgres(r)) } + + #[inline(always)] + pub(crate) async fn query_one<'a, T: RowMapper>( + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + conn: &PostgreSqlConnection, + ) -> Result, Box<(dyn Error + Sync + Send)>> { + let m_params: Vec<_> = params.iter().map(|param| param.as_postgres_param()).collect(); + let r = conn.client.query_one(stmt, m_params.as_slice()).await?; + Ok(Some(T::deserialize_postgresql(&r))) + } } diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index 756e2960..9a031b67 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -11,6 +11,8 @@ use crate::query_parameters::QueryParameter; use crate::rows::CanyonRows; use std::error::Error; use std::future::Future; +use crate::connection::db_connector::connection_helpers::{db_conn_launch_impl, db_conn_query_one_impl}; +use crate::mapper::RowMapper; pub trait DbConnection { fn launch<'a>( @@ -19,6 +21,12 @@ pub trait DbConnection { params: &[&'a dyn QueryParameter<'a>], ) -> impl Future>> + Send; + fn query_one<'a, R: RowMapper>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send; + fn get_database_type(&self) -> Result>; } @@ -27,16 +35,22 @@ pub trait DbConnection { /// directly with an [`&str`] that must match one of the datasources defined /// within the user config file impl DbConnection for &str { - fn launch<'a>( + async fn launch<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send { - async move { - let sane_ds_name = if !self.is_empty() { Some(*self) } else { None }; - let conn = get_database_connection_by_ds(sane_ds_name).await?; - conn.launch(stmt, params).await - } + ) -> Result>{ + let sane_ds_name = if !self.is_empty() { Some(*self) } else { None }; + let conn = get_database_connection_by_ds(sane_ds_name).await?; + conn.launch(stmt, params).await + } + + async fn query_one<'a, R: RowMapper>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) + -> Result, Box<(dyn Error + Sync + Send)>> + { + let sane_ds_name = if !self.is_empty() { Some(*self) } else { None }; + let conn = get_database_connection_by_ds(sane_ds_name).await?; + conn.query_one(stmt, params).await } fn get_database_type(&self) -> Result> { @@ -59,23 +73,18 @@ pub enum DatabaseConnection { } impl DbConnection for DatabaseConnection { - fn launch<'a>( + async fn launch<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send { - async move { - match self { - #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(client) => client.launch(stmt, params).await, - - #[cfg(feature = "mssql")] - DatabaseConnection::SqlServer(client) => client.launch(stmt, params).await, + ) -> Result> { + db_conn_launch_impl(self, stmt, params).await + } - #[cfg(feature = "mysql")] - DatabaseConnection::MySQL(client) => client.launch(stmt, params).await, - } - } + async fn query_one<'a, R: RowMapper>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) + -> Result, Box<(dyn Error + Sync + Send)>> + { + db_conn_query_one_impl(self, stmt, params).await } fn get_database_type(&self) -> Result> { @@ -84,23 +93,18 @@ impl DbConnection for DatabaseConnection { } impl DbConnection for &mut DatabaseConnection { - fn launch<'a>( + async fn launch<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send { - async move { - match self { - #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(client) => client.launch(stmt, params).await, - - #[cfg(feature = "mssql")] - DatabaseConnection::SqlServer(client) => client.launch(stmt, params).await, + ) -> Result> { + db_conn_launch_impl(self, stmt, params).await + } - #[cfg(feature = "mysql")] - DatabaseConnection::MySQL(client) => client.launch(stmt, params).await, - } - } + async fn query_one<'a, R: RowMapper>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) + -> Result, Box<(dyn Error + Sync + Send)>> + { + db_conn_query_one_impl(self, stmt, params).await } fn get_database_type(&self) -> Result> { @@ -173,6 +177,7 @@ impl DatabaseConnection { mod connection_helpers { use super::*; use tokio_postgres::NoTls; + #[cfg(feature = "postgres")] pub async fn create_postgres_connection( @@ -254,6 +259,36 @@ mod connection_helpers { db = datasource.properties.db_name ) } + + pub(crate) async fn db_conn_launch_impl<'a>(c: &DatabaseConnection, stmt: &str, params: &[&'a (dyn QueryParameter<'a> + 'a)]) -> Result>{ + match c { + #[cfg(feature = "postgres")] + DatabaseConnection::Postgres(client) => client.launch(stmt, params).await, + + #[cfg(feature = "mssql")] + DatabaseConnection::SqlServer(client) => client.launch(stmt, params).await, + + #[cfg(feature = "mysql")] + DatabaseConnection::MySQL(client) => client.launch(stmt, params).await, + } + } + + pub(crate) async fn db_conn_query_one_impl<'a, T: RowMapper>(c: &DatabaseConnection, + stmt: &str, + params: &[&'a (dyn QueryParameter<'a> + 'a)]) + -> Result, Box> + { + match c { + #[cfg(feature = "postgres")] + DatabaseConnection::Postgres(client) => client.query_one(stmt, params).await, + + #[cfg(feature = "mssql")] + DatabaseConnection::SqlServer(client) => client.query_one(stmt, params).await, + + #[cfg(feature = "mysql")] + DatabaseConnection::MySQL(client) => client.query_one(stmt, params).await, + } + } } mod auth { diff --git a/canyon_core/src/rows.rs b/canyon_core/src/rows.rs index 9a85fad3..d4ce4828 100644 --- a/canyon_core/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -110,6 +110,17 @@ impl CanyonRows { } } + pub fn first_row>(&self) -> Option { + match self { + #[cfg(feature = "postgres")] + Self::Postgres(v) => v.get(0).map(|r| T::deserialize_postgresql(r)), + #[cfg(feature = "mssql")] + Self::Tiberius(v) => v.get(0).map(|r| T::deserialize_sqlserver(r)), + #[cfg(feature = "mysql")] + Self::MySQL(v) => v.get(0).map(|r| T::deserialize_mysql(r)), + } + } + pub fn get_column_at_row<'a, C: FromSql<'a, C>>( &'a self, column_name: &str, From ac5b1cab23c439be2828b6ea67af9b84dda826d1 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 4 Feb 2025 12:58:38 +0100 Subject: [PATCH 063/155] feat: renamed DbConnection::launch(self, ...) to DbConnection::query(self, ...) --- canyon_core/src/connection/db_clients/mssql.rs | 6 +----- canyon_core/src/connection/db_clients/mysql.rs | 2 +- .../src/connection/db_clients/postgresql.rs | 2 +- canyon_core/src/connection/db_connector.rs | 16 ++++++++-------- canyon_core/src/transaction.rs | 2 +- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs index 9c59f6ba..045d8156 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -15,7 +15,7 @@ pub struct SqlServerConnection { } impl DbConnection for SqlServerConnection { - fn launch<'a>( + fn query<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], @@ -39,14 +39,10 @@ impl DbConnection for SqlServerConnection { #[cfg(feature = "mssql")] pub(crate) mod sqlserver_query_launcher { - use std::future::Future; - use std::io::ErrorKind; - use tiberius::Row; use crate::mapper::RowMapper; use super::*; #[inline(always)] - pub(crate) async fn launch<'a>( stmt: &str, params: &[&'a dyn QueryParameter<'a>], diff --git a/canyon_core/src/connection/db_clients/mysql.rs b/canyon_core/src/connection/db_clients/mysql.rs index 4ea500f6..875e1d34 100644 --- a/canyon_core/src/connection/db_clients/mysql.rs +++ b/canyon_core/src/connection/db_clients/mysql.rs @@ -17,7 +17,7 @@ pub struct MysqlConnection { } impl DbConnection for MysqlConnection { - fn launch<'a>( + fn query<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], diff --git a/canyon_core/src/connection/db_clients/postgresql.rs b/canyon_core/src/connection/db_clients/postgresql.rs index df1b6a70..e50eb8c9 100644 --- a/canyon_core/src/connection/db_clients/postgresql.rs +++ b/canyon_core/src/connection/db_clients/postgresql.rs @@ -15,7 +15,7 @@ pub struct PostgreSqlConnection { } impl DbConnection for PostgreSqlConnection { - fn launch<'a>( + fn query<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index 9a031b67..19fea896 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -15,7 +15,7 @@ use crate::connection::db_connector::connection_helpers::{db_conn_launch_impl, d use crate::mapper::RowMapper; pub trait DbConnection { - fn launch<'a>( + fn query<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], @@ -35,14 +35,14 @@ pub trait DbConnection { /// directly with an [`&str`] that must match one of the datasources defined /// within the user config file impl DbConnection for &str { - async fn launch<'a>( + async fn query<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result>{ let sane_ds_name = if !self.is_empty() { Some(*self) } else { None }; let conn = get_database_connection_by_ds(sane_ds_name).await?; - conn.launch(stmt, params).await + conn.query(stmt, params).await } async fn query_one<'a, R: RowMapper>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) @@ -73,7 +73,7 @@ pub enum DatabaseConnection { } impl DbConnection for DatabaseConnection { - async fn launch<'a>( + async fn query<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], @@ -93,7 +93,7 @@ impl DbConnection for DatabaseConnection { } impl DbConnection for &mut DatabaseConnection { - async fn launch<'a>( + async fn query<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], @@ -263,13 +263,13 @@ mod connection_helpers { pub(crate) async fn db_conn_launch_impl<'a>(c: &DatabaseConnection, stmt: &str, params: &[&'a (dyn QueryParameter<'a> + 'a)]) -> Result>{ match c { #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(client) => client.launch(stmt, params).await, + DatabaseConnection::Postgres(client) => client.query(stmt, params).await, #[cfg(feature = "mssql")] - DatabaseConnection::SqlServer(client) => client.launch(stmt, params).await, + DatabaseConnection::SqlServer(client) => client.query(stmt, params).await, #[cfg(feature = "mysql")] - DatabaseConnection::MySQL(client) => client.launch(stmt, params).await, + DatabaseConnection::MySQL(client) => client.query(stmt, params).await, } } diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index c776da0d..5a4ca0b7 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -17,6 +17,6 @@ pub trait Transaction { S: AsRef + Display + Sync + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, { - async move { input.launch(stmt.as_ref(), params.as_ref()).await } + async move { input.query(stmt.as_ref(), params.as_ref()).await } } } From 8ed4e67af17e735a53c561dde54f167ca6ad52bb Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 4 Feb 2025 13:26:45 +0100 Subject: [PATCH 064/155] feat: query_one to the public interface of Transaction --- canyon_core/src/rows.rs | 6 +++--- canyon_core/src/transaction.rs | 14 +++++++++++++- canyon_migrations/src/migrations/handler.rs | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/canyon_core/src/rows.rs b/canyon_core/src/rows.rs index d4ce4828..3342264f 100644 --- a/canyon_core/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -113,11 +113,11 @@ impl CanyonRows { pub fn first_row>(&self) -> Option { match self { #[cfg(feature = "postgres")] - Self::Postgres(v) => v.get(0).map(|r| T::deserialize_postgresql(r)), + Self::Postgres(v) => v.first().map(|r| T::deserialize_postgresql(r)), #[cfg(feature = "mssql")] - Self::Tiberius(v) => v.get(0).map(|r| T::deserialize_sqlserver(r)), + Self::Tiberius(v) => v.first().map(|r| T::deserialize_sqlserver(r)), #[cfg(feature = "mysql")] - Self::MySQL(v) => v.get(0).map(|r| T::deserialize_mysql(r)), + Self::MySQL(v) => v.first().map(|r| T::deserialize_mysql(r)), } } diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index 5a4ca0b7..72cf8650 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -2,9 +2,9 @@ use crate::connection::db_connector::DbConnection; use crate::{query_parameters::QueryParameter, rows::CanyonRows}; use std::error::Error; use std::{fmt::Display, future::Future}; +use crate::mapper::RowMapper; pub trait Transaction { - // provisional name /// Performs a query against the targeted database by the selected or /// the defaulted datasource, wrapping the resultant collection of entities /// in [`super::rows::CanyonRows`] @@ -19,4 +19,16 @@ pub trait Transaction { { async move { input.query(stmt.as_ref(), params.as_ref()).await } } + + fn query_one<'a, S, Z, R: RowMapper>( + stmt: S, + params: Z, + input: impl DbConnection + Send + 'a, + ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send + where + S: AsRef + Display + Sync + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, + { + async move { input.query_one(stmt.as_ref(), params.as_ref()).await } + } } diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index 13ab444a..a8c4b10e 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -90,7 +90,7 @@ impl Migrations { } /// Fetches a concrete schema metadata by target the database - /// chosen by it's datasource name property + /// chosen by its datasource name property async fn fetch_database( ds_name: &str, db_conn: &mut DatabaseConnection, From 92d8d1c284dd1f2ef0415e8103e5464a4a52e4e4 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 5 Feb 2025 10:48:49 +0100 Subject: [PATCH 065/155] feat(wip): query_rows impl as the future replacement for the CanyonRows wrapper --- .../src/connection/db_clients/mssql.rs | 9 +++ .../src/connection/db_clients/mysql.rs | 9 +++ .../src/connection/db_clients/postgresql.rs | 47 +++++++++++++ canyon_core/src/connection/db_connector.rs | 68 ++++++++++++++++++- canyon_core/src/transaction.rs | 17 +++++ 5 files changed, 148 insertions(+), 2 deletions(-) diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs index 045d8156..ebd7dbd6 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -1,6 +1,7 @@ #[cfg(feature = "mssql")] use async_std::net::TcpStream; use std::error::Error; +use std::fmt::Display; use std::future::Future; use crate::connection::database_type::DatabaseType; use crate::connection::db_connector::DbConnection; @@ -24,6 +25,14 @@ impl DbConnection for SqlServerConnection { sqlserver_query_launcher::launch(stmt, params, self) } + fn query_rows<'a, S, Z, R: RowMapper>(&self, stmt: S, params: Z) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send + where + S: AsRef + Display + Sync + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a + { + async move { todo!() } + } + fn query_one<'a, R>(&self, stmt: &str, params: &[&'a (dyn QueryParameter<'a>)]) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where diff --git a/canyon_core/src/connection/db_clients/mysql.rs b/canyon_core/src/connection/db_clients/mysql.rs index 875e1d34..2b626158 100644 --- a/canyon_core/src/connection/db_clients/mysql.rs +++ b/canyon_core/src/connection/db_clients/mysql.rs @@ -1,6 +1,7 @@ #[cfg(feature = "mysql")] use mysql_async::Pool; use std::error::Error; +use std::fmt::Display; use std::future::Future; use crate::connection::database_type::DatabaseType; use crate::connection::db_connector::DbConnection; @@ -26,6 +27,14 @@ impl DbConnection for MysqlConnection { mysql_query_launcher::query(stmt, params, self) } + fn query_rows<'a, S, Z, R: RowMapper>(&self, stmt: S, params: Z) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send + where + S: AsRef + Display + Sync + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a + { + async move { todo!() } + } + fn query_one<'a, R: RowMapper>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send { diff --git a/canyon_core/src/connection/db_clients/postgresql.rs b/canyon_core/src/connection/db_clients/postgresql.rs index e50eb8c9..86634a82 100644 --- a/canyon_core/src/connection/db_clients/postgresql.rs +++ b/canyon_core/src/connection/db_clients/postgresql.rs @@ -1,11 +1,13 @@ use crate::{query_parameters::QueryParameter, rows::CanyonRows}; use std::error::Error; +use std::fmt::Display; use std::future::Future; use crate::connection::database_type::DatabaseType; use crate::connection::db_connector::DbConnection; #[cfg(feature = "postgres")] use tokio_postgres::Client; use crate::mapper::RowMapper; +use crate::row::Row; /// A connection with a `PostgreSQL` database #[cfg(feature = "postgres")] @@ -24,6 +26,18 @@ impl DbConnection for PostgreSqlConnection { postgres_query_launcher::query(stmt, params, self) } + fn query_rows<'a, S, Z, R: RowMapper>( + &self, + stmt: S, + params: Z, + ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send + where + S: AsRef + Display + Sync + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a + { + postgres_query_launcher::query_rows(stmt, params, self) + } + fn query_one<'a, R>(&self, stmt: &str, params: &[&'a (dyn QueryParameter<'a>)]) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where @@ -39,6 +53,9 @@ impl DbConnection for PostgreSqlConnection { #[cfg(feature = "postgres")] pub(crate) mod postgres_query_launcher { + use std::iter::Map; + use std::slice::Iter; + use tokio_postgres::types::ToSql; use super::*; #[inline(always)] @@ -62,4 +79,34 @@ pub(crate) mod postgres_query_launcher { let r = conn.client.query_one(stmt, m_params.as_slice()).await?; Ok(Some(T::deserialize_postgresql(&r))) } + + #[inline(always)] + pub(crate) async fn query_rows<'a, S, Z, R: RowMapper>( + stmt: S, + params: Z, + conn: &PostgreSqlConnection, + ) -> Result, Box<(dyn Error + Sync + Send + 'a)>> + where + S: AsRef + Display + Sync + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a + { + Ok(conn.client + .query(stmt.as_ref(), &get_psql_params(params)) + .await? + .iter() + .map(|row| { R::deserialize_postgresql(row) }) + .collect() + ) + } + + fn get_psql_params<'a, Z>(params: Z) + -> Vec<&'a (dyn ToSql + Sync)> + where Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a + { + params + .as_ref() + .iter() + .map(|param| param.as_postgres_param()) + .collect::>() + } } diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index 19fea896..85b423b5 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -10,9 +10,11 @@ use crate::connection::{find_datasource_by_name_or_try_default, get_database_con use crate::query_parameters::QueryParameter; use crate::rows::CanyonRows; use std::error::Error; +use std::fmt::Display; use std::future::Future; use crate::connection::db_connector::connection_helpers::{db_conn_launch_impl, db_conn_query_one_impl}; use crate::mapper::RowMapper; +use crate::row::Row; pub trait DbConnection { fn query<'a>( @@ -21,6 +23,15 @@ pub trait DbConnection { params: &[&'a dyn QueryParameter<'a>], ) -> impl Future>> + Send; + fn query_rows<'a, S, Z, R: RowMapper>( + &self, + stmt: S, + params: Z, + ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send + where + S: AsRef + Display + Sync + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a; + fn query_one<'a, R: RowMapper>( &self, stmt: &str, @@ -40,11 +51,23 @@ impl DbConnection for &str { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result>{ - let sane_ds_name = if !self.is_empty() { Some(*self) } else { None }; - let conn = get_database_connection_by_ds(sane_ds_name).await?; + let conn = get_database_connection_by_ds(Some(&self)).await?; conn.query(stmt, params).await } + async fn query_rows<'a, S, Z, R: RowMapper>( + &self, + stmt: S, + params: Z, + ) -> Result, Box<(dyn Error + Sync + Send + 'a)>> + where + S: AsRef + Display + Sync + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a + { + let conn = get_database_connection_by_ds(Some(&self)).await?; + conn.query_rows(stmt, params).await + } + async fn query_one<'a, R: RowMapper>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) -> Result, Box<(dyn Error + Sync + Send)>> { @@ -81,6 +104,27 @@ impl DbConnection for DatabaseConnection { db_conn_launch_impl(self, stmt, params).await } + async fn query_rows<'a, S, Z, R: RowMapper>( + &self, + stmt: S, + params: Z, + ) -> Result, Box<(dyn Error + Sync + Send + 'a)>> + where + S: AsRef + Display + Sync + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a + { + match self { + #[cfg(feature = "postgres")] + DatabaseConnection::Postgres(client) => client.query_rows(stmt, params).await, + + #[cfg(feature = "mssql")] + DatabaseConnection::SqlServer(client) => client.query_rows(stmt, params).await, + + #[cfg(feature = "mysql")] + DatabaseConnection::MySQL(client) => client.query_rows(stmt, params).await, + } + } + async fn query_one<'a, R: RowMapper>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) -> Result, Box<(dyn Error + Sync + Send)>> { @@ -100,6 +144,26 @@ impl DbConnection for &mut DatabaseConnection { ) -> Result> { db_conn_launch_impl(self, stmt, params).await } + async fn query_rows<'a, S, Z, R: RowMapper>( + &self, + stmt: S, + params: Z, + ) -> Result, Box<(dyn Error + Sync + Send + 'a)>> + where + S: AsRef + Display + Sync + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a + { + match self { + #[cfg(feature = "postgres")] + DatabaseConnection::Postgres(client) => client.query_rows(stmt, params).await, + + #[cfg(feature = "mssql")] + DatabaseConnection::SqlServer(client) => client.query_rows(stmt, params).await, + + #[cfg(feature = "mysql")] + DatabaseConnection::MySQL(client) => client.query_rows(stmt, params).await, + } + } async fn query_one<'a, R: RowMapper>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) -> Result, Box<(dyn Error + Sync + Send)>> diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index 72cf8650..fe66aece 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -3,6 +3,7 @@ use crate::{query_parameters::QueryParameter, rows::CanyonRows}; use std::error::Error; use std::{fmt::Display, future::Future}; use crate::mapper::RowMapper; +use crate::row::Row; pub trait Transaction { /// Performs a query against the targeted database by the selected or @@ -20,6 +21,22 @@ pub trait Transaction { async move { input.query(stmt.as_ref(), params.as_ref()).await } } + /// + /// *Impl notes:* allow async fn in trait is provisionally here because we have to + /// rework certain details around the bounds of QueryParameter + #[allow(async_fn_in_trait)] + async fn query_rows<'a, S, Z, R: RowMapper>( + stmt: S, + params: Z, + input: impl DbConnection + Send + 'a, + ) -> Result, Box<(dyn Error + Sync + Send + 'a)>> + where + S: AsRef + Display + Sync + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, + { + input.query_rows(stmt, params).await + } + fn query_one<'a, S, Z, R: RowMapper>( stmt: S, params: Z, From 042a234fd36f50e37d0d8b233b110169841a9d7a Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 5 Feb 2025 16:51:29 +0100 Subject: [PATCH 066/155] feat(wip): reworking the new Transaction::query lifetime and type bounds --- .../src/connection/db_clients/mssql.rs | 107 ++- .../src/connection/db_clients/mysql.rs | 82 +- .../src/connection/db_clients/postgresql.rs | 30 +- canyon_core/src/connection/db_connector.rs | 63 +- canyon_core/src/transaction.rs | 33 +- canyon_crud/src/crud.rs | 160 +-- .../src/query_elements/query_builder.rs | 9 +- canyon_macros/src/query_operations/delete.rs | 10 +- .../src/query_operations/foreign_key.rs | 3 +- canyon_macros/src/query_operations/insert.rs | 7 +- .../src/query_operations/macro_template.rs | 8 +- canyon_macros/src/query_operations/read.rs | 18 +- canyon_macros/src/query_operations/update.rs | 4 +- canyon_migrations/src/migrations/handler.rs | 2 +- canyon_migrations/src/migrations/memory.rs | 4 +- canyon_migrations/src/migrations/processor.rs | 2 +- tests/crud/delete_operations.rs | 318 +++--- tests/crud/foreign_key_operations.rs | 326 +++---- tests/crud/init_mssql.rs | 124 +-- tests/crud/insert_operations.rs | 634 ++++++------ tests/crud/querybuilder_operations.rs | 908 +++++++++--------- tests/crud/read_operations.rs | 226 ++--- tests/crud/update_operations.rs | 284 +++--- tests/migrations/mod.rs | 2 +- tests/tests_models/player.rs | 48 +- tests/tests_models/tournament.rs | 30 +- 26 files changed, 1730 insertions(+), 1712 deletions(-) diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs index ebd7dbd6..dd9c7514 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -16,21 +16,21 @@ pub struct SqlServerConnection { } impl DbConnection for SqlServerConnection { - fn query<'a>( + fn query_rows<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> impl Future>> + Send { - sqlserver_query_launcher::launch(stmt, params, self) + sqlserver_query_launcher::query_rows(stmt, params, self) } - fn query_rows<'a, S, Z, R: RowMapper>(&self, stmt: S, params: Z) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send + fn query<'a, S, R: RowMapper>(&self, stmt: S, params: &[&'a (dyn QueryParameter<'_>)]) + -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send where - S: AsRef + Display + Sync + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a + S: AsRef + Display + Send { - async move { todo!() } + sqlserver_query_launcher::query(stmt, params, self) } fn query_one<'a, R>(&self, stmt: &str, params: &[&'a (dyn QueryParameter<'a>)]) @@ -48,61 +48,71 @@ impl DbConnection for SqlServerConnection { #[cfg(feature = "mssql")] pub(crate) mod sqlserver_query_launcher { + use tiberius::QueryStream; use crate::mapper::RowMapper; use super::*; - + #[inline(always)] - pub(crate) async fn launch<'a>( + pub(crate) async fn query<'a, S, R: RowMapper>( + stmt: S, + params: &[&'a dyn QueryParameter<'_>], + conn: &SqlServerConnection, + ) -> Result, Box<(dyn Error + Sync + Send)>> + where + S: AsRef + Display + Send + { + Ok(execute_query(stmt.as_ref(), params, conn) + .await? + .into_results() + .await? + .into_iter() + .flatten() + .map(|row| R::deserialize_sqlserver(&row)) + .collect::>()) + } + + #[inline(always)] + pub(crate) async fn query_rows<'a>( stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &SqlServerConnection, ) -> Result> { - // Re-generate de insert statement to adequate it to the SQL SERVER syntax to retrieve the PK value(s) after insert - // TODO: redo this branch into the generated queries, before the MACROS - let mut stmt = String::from(stmt); - if stmt.contains("RETURNING") { - let c = stmt.clone(); - let temp = c.split_once("RETURNING").unwrap(); - let temp2 = temp.0.split_once("VALUES").unwrap(); - - stmt = format!( - "{} OUTPUT inserted.{} VALUES {}", - temp2.0.trim(), - temp.1.trim(), - temp2.1.trim() - ); - } - - // TODO: We must address the query generation. Look at the returning example, or the - // replace below. We may use our own type Query to address this concerns when the query - // is generated - let mut mssql_query = Query::new(stmt.to_owned().replace('$', "@P")); - params.iter().for_each(|param| mssql_query.bind(*param)); - - #[allow(mutable_transmutes)] // TODO: pls solve this elegantly someday :( - let sqlservconn = - unsafe { std::mem::transmute::<&SqlServerConnection, &mut SqlServerConnection>(conn) }; - let _results = mssql_query - .query(sqlservconn.client) + let result = execute_query(stmt, params, conn) .await? .into_results() - .await?; + .await? + .into_iter() + .flatten() + .collect(); - Ok(CanyonRows::Tiberius( - _results.into_iter().flatten().collect(), - )) + Ok(CanyonRows::Tiberius(result)) } pub(crate) async fn query_one<'a, R>( stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &SqlServerConnection, - ) - -> Result, Box<(dyn Error + Send + Sync)>> + ) -> Result, Box<(dyn Error + Send + Sync)>> where R: RowMapper { - let mut stmt = String::from(stmt); + let result = execute_query(stmt, params, conn) + .await? + .into_row() + .await?; + + match result { + Some(r) => { Ok(Some(R::deserialize_sqlserver(&r))) } + None => { Ok(None) } + } + } + + async fn execute_query<'a, S>(stmt: S, params: &[&'a (dyn QueryParameter<'_>)], conn: &SqlServerConnection) + -> Result, Box<(dyn Error + Send + Sync)>> + where + S: AsRef + Display + { + let mut stmt = String::from(stmt.as_ref()); if stmt.contains("RETURNING") { let c = stmt.clone(); let temp = c.split_once("RETURNING").unwrap(); @@ -115,7 +125,7 @@ pub(crate) mod sqlserver_query_launcher { temp2.1.trim() ); } - + // TODO: We must address the query generation. Look at the returning example, or the // replace below. We may use our own type Query to address this concerns when the query // is generated @@ -125,15 +135,8 @@ pub(crate) mod sqlserver_query_launcher { #[allow(mutable_transmutes)] // TODO: pls solve this elegantly someday :( let sqlservconn = unsafe { std::mem::transmute::<&SqlServerConnection, &mut SqlServerConnection>(conn) }; - let result = mssql_query + Ok(mssql_query .query(sqlservconn.client) - .await? - .into_row() - .await?; - - match result { - Some(r) => { Ok(Some(R::deserialize_sqlserver(&r))) } - None => { Ok(None) } - } + .await?) } } diff --git a/canyon_core/src/connection/db_clients/mysql.rs b/canyon_core/src/connection/db_clients/mysql.rs index 2b626158..2cb31f2d 100644 --- a/canyon_core/src/connection/db_clients/mysql.rs +++ b/canyon_core/src/connection/db_clients/mysql.rs @@ -18,21 +18,21 @@ pub struct MysqlConnection { } impl DbConnection for MysqlConnection { - fn query<'a>( + fn query_rows<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> impl Future>> + Send { - mysql_query_launcher::query(stmt, params, self) + mysql_query_launcher::query_rows(stmt, params, self) } - fn query_rows<'a, S, Z, R: RowMapper>(&self, stmt: S, params: Z) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send + fn query<'a, S, R: RowMapper>(&self, stmt: S, params: &[&'a (dyn QueryParameter<'_>)],) + -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send where - S: AsRef + Display + Sync + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a + S: AsRef + Display + Send { - async move { todo!() } + mysql_query_launcher::query(stmt, params, self) } fn query_one<'a, R: RowMapper>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) @@ -61,28 +61,62 @@ pub(crate) mod mysql_query_launcher { use regex::Regex; use std::sync::Arc; - #[inline(always)] // TODO: very provisional implementation! care! - // TODO: would be better to launch a simple query for the last id? - pub(crate) async fn query_one<'a, R: RowMapper>( - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + #[inline(always)] + pub async fn query<'a, S, R: RowMapper>( + stmt: S, + params: &[&'a dyn QueryParameter<'_>], conn: &MysqlConnection, - ) -> Result, Box<(dyn Error + Sync + Send)>> { - Ok(query(stmt, params, conn) - .await? - .first_row() + ) -> Result, Box<(dyn Error + Sync + Send)>> + where + S: AsRef + Display + Send + { + Ok( + execute_query(stmt, params, conn).await? + .iter() + .map(|row| R::deserialize_mysql(row)) + .collect() ) } - #[inline(always)] - pub(crate) async fn query<'a>( + #[inline(always)] + pub(crate) async fn query_rows<'a>( stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &MysqlConnection, ) -> Result> { + Ok(CanyonRows::MySQL( + execute_query(stmt, params, conn).await? + )) + } + + #[inline(always)] + pub(crate) async fn query_one<'a, R: RowMapper>( + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + conn: &MysqlConnection, + ) -> Result, Box<(dyn Error + Sync + Send)>> { + let result = execute_query(stmt, params, conn) + .await?; + + match result.first() { + Some(r) => Ok(Some(R::deserialize_mysql(r))), + None => Ok(None), + } + } + + #[inline(always)] // TODO: very provisional implementation! care! + // TODO: would be better to launch a simple query for the last id? + async fn execute_query<'a, S>( + stmt: S, + params: &[&'a dyn QueryParameter<'_>], + conn: &MysqlConnection, + ) -> Result, Box<(dyn Error + Sync + Send)>> + where + S: AsRef + Display + Send + { let mysql_connection = conn.client.get_conn().await?; - let stmt_with_escape_characters = regex::escape(stmt); + let stmt_with_escape_characters = regex::escape(stmt.as_ref()); let query_string = Regex::new(DETECT_PARAMS_IN_QUERY)?.replace_all(&stmt_with_escape_characters, "?"); @@ -99,7 +133,7 @@ pub(crate) mod mysql_query_launcher { } let params_query: Vec = - reorder_params(stmt, params, |f| (*f).as_mysql_param().to_value()); + reorder_params(stmt.as_ref(), params, |f| (*f).as_mysql_param().to_value()); let query_with_params = QueryWithParams { query: query_string, @@ -108,8 +142,7 @@ pub(crate) mod mysql_query_launcher { let mut query_result = query_with_params .run(mysql_connection) - .await - .expect("Error executing query in mysql"); + .await?; let result_rows = if is_insert { let last_insert = query_result @@ -124,11 +157,10 @@ pub(crate) mod mysql_query_launcher { } else { query_result .collect::() - .await - .expect("Error resolved trait FromRow in mysql") + .await? }; - let a = CanyonRows::MySQL(result_rows); - Ok(a) + + Ok(result_rows) } } diff --git a/canyon_core/src/connection/db_clients/postgresql.rs b/canyon_core/src/connection/db_clients/postgresql.rs index 86634a82..5cabe5d1 100644 --- a/canyon_core/src/connection/db_clients/postgresql.rs +++ b/canyon_core/src/connection/db_clients/postgresql.rs @@ -7,7 +7,6 @@ use crate::connection::db_connector::DbConnection; #[cfg(feature = "postgres")] use tokio_postgres::Client; use crate::mapper::RowMapper; -use crate::row::Row; /// A connection with a `PostgreSQL` database #[cfg(feature = "postgres")] @@ -17,25 +16,24 @@ pub struct PostgreSqlConnection { } impl DbConnection for PostgreSqlConnection { - fn query<'a>( + fn query_rows<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> impl Future>> + Send { - postgres_query_launcher::query(stmt, params, self) + postgres_query_launcher::query_rows(stmt, params, self) } - fn query_rows<'a, S, Z, R: RowMapper>( + fn query<'a, S, R: RowMapper>( &self, stmt: S, - params: Z, + params: &[&'a (dyn QueryParameter<'_>)], ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send where - S: AsRef + Display + Sync + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a + S: AsRef + Display + Send { - postgres_query_launcher::query_rows(stmt, params, self) + postgres_query_launcher::query(stmt, params, self) } fn query_one<'a, R>(&self, stmt: &str, params: &[&'a (dyn QueryParameter<'a>)]) @@ -53,13 +51,13 @@ impl DbConnection for PostgreSqlConnection { #[cfg(feature = "postgres")] pub(crate) mod postgres_query_launcher { - use std::iter::Map; - use std::slice::Iter; + + use tokio_postgres::types::ToSql; use super::*; #[inline(always)] - pub(crate) async fn query<'a>( + pub(crate) async fn query_rows<'a>( stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &PostgreSqlConnection, @@ -81,14 +79,13 @@ pub(crate) mod postgres_query_launcher { } #[inline(always)] - pub(crate) async fn query_rows<'a, S, Z, R: RowMapper>( + pub(crate) async fn query<'a, S, R: RowMapper>( stmt: S, - params: Z, + params: &[&'a (dyn QueryParameter<'_>)], conn: &PostgreSqlConnection, ) -> Result, Box<(dyn Error + Sync + Send + 'a)>> where - S: AsRef + Display + Sync + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a + S: AsRef + Display + Send { Ok(conn.client .query(stmt.as_ref(), &get_psql_params(params)) @@ -99,9 +96,8 @@ pub(crate) mod postgres_query_launcher { ) } - fn get_psql_params<'a, Z>(params: Z) + fn get_psql_params<'a>(params: &[&'a (dyn QueryParameter<'_>)],) -> Vec<&'a (dyn ToSql + Sync)> - where Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a { params .as_ref() diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index 85b423b5..e9f8b7d4 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -14,23 +14,21 @@ use std::fmt::Display; use std::future::Future; use crate::connection::db_connector::connection_helpers::{db_conn_launch_impl, db_conn_query_one_impl}; use crate::mapper::RowMapper; -use crate::row::Row; pub trait DbConnection { - fn query<'a>( + fn query_rows<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> impl Future>> + Send; - fn query_rows<'a, S, Z, R: RowMapper>( + fn query<'a, S, R: RowMapper>( &self, stmt: S, - params: Z, + params: &[&'a (dyn QueryParameter<'_>)], ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send where - S: AsRef + Display + Sync + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a; + S: AsRef + Display + Send; fn query_one<'a, R: RowMapper>( &self, @@ -46,26 +44,25 @@ pub trait DbConnection { /// directly with an [`&str`] that must match one of the datasources defined /// within the user config file impl DbConnection for &str { - async fn query<'a>( + async fn query_rows<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result>{ - let conn = get_database_connection_by_ds(Some(&self)).await?; - conn.query(stmt, params).await + let conn = get_database_connection_by_ds(Some(self)).await?; + conn.query_rows(stmt, params).await } - async fn query_rows<'a, S, Z, R: RowMapper>( + async fn query<'a, S, R: RowMapper>( &self, stmt: S, - params: Z, + params: &[&'a (dyn QueryParameter<'_>)], ) -> Result, Box<(dyn Error + Sync + Send + 'a)>> where - S: AsRef + Display + Sync + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a + S: AsRef + Display + Send, { - let conn = get_database_connection_by_ds(Some(&self)).await?; - conn.query_rows(stmt, params).await + let conn = get_database_connection_by_ds(Some(self)).await?; + conn.query(stmt, params).await } async fn query_one<'a, R: RowMapper>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) @@ -96,7 +93,7 @@ pub enum DatabaseConnection { } impl DbConnection for DatabaseConnection { - async fn query<'a>( + async fn query_rows<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], @@ -104,24 +101,23 @@ impl DbConnection for DatabaseConnection { db_conn_launch_impl(self, stmt, params).await } - async fn query_rows<'a, S, Z, R: RowMapper>( + async fn query<'a, S, R: RowMapper>( &self, stmt: S, - params: Z, + params: &[&'a (dyn QueryParameter<'_>)], ) -> Result, Box<(dyn Error + Sync + Send + 'a)>> where - S: AsRef + Display + Sync + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a + S: AsRef + Display + Send, { match self { #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(client) => client.query_rows(stmt, params).await, + DatabaseConnection::Postgres(client) => client.query(stmt, params).await, #[cfg(feature = "mssql")] - DatabaseConnection::SqlServer(client) => client.query_rows(stmt, params).await, + DatabaseConnection::SqlServer(client) => client.query(stmt, params).await, #[cfg(feature = "mysql")] - DatabaseConnection::MySQL(client) => client.query_rows(stmt, params).await, + DatabaseConnection::MySQL(client) => client.query(stmt, params).await, } } @@ -137,31 +133,30 @@ impl DbConnection for DatabaseConnection { } impl DbConnection for &mut DatabaseConnection { - async fn query<'a>( + async fn query_rows<'a>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result> { db_conn_launch_impl(self, stmt, params).await } - async fn query_rows<'a, S, Z, R: RowMapper>( + async fn query<'a, S, R: RowMapper>( &self, stmt: S, - params: Z, + params: &[&'a (dyn QueryParameter<'_>)], ) -> Result, Box<(dyn Error + Sync + Send + 'a)>> where - S: AsRef + Display + Sync + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a + S: AsRef + Display + Send, { match self { #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(client) => client.query_rows(stmt, params).await, + DatabaseConnection::Postgres(client) => client.query(stmt, params).await, #[cfg(feature = "mssql")] - DatabaseConnection::SqlServer(client) => client.query_rows(stmt, params).await, + DatabaseConnection::SqlServer(client) => client.query(stmt, params).await, #[cfg(feature = "mysql")] - DatabaseConnection::MySQL(client) => client.query_rows(stmt, params).await, + DatabaseConnection::MySQL(client) => client.query(stmt, params).await, } } @@ -327,13 +322,13 @@ mod connection_helpers { pub(crate) async fn db_conn_launch_impl<'a>(c: &DatabaseConnection, stmt: &str, params: &[&'a (dyn QueryParameter<'a> + 'a)]) -> Result>{ match c { #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(client) => client.query(stmt, params).await, + DatabaseConnection::Postgres(client) => client.query_rows(stmt, params).await, #[cfg(feature = "mssql")] - DatabaseConnection::SqlServer(client) => client.query(stmt, params).await, + DatabaseConnection::SqlServer(client) => client.query_rows(stmt, params).await, #[cfg(feature = "mysql")] - DatabaseConnection::MySQL(client) => client.query(stmt, params).await, + DatabaseConnection::MySQL(client) => client.query_rows(stmt, params).await, } } diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index fe66aece..8dc48339 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -3,38 +3,31 @@ use crate::{query_parameters::QueryParameter, rows::CanyonRows}; use std::error::Error; use std::{fmt::Display, future::Future}; use crate::mapper::RowMapper; -use crate::row::Row; pub trait Transaction { /// Performs a query against the targeted database by the selected or /// the defaulted datasource, wrapping the resultant collection of entities /// in [`super::rows::CanyonRows`] - fn query<'a, S, Z>( + fn query_rows<'a, S, Z>( stmt: S, params: Z, input: impl DbConnection + Send + 'a, ) -> impl Future>> + Send where - S: AsRef + Display + Sync + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, + S: AsRef + Display + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send + 'a, { - async move { input.query(stmt.as_ref(), params.as_ref()).await } + async move { input.query_rows(stmt.as_ref(), params.as_ref()).await } } - - /// - /// *Impl notes:* allow async fn in trait is provisionally here because we have to - /// rework certain details around the bounds of QueryParameter - #[allow(async_fn_in_trait)] - async fn query_rows<'a, S, Z, R: RowMapper>( - stmt: S, - params: Z, - input: impl DbConnection + Send + 'a, - ) -> Result, Box<(dyn Error + Sync + Send + 'a)>> + + fn query<'a, R: RowMapper>( + stmt: &str, + params: &[&'a (dyn QueryParameter<'a>)], + input: impl DbConnection + Send, + ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> where - S: AsRef + Display + Sync + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, { - input.query_rows(stmt, params).await + async move { input.query(stmt, params).await } } fn query_one<'a, S, Z, R: RowMapper>( @@ -43,8 +36,8 @@ pub trait Transaction { input: impl DbConnection + Send + 'a, ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where - S: AsRef + Display + Sync + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Sync + Send + 'a, + S: AsRef + Display + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send + 'a, { async move { input.query_one(stmt.as_ref(), params.as_ref()).await } } diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 4bc86341..f4977394 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -27,93 +27,93 @@ where T: CrudOperations + RowMapper, { fn find_all() -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send; - + fn find_all_with<'a, I>( input: I, ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send where I: DbConnection + Send + 'a; - + fn find_all_unchecked() -> impl Future> + Send; - + fn find_all_unchecked_with<'a, I>(input: I) -> impl Future> + Send where I: DbConnection + Send + 'a; - - fn select_query<'a>() -> SelectQueryBuilder<'a, T, &'a str>; - - fn select_query_with<'a, I>(input: I) -> SelectQueryBuilder<'a, T, I> - where - I: DbConnection + Send + 'a; - - fn count() -> impl Future>> + Send; - - fn count_with<'a, I>( - input: I, - ) -> impl Future>> + Send - where - I: DbConnection + Send + 'a; - - fn find_by_pk<'a>( - value: &'a dyn QueryParameter<'a>, - ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send; - - fn find_by_pk_with<'a, I>( - value: &'a dyn QueryParameter<'a>, - input: I, - ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send - where - I: DbConnection + Send + 'a; - - fn insert<'a>( - &'a mut self, - ) -> impl Future>> + Send; - - fn insert_with<'a, I>( - &mut self, - input: I, - ) -> impl Future>> + Send - where - I: DbConnection + Send + 'a; - - fn multi_insert<'a>( - instances: &'a mut [&'a mut T], - ) -> impl Future>> + Send; - - fn multi_insert_with<'a, I>( - instances: &'a mut [&'a mut T], - input: I, - ) -> impl Future>> + Send - where - I: DbConnection + Send + 'a; - - fn update(&self) -> impl Future>> + Send; - - fn update_with<'a, I>( - &self, - input: I, - ) -> impl Future>> + Send - where - I: DbConnection + Send + 'a; - - fn update_query<'a>() -> UpdateQueryBuilder<'a, T, &'a str>; - - fn update_query_with<'a, I>(input: I) -> UpdateQueryBuilder<'a, T, I> - where - I: DbConnection + Send + 'a; - - fn delete(&self) -> impl Future>> + Send; - - fn delete_with<'a, I>( - &self, - input: I, - ) -> impl Future>> + Send - where - I: DbConnection + Send + 'a; - - fn delete_query<'a>() -> DeleteQueryBuilder<'a, T, &'a str>; - - fn delete_query_with<'a, I>(input: I) -> DeleteQueryBuilder<'a, T, I> - where - I: DbConnection + Send + 'a; + + // fn select_query<'a>() -> SelectQueryBuilder<'a, T, &'a str>; + // + // fn select_query_with<'a, I>(input: I) -> SelectQueryBuilder<'a, T, I> + // where + // I: DbConnection + Send + 'a; + // + // fn count() -> impl Future>> + Send; + // + // fn count_with<'a, I>( + // input: I, + // ) -> impl Future>> + Send + // where + // I: DbConnection + Send + 'a; + // + // fn find_by_pk<'a>( + // value: &'a dyn QueryParameter<'a>, + // ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send; + // + // fn find_by_pk_with<'a, I>( + // value: &'a dyn QueryParameter<'a>, + // input: I, + // ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send + // where + // I: DbConnection + Send + 'a; + // + // fn insert<'a>( + // &'a mut self, + // ) -> impl Future>> + Send; + // + // fn insert_with<'a, I>( + // &mut self, + // input: I, + // ) -> impl Future>> + Send + // where + // I: DbConnection + Send + 'a; + // + // fn multi_insert<'a>( + // instances: &'a mut [&'a mut T], + // ) -> impl Future>> + Send; + // + // fn multi_insert_with<'a, I>( + // instances: &'a mut [&'a mut T], + // input: I, + // ) -> impl Future>> + Send + // where + // I: DbConnection + Send + 'a; + // + // fn update(&self) -> impl Future>> + Send; + // + // fn update_with<'a, I>( + // &self, + // input: I, + // ) -> impl Future>> + Send + // where + // I: DbConnection + Send + 'a; + // + // fn update_query<'a>() -> UpdateQueryBuilder<'a, T, &'a str>; + // + // fn update_query_with<'a, I>(input: I) -> UpdateQueryBuilder<'a, T, I> + // where + // I: DbConnection + Send + 'a; + // + // fn delete(&self) -> impl Future>> + Send; + // + // fn delete_with<'a, I>( + // &self, + // input: I, + // ) -> impl Future>> + Send + // where + // I: DbConnection + Send + 'a; + // + // fn delete_query<'a>() -> DeleteQueryBuilder<'a, T, &'a str>; + // + // fn delete_query_with<'a, I>(input: I) -> DeleteQueryBuilder<'a, T, I> + // where + // I: DbConnection + Send + 'a; } diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index 7d8389e0..b4221890 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -177,13 +177,12 @@ where ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> { self.query.sql.push(';'); - Ok(T::query( - self.query.sql.clone(), - self.query.params.to_vec(), + T::query( + &self.query.sql, + &self.query.params, self.input, ) - .await? - .into_results::()) + .await } pub fn r#where>(&mut self, r#where: Z, op: impl Operator) { diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index 8586df2a..adba376c 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -51,10 +51,12 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri }); } - let delete_with_querybuilder = generate_delete_query_tokens(&ty, table_schema_data); - delete_ops_tokens.extend(delete_with_querybuilder); + // let delete_with_querybuilder = generate_delete_query_tokens(&ty, table_schema_data); + // delete_ops_tokens.extend(delete_with_querybuilder); + // + // delete_ops_tokens - delete_ops_tokens + quote!{} } /// Generates the TokenStream for the __delete() CRUD operation as a @@ -112,7 +114,6 @@ mod __details { .query_string(stmt) .forwarded_parameters(quote! {&[#pk_field_value]}) .propagate_transaction_result() - .disable_mapping() .raw_return() .with_no_result_value() } @@ -135,7 +136,6 @@ mod __details { .query_string(stmt) .forwarded_parameters(quote! {&[#pk_field_value]}) .propagate_transaction_result() - .disable_mapping() .raw_return() .with_no_result_value() } diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index 0eda45b6..cc640e2d 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -240,5 +240,6 @@ fn generate_find_by_reverse_foreign_key_tokens( } } - rev_fk_quotes + rev_fk_quotes; + vec![] } diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 663ca8f4..f896a1a4 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -166,10 +166,11 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri }); - let multi_insert_tokens = generate_multiple_insert_tokens(macro_data, table_schema_data); - insert_ops_tokens.extend(multi_insert_tokens); + // let multi_insert_tokens = generate_multiple_insert_tokens(macro_data, table_schema_data); + // insert_ops_tokens.extend(multi_insert_tokens); - insert_ops_tokens + // insert_ops_tokens + quote!{} } /// Generates the TokenStream for the __insert() CRUD operation, but being available diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index c102bbc7..f5e4ec08 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -21,7 +21,7 @@ pub struct MacroOperationBuilder { with_no_result_value: bool, // Ok(()) transaction_as_variable: bool, direct_error_return: Option, - disable_mapping: bool, + enable_mapping: bool, raw_return: bool, propagate_transaction_result: bool, post_body: Option, @@ -55,7 +55,7 @@ impl MacroOperationBuilder { with_no_result_value: false, transaction_as_variable: false, direct_error_return: None, - disable_mapping: false, + enable_mapping: false, raw_return: false, propagate_transaction_result: false, post_body: None, @@ -285,7 +285,7 @@ impl MacroOperationBuilder { } pub fn disable_mapping(mut self) -> Self { - self.disable_mapping = true; + self.enable_mapping = true; self } @@ -333,7 +333,7 @@ impl MacroOperationBuilder { if self.propagate_transaction_result { base_body_tokens.extend(quote! { ? }) }; - if !self.disable_mapping { + if self.enable_mapping { base_body_tokens.extend(quote! { .into_results::<#ty>() }) }; if self.with_no_result_value { diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index e9fe0ff9..dd95eaa6 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -34,13 +34,13 @@ pub fn generate_read_operations_tokens( #find_all_with #find_all_unchecked #find_all_unchecked_with - - #count - #count_with - - #find_by_pk_complex_tokens - - #read_querybuilder_ops + // + // #count + // #count_with + // + // #find_by_pk_complex_tokens + // + // #read_querybuilder_ops } } @@ -250,7 +250,6 @@ mod __details { } }) .propagate_transaction_result() - .disable_mapping() .raw_return() } @@ -273,7 +272,6 @@ mod __details { } }) .propagate_transaction_result() - .disable_mapping() .raw_return() } } @@ -299,7 +297,6 @@ mod __details { .input_parameters(quote! { value: &'a dyn canyon_sql::core::QueryParameter<'a> }) .forwarded_parameters(quote! { vec![value] }) .propagate_transaction_result() - .disable_mapping() .single_result() .transaction_as_variable(quote! { match transaction_result { // NOTE: dark magic. Should be refactored @@ -324,7 +321,6 @@ mod __details { .input_parameters(quote! { value: &'a dyn canyon_sql::core::QueryParameter<'a> }) .forwarded_parameters(quote! { vec![value] }) .propagate_transaction_result() - .disable_mapping() .single_result() .transaction_as_variable(quote! { match transaction_result { // NOTE: dark magic. Should be refactored diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index 516564cc..ca568ec3 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -86,7 +86,9 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let querybuilder_update_tokens = generate_update_query_tokens(ty, table_schema_data); update_ops_tokens.extend(querybuilder_update_tokens); - update_ops_tokens + update_ops_tokens; + + quote!{} } /// Generates the TokenStream for the __update() CRUD operation diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index a8c4b10e..a3b3d65e 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -105,7 +105,7 @@ impl Migrations { DatabaseType::MySQL => todo!("Not implemented fetch database in mysql"), }; - Self::query(query, [], db_conn).await.unwrap_or_else(|_| { + Self::query_rows(query, [], db_conn).await.unwrap_or_else(|_| { panic!("Error querying the schema information for the datasource: {ds_name}") }) } diff --git a/canyon_migrations/src/migrations/memory.rs b/canyon_migrations/src/migrations/memory.rs index e82e0d70..eea809fa 100644 --- a/canyon_migrations/src/migrations/memory.rs +++ b/canyon_migrations/src/migrations/memory.rs @@ -74,7 +74,7 @@ impl CanyonMemory { Self::create_memory(&datasource.name, db_conn, &datasource.get_db_type()).await; // Retrieve the last status data from the `canyon_memory` table - let res = Self::query("SELECT * FROM canyon_memory", [], db_conn) + let res = Self::query_rows("SELECT * FROM canyon_memory", [], db_conn) .await .expect("Error querying Canyon Memory"); @@ -262,7 +262,7 @@ impl CanyonMemory { DatabaseType::MySQL => todo!("Memory table in mysql not implemented"), }; - Self::query(query, [], db_conn) + Self::query_rows(query, [], db_conn) .await .unwrap_or_else(|_| panic!("Error creating the 'canyon_memory' table while processing the datasource: {datasource_name}")); } diff --git a/canyon_migrations/src/migrations/processor.rs b/canyon_migrations/src/migrations/processor.rs index 4a9a9a74..334d9534 100644 --- a/canyon_migrations/src/migrations/processor.rs +++ b/canyon_migrations/src/migrations/processor.rs @@ -587,7 +587,7 @@ impl MigrationsProcessor { &mut conn_cache, ); - let res = Self::query(query_to_execute, [], db_conn).await; + let res = Self::query_rows(query_to_execute, [], db_conn).await; match res { Ok(_) => println!( diff --git a/tests/crud/delete_operations.rs b/tests/crud/delete_operations.rs index e9bb61ef..ed0dc70a 100644 --- a/tests/crud/delete_operations.rs +++ b/tests/crud/delete_operations.rs @@ -1,159 +1,159 @@ -//! Integration tests for the CRUD operations available in `Canyon` that -//! generates and executes *INSERT* statements -use canyon_sql::crud::CrudOperations; - -#[cfg(feature = "mysql")] -use crate::constants::MYSQL_DS; -#[cfg(feature = "postgres")] -use crate::constants::PSQL_DS; -#[cfg(feature = "mssql")] -use crate::constants::SQL_SERVER_DS; - -use crate::tests_models::league::*; - -/// Deletes a row from the database that is mapped into some instance of a `T` entity. -/// -/// The `t.delete(&self)` operation is only enabled for types that -/// has, at least, one of it's fields annotated with a `#[primary_key]` -/// operation, because we use that concrete field to construct the clause that targets -/// that entity. -/// -/// Attempt of usage the `t.delete(&self)` method on an entity without `#[primary_key]` -/// will raise a runtime error. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_delete_method_operation() { - // For test the delete operation, we will insert a new instance of the database, and then, - // after inspect it, we will proceed to delete it - let mut new_league: League = League { - id: Default::default(), - ext_id: 7892635306594_i64, - slug: "some-new-league".to_string(), - name: "Some New League".to_string(), - region: "Bahía de cochinos".to_string(), - image_url: "https://nobodyspectsandimage.io".to_string(), - }; - - // We insert the instance on the database, on the `League` entity - new_league.insert().await.expect("Failed insert operation"); - - assert_eq!( - new_league.id, - League::find_by_pk_with(&new_league.id, PSQL_DS) - .await - .expect("Request error") - .expect("None value") - .id - ); - - // Now that we have an instance mapped to some entity by a primary key, we can now - // remove that entry from the database with the delete operation - new_league - .delete() - .await - .expect("Failed to delete the operation"); - - // To check the success, we can query by the primary key value and check if, after unwrap() - // the result of the operation, the find by primary key contains Some(v) or None - // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> - assert_eq!( - League::find_by_pk(&new_league.id) - .await - .expect("Unwrapping the result, letting the Option"), - None - ); -} - -/// Same as the delete test, but performing the operations with the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_delete_with_mssql_method_operation() { - // For test the delete, we will insert a new instance of the database, and then, - // after inspect it, we will proceed to delete it - let mut new_league: League = League { - id: Default::default(), - ext_id: 7892635306594_i64, - slug: "some-new-league".to_string(), - name: "Some New League".to_string(), - region: "Bahía de cochinos".to_string(), - image_url: "https://nobodyspectsandimage.io".to_string(), - }; - - // We insert the instance on the database, on the `League` entity - new_league - .insert_with(SQL_SERVER_DS) - .await - .expect("Failed insert operation"); - assert_eq!( - new_league.id, - League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) - .await - .expect("Request error") - .expect("None value") - .id - ); - - // Now that we have an instance mapped to some entity by a primary key, we can now - // remove that entry from the database with the delete operation - new_league - .delete_with(SQL_SERVER_DS) - .await - .expect("Failed to delete the operation"); - - // To check the success, we can query by the primary key value and check if, after unwrap() - // the result of the operation, the find by primary key contains Some(v) or None - // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> - assert_eq!( - League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) - .await - .expect("Unwrapping the result, letting the Option"), - None - ); -} - -/// Same as the delete test, but performing the operations with the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_delete_with_mysql_method_operation() { - // For test the delete, we will insert a new instance of the database, and then, - // after inspect it, we will proceed to delete it - let mut new_league: League = League { - id: Default::default(), - ext_id: 7892635306594_i64, - slug: "some-new-league".to_string(), - name: "Some New League".to_string(), - region: "Bahía de cochinos".to_string(), - image_url: "https://nobodyspectsandimage.io".to_string(), - }; - - // We insert the instance on the database, on the `League` entity - new_league - .insert_with(MYSQL_DS) - .await - .expect("Failed insert operation"); - assert_eq!( - new_league.id, - League::find_by_pk_with(&new_league.id, MYSQL_DS) - .await - .expect("Request error") - .expect("None value") - .id - ); - - // Now that we have an instance mapped to some entity by a primary key, we can now - // remove that entry from the database with the delete operation - new_league - .delete_with(MYSQL_DS) - .await - .expect("Failed to delete the operation"); - - // To check the success, we can query by the primary key value and check if, after unwrap() - // the result of the operation, the find by primary key contains Some(v) or None - // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> - assert_eq!( - League::find_by_pk_with(&new_league.id, MYSQL_DS) - .await - .expect("Unwrapping the result, letting the Option"), - None - ); -} +// //! Integration tests for the CRUD operations available in `Canyon` that +// //! generates and executes *INSERT* statements +// use canyon_sql::crud::CrudOperations; +// +// #[cfg(feature = "mysql")] +// use crate::constants::MYSQL_DS; +// #[cfg(feature = "postgres")] +// use crate::constants::PSQL_DS; +// #[cfg(feature = "mssql")] +// use crate::constants::SQL_SERVER_DS; +// +// use crate::tests_models::league::*; +// +// /// Deletes a row from the database that is mapped into some instance of a `T` entity. +// /// +// /// The `t.delete(&self)` operation is only enabled for types that +// /// has, at least, one of it's fields annotated with a `#[primary_key]` +// /// operation, because we use that concrete field to construct the clause that targets +// /// that entity. +// /// +// /// Attempt of usage the `t.delete(&self)` method on an entity without `#[primary_key]` +// /// will raise a runtime error. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_delete_method_operation() { +// // For test the delete operation, we will insert a new instance of the database, and then, +// // after inspect it, we will proceed to delete it +// let mut new_league: League = League { +// id: Default::default(), +// ext_id: 7892635306594_i64, +// slug: "some-new-league".to_string(), +// name: "Some New League".to_string(), +// region: "Bahía de cochinos".to_string(), +// image_url: "https://nobodyspectsandimage.io".to_string(), +// }; +// +// // We insert the instance on the database, on the `League` entity +// new_league.insert().await.expect("Failed insert operation"); +// +// assert_eq!( +// new_league.id, +// League::find_by_pk_with(&new_league.id, PSQL_DS) +// .await +// .expect("Request error") +// .expect("None value") +// .id +// ); +// +// // Now that we have an instance mapped to some entity by a primary key, we can now +// // remove that entry from the database with the delete operation +// new_league +// .delete() +// .await +// .expect("Failed to delete the operation"); +// +// // To check the success, we can query by the primary key value and check if, after unwrap() +// // the result of the operation, the find by primary key contains Some(v) or None +// // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> +// assert_eq!( +// League::find_by_pk(&new_league.id) +// .await +// .expect("Unwrapping the result, letting the Option"), +// None +// ); +// } +// +// /// Same as the delete test, but performing the operations with the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_delete_with_mssql_method_operation() { +// // For test the delete, we will insert a new instance of the database, and then, +// // after inspect it, we will proceed to delete it +// let mut new_league: League = League { +// id: Default::default(), +// ext_id: 7892635306594_i64, +// slug: "some-new-league".to_string(), +// name: "Some New League".to_string(), +// region: "Bahía de cochinos".to_string(), +// image_url: "https://nobodyspectsandimage.io".to_string(), +// }; +// +// // We insert the instance on the database, on the `League` entity +// new_league +// .insert_with(SQL_SERVER_DS) +// .await +// .expect("Failed insert operation"); +// assert_eq!( +// new_league.id, +// League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) +// .await +// .expect("Request error") +// .expect("None value") +// .id +// ); +// +// // Now that we have an instance mapped to some entity by a primary key, we can now +// // remove that entry from the database with the delete operation +// new_league +// .delete_with(SQL_SERVER_DS) +// .await +// .expect("Failed to delete the operation"); +// +// // To check the success, we can query by the primary key value and check if, after unwrap() +// // the result of the operation, the find by primary key contains Some(v) or None +// // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> +// assert_eq!( +// League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) +// .await +// .expect("Unwrapping the result, letting the Option"), +// None +// ); +// } +// +// /// Same as the delete test, but performing the operations with the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_delete_with_mysql_method_operation() { +// // For test the delete, we will insert a new instance of the database, and then, +// // after inspect it, we will proceed to delete it +// let mut new_league: League = League { +// id: Default::default(), +// ext_id: 7892635306594_i64, +// slug: "some-new-league".to_string(), +// name: "Some New League".to_string(), +// region: "Bahía de cochinos".to_string(), +// image_url: "https://nobodyspectsandimage.io".to_string(), +// }; +// +// // We insert the instance on the database, on the `League` entity +// new_league +// .insert_with(MYSQL_DS) +// .await +// .expect("Failed insert operation"); +// assert_eq!( +// new_league.id, +// League::find_by_pk_with(&new_league.id, MYSQL_DS) +// .await +// .expect("Request error") +// .expect("None value") +// .id +// ); +// +// // Now that we have an instance mapped to some entity by a primary key, we can now +// // remove that entry from the database with the delete operation +// new_league +// .delete_with(MYSQL_DS) +// .await +// .expect("Failed to delete the operation"); +// +// // To check the success, we can query by the primary key value and check if, after unwrap() +// // the result of the operation, the find by primary key contains Some(v) or None +// // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> +// assert_eq!( +// League::find_by_pk_with(&new_league.id, MYSQL_DS) +// .await +// .expect("Unwrapping the result, letting the Option"), +// None +// ); +// } diff --git a/tests/crud/foreign_key_operations.rs b/tests/crud/foreign_key_operations.rs index 00f153e3..142c3883 100644 --- a/tests/crud/foreign_key_operations.rs +++ b/tests/crud/foreign_key_operations.rs @@ -1,163 +1,163 @@ -/// Integration tests for the CRUD operations available in `Canyon` that -/// generates and executes *SELECT* statements based on a entity -/// annotated with the `#[foreign_key(... args)]` annotation looking -/// for the related data with some entity `U` that acts as is parent, where `U` -/// impls `ForeignKeyable` (isn't required, but it won't unlock the -/// reverse search features parent -> child, only the child -> parent ones). -/// -/// Names of the foreign key methods are autogenerated for the direct and -/// reverse side of the implementations. -/// For more info: TODO -> Link to the docs of the foreign key chapter -use canyon_sql::crud::CrudOperations; - -#[cfg(feature = "mssql")] -use crate::constants::MYSQL_DS; -#[cfg(feature = "mssql")] -use crate::constants::SQL_SERVER_DS; - -use crate::tests_models::league::*; -use crate::tests_models::tournament::*; - -/// Given an entity `T` which has some field declaring a foreign key relation -/// with some another entity `U`, for example, performs a search to find -/// what is the parent type `U` of `T` -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_search_by_foreign_key() { - let some_tournament: Tournament = Tournament::find_by_pk(&1) - .await - .expect("Result variant of the query is err") - .expect("No result found for the given parameter"); - - // We can get the parent entity for the retrieved child instance - let parent_entity: Option = some_tournament - .search_league() - .await - .expect("Result variant of the query is err"); - - if let Some(league) = parent_entity { - assert_eq!(some_tournament.league, league.id) - } else { - assert_eq!(parent_entity, None) - } -} - -/// Same as the search by foreign key, but with the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_search_by_foreign_key_with_mssql() { - let some_tournament: Tournament = Tournament::find_by_pk_with(&10, SQL_SERVER_DS) - .await - .expect("Result variant of the query is err") - .expect("No result found for the given parameter"); - - // We can get the parent entity for the retrieved child instance - let parent_entity: Option = some_tournament - .search_league_with(SQL_SERVER_DS) - .await - .expect("Result variant of the query is err"); - - // These are tests, and we could unwrap the result contained in the option, because - // it always should exist that search for the data inserted when the docker starts. - // But, just for change the style a little bit and offer more options about how to - // handle things done with Canyon - if let Some(league) = parent_entity { - assert_eq!(some_tournament.league, league.id) - } else { - assert_eq!(parent_entity, None) - } -} - -/// Same as the search by foreign key, but with the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_search_by_foreign_key_with_mysql() { - let some_tournament: Tournament = Tournament::find_by_pk_with(&10, MYSQL_DS) - .await - .expect("Result variant of the query is err") - .expect("No result found for the given parameter"); - - // We can get the parent entity for the retrieved child instance - let parent_entity: Option = some_tournament - .search_league_with(MYSQL_DS) - .await - .expect("Result variant of the query is err"); - - // These are tests, and we could unwrap the result contained in the option, because - // it always should exist that search for the data inserted when the docker starts. - // But, just for change the style a little bit and offer more options about how to - // handle things done with Canyon - if let Some(league) = parent_entity { - assert_eq!(some_tournament.league, league.id) - } else { - assert_eq!(parent_entity, None) - } -} - -/// Given an entity `U` that is know as the "parent" side of the relation with another -/// entity `T`, for example, we can ask to the parent for the childrens that belongs -/// to `U`. -/// -/// For this to work, `U`, the parent, must have derived the `ForeignKeyable` proc macro -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_search_reverse_side_foreign_key() { - let some_league: League = League::find_by_pk(&1) - .await - .expect("Result variant of the query is err") - .expect("No result found for the given parameter"); - - // Computes how many tournaments are pointing to the retrieved league - let child_tournaments: Vec = Tournament::search_league_childrens(&some_league) - .await - .expect("Result variant of the query is err"); - - assert!(!child_tournaments.is_empty()); - child_tournaments - .iter() - .for_each(|t| assert_eq!(t.league, some_league.id)); -} - -/// Same as the search by the reverse side of a foreign key relation -/// but with the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_search_reverse_side_foreign_key_with_mssql() { - let some_league: League = League::find_by_pk_with(&1, SQL_SERVER_DS) - .await - .expect("Result variant of the query is err") - .expect("No result found for the given parameter"); - - // Computes how many tournaments are pointing to the retrieved league - let child_tournaments: Vec = - Tournament::search_league_childrens_with(&some_league, SQL_SERVER_DS) - .await - .expect("Result variant of the query is err"); - - assert!(!child_tournaments.is_empty()); - child_tournaments - .iter() - .for_each(|t| assert_eq!(t.league, some_league.id)); -} - -/// Same as the search by the reverse side of a foreign key relation -/// but with the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_search_reverse_side_foreign_key_with_mysql() { - let some_league: League = League::find_by_pk_with(&1, MYSQL_DS) - .await - .expect("Result variant of the query is err") - .expect("No result found for the given parameter"); - - // Computes how many tournaments are pointing to the retrieved league - let child_tournaments: Vec = - Tournament::search_league_childrens_with(&some_league, MYSQL_DS) - .await - .expect("Result variant of the query is err"); - - assert!(!child_tournaments.is_empty()); - child_tournaments - .iter() - .for_each(|t| assert_eq!(t.league, some_league.id)); -} +// /// Integration tests for the CRUD operations available in `Canyon` that +// /// generates and executes *SELECT* statements based on a entity +// /// annotated with the `#[foreign_key(... args)]` annotation looking +// /// for the related data with some entity `U` that acts as is parent, where `U` +// /// impls `ForeignKeyable` (isn't required, but it won't unlock the +// /// reverse search features parent -> child, only the child -> parent ones). +// /// +// /// Names of the foreign key methods are autogenerated for the direct and +// /// reverse side of the implementations. +// /// For more info: TODO -> Link to the docs of the foreign key chapter +// use canyon_sql::crud::CrudOperations; +// +// #[cfg(feature = "mssql")] +// use crate::constants::MYSQL_DS; +// #[cfg(feature = "mssql")] +// use crate::constants::SQL_SERVER_DS; +// +// use crate::tests_models::league::*; +// use crate::tests_models::tournament::*; +// +// /// Given an entity `T` which has some field declaring a foreign key relation +// /// with some another entity `U`, for example, performs a search to find +// /// what is the parent type `U` of `T` +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_search_by_foreign_key() { +// let some_tournament: Tournament = Tournament::find_by_pk(&1) +// .await +// .expect("Result variant of the query is err") +// .expect("No result found for the given parameter"); +// +// // We can get the parent entity for the retrieved child instance +// let parent_entity: Option = some_tournament +// .search_league() +// .await +// .expect("Result variant of the query is err"); +// +// if let Some(league) = parent_entity { +// assert_eq!(some_tournament.league, league.id) +// } else { +// assert_eq!(parent_entity, None) +// } +// } +// +// /// Same as the search by foreign key, but with the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_search_by_foreign_key_with_mssql() { +// let some_tournament: Tournament = Tournament::find_by_pk_with(&10, SQL_SERVER_DS) +// .await +// .expect("Result variant of the query is err") +// .expect("No result found for the given parameter"); +// +// // We can get the parent entity for the retrieved child instance +// let parent_entity: Option = some_tournament +// .search_league_with(SQL_SERVER_DS) +// .await +// .expect("Result variant of the query is err"); +// +// // These are tests, and we could unwrap the result contained in the option, because +// // it always should exist that search for the data inserted when the docker starts. +// // But, just for change the style a little bit and offer more options about how to +// // handle things done with Canyon +// if let Some(league) = parent_entity { +// assert_eq!(some_tournament.league, league.id) +// } else { +// assert_eq!(parent_entity, None) +// } +// } +// +// /// Same as the search by foreign key, but with the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_search_by_foreign_key_with_mysql() { +// let some_tournament: Tournament = Tournament::find_by_pk_with(&10, MYSQL_DS) +// .await +// .expect("Result variant of the query is err") +// .expect("No result found for the given parameter"); +// +// // We can get the parent entity for the retrieved child instance +// let parent_entity: Option = some_tournament +// .search_league_with(MYSQL_DS) +// .await +// .expect("Result variant of the query is err"); +// +// // These are tests, and we could unwrap the result contained in the option, because +// // it always should exist that search for the data inserted when the docker starts. +// // But, just for change the style a little bit and offer more options about how to +// // handle things done with Canyon +// if let Some(league) = parent_entity { +// assert_eq!(some_tournament.league, league.id) +// } else { +// assert_eq!(parent_entity, None) +// } +// } +// +// /// Given an entity `U` that is know as the "parent" side of the relation with another +// /// entity `T`, for example, we can ask to the parent for the childrens that belongs +// /// to `U`. +// /// +// /// For this to work, `U`, the parent, must have derived the `ForeignKeyable` proc macro +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_search_reverse_side_foreign_key() { +// let some_league: League = League::find_by_pk(&1) +// .await +// .expect("Result variant of the query is err") +// .expect("No result found for the given parameter"); +// +// // Computes how many tournaments are pointing to the retrieved league +// let child_tournaments: Vec = Tournament::search_league_childrens(&some_league) +// .await +// .expect("Result variant of the query is err"); +// +// assert!(!child_tournaments.is_empty()); +// child_tournaments +// .iter() +// .for_each(|t| assert_eq!(t.league, some_league.id)); +// } +// +// /// Same as the search by the reverse side of a foreign key relation +// /// but with the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_search_reverse_side_foreign_key_with_mssql() { +// let some_league: League = League::find_by_pk_with(&1, SQL_SERVER_DS) +// .await +// .expect("Result variant of the query is err") +// .expect("No result found for the given parameter"); +// +// // Computes how many tournaments are pointing to the retrieved league +// let child_tournaments: Vec = +// Tournament::search_league_childrens_with(&some_league, SQL_SERVER_DS) +// .await +// .expect("Result variant of the query is err"); +// +// assert!(!child_tournaments.is_empty()); +// child_tournaments +// .iter() +// .for_each(|t| assert_eq!(t.league, some_league.id)); +// } +// +// /// Same as the search by the reverse side of a foreign key relation +// /// but with the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_search_reverse_side_foreign_key_with_mysql() { +// let some_league: League = League::find_by_pk_with(&1, MYSQL_DS) +// .await +// .expect("Result variant of the query is err") +// .expect("No result found for the given parameter"); +// +// // Computes how many tournaments are pointing to the retrieved league +// let child_tournaments: Vec = +// Tournament::search_league_childrens_with(&some_league, MYSQL_DS) +// .await +// .expect("Result variant of the query is err"); +// +// assert!(!child_tournaments.is_empty()); +// child_tournaments +// .iter() +// .for_each(|t| assert_eq!(t.league, some_league.id)); +// } diff --git a/tests/crud/init_mssql.rs b/tests/crud/init_mssql.rs index 2f4f43a9..db8ff69f 100644 --- a/tests/crud/init_mssql.rs +++ b/tests/crud/init_mssql.rs @@ -1,62 +1,62 @@ -use crate::constants::SQL_SERVER_CREATE_TABLES; -use crate::constants::SQL_SERVER_DS; -use crate::constants::SQL_SERVER_FILL_TABLE_VALUES; -use crate::tests_models::league::League; - -use canyon_sql::crud::CrudOperations; -use canyon_sql::db_clients::tiberius::{Client, Config}; -use canyon_sql::runtime::tokio::net::TcpStream; -use canyon_sql::runtime::tokio_util::compat::TokioAsyncWriteCompatExt; - -/// In order to initialize data on `SqlServer`. we must manually insert it -/// when the docker starts. SqlServer official docker from Microsoft does -/// not allow you to run `.sql` files against the database (not at least, without) -/// using a workaround. So, we are going to query the `SqlServer` to check if already -/// has some data (other processes, persistence or multi-threading envs), af if not, -/// we are going to retrieve the inserted data on the `postgreSQL` at start-up and -/// inserting into the `SqlServer` instance. -/// -/// This will be marked as `#[ignore]`, so we can force to run first the marked as -/// ignored, check the data available, perform the necessary init operations and -/// then *cargo test * the real integration tests -#[canyon_sql::macros::canyon_tokio_test] -#[ignore] -fn initialize_sql_server_docker_instance() { - static CONN_STR: &str = // TODO: change this for the DS when will be in the public API - "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true"; - - canyon_sql::runtime::futures::executor::block_on(async { - let config = Config::from_ado_string(CONN_STR).unwrap(); - - let tcp = TcpStream::connect(config.get_addr()).await.unwrap(); - let tcp2 = TcpStream::connect(config.get_addr()).await.unwrap(); - tcp.set_nodelay(true).ok(); - - let mut client = Client::connect(config.clone(), tcp.compat_write()) - .await - .unwrap(); - - // Create the tables - let query_result = client.query(SQL_SERVER_CREATE_TABLES, &[]).await; - assert!(query_result.is_ok()); - - let leagues_sql = League::find_all_with(SQL_SERVER_DS).await; - println!("LSQL ERR: {leagues_sql:?}"); - assert!(leagues_sql.is_ok()); - - match leagues_sql { - Ok(ref leagues) => { - let leagues_len = leagues.len(); - println!("Leagues already inserted on SQLSERVER: {:?}", &leagues_len); - if leagues.len() < 10 { - let mut client2 = Client::connect(config, tcp2.compat_write()) - .await - .expect("Can't connect to MSSQL"); - let result = client2.query(SQL_SERVER_FILL_TABLE_VALUES, &[]).await; - assert!(result.is_ok()); - } - } - Err(e) => eprintln!("Error retrieving the leagues: {e}"), - } - }); -} +// use crate::constants::SQL_SERVER_CREATE_TABLES; +// use crate::constants::SQL_SERVER_DS; +// use crate::constants::SQL_SERVER_FILL_TABLE_VALUES; +// use crate::tests_models::league::League; +// +// use canyon_sql::crud::CrudOperations; +// use canyon_sql::db_clients::tiberius::{Client, Config}; +// use canyon_sql::runtime::tokio::net::TcpStream; +// use canyon_sql::runtime::tokio_util::compat::TokioAsyncWriteCompatExt; +// +// /// In order to initialize data on `SqlServer`. we must manually insert it +// /// when the docker starts. SqlServer official docker from Microsoft does +// /// not allow you to run `.sql` files against the database (not at least, without) +// /// using a workaround. So, we are going to query the `SqlServer` to check if already +// /// has some data (other processes, persistence or multi-threading envs), af if not, +// /// we are going to retrieve the inserted data on the `postgreSQL` at start-up and +// /// inserting into the `SqlServer` instance. +// /// +// /// This will be marked as `#[ignore]`, so we can force to run first the marked as +// /// ignored, check the data available, perform the necessary init operations and +// /// then *cargo test * the real integration tests +// #[canyon_sql::macros::canyon_tokio_test] +// #[ignore] +// fn initialize_sql_server_docker_instance() { +// static CONN_STR: &str = // TODO: change this for the DS when will be in the public API +// "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true"; +// +// canyon_sql::runtime::futures::executor::block_on(async { +// let config = Config::from_ado_string(CONN_STR).unwrap(); +// +// let tcp = TcpStream::connect(config.get_addr()).await.unwrap(); +// let tcp2 = TcpStream::connect(config.get_addr()).await.unwrap(); +// tcp.set_nodelay(true).ok(); +// +// let mut client = Client::connect(config.clone(), tcp.compat_write()) +// .await +// .unwrap(); +// +// // Create the tables +// let query_result = client.query(SQL_SERVER_CREATE_TABLES, &[]).await; +// assert!(query_result.is_ok()); +// +// let leagues_sql = League::find_all_with(SQL_SERVER_DS).await; +// println!("LSQL ERR: {leagues_sql:?}"); +// assert!(leagues_sql.is_ok()); +// +// match leagues_sql { +// Ok(ref leagues) => { +// let leagues_len = leagues.len(); +// println!("Leagues already inserted on SQLSERVER: {:?}", &leagues_len); +// if leagues.len() < 10 { +// let mut client2 = Client::connect(config, tcp2.compat_write()) +// .await +// .expect("Can't connect to MSSQL"); +// let result = client2.query(SQL_SERVER_FILL_TABLE_VALUES, &[]).await; +// assert!(result.is_ok()); +// } +// } +// Err(e) => eprintln!("Error retrieving the leagues: {e}"), +// } +// }); +// } diff --git a/tests/crud/insert_operations.rs b/tests/crud/insert_operations.rs index 4fb742ca..9f92e118 100644 --- a/tests/crud/insert_operations.rs +++ b/tests/crud/insert_operations.rs @@ -1,317 +1,317 @@ -//! Integration tests for the CRUD operations available in `Canyon` that -//! generates and executes *INSERT* statements -use canyon_sql::crud::CrudOperations; - -#[cfg(feature = "mysql")] -use crate::constants::MYSQL_DS; -#[cfg(feature = "mssql")] -use crate::constants::SQL_SERVER_DS; - -use crate::tests_models::league::*; - -/// Inserts a new record on the database, given an entity that is -/// annotated with `#[canyon_entity]` macro over a *T* type. -/// -/// For insert a new record on a database, the *insert* operation needs -/// some special requirements: -/// > - We need a mutable instance of `T`. If the operation completes -/// successfully, the insert operation will automatically set the autogenerated -/// value for the `primary_key` annotated field in it. -/// -/// > - It's considered a good practice to initialize that concrete field with -/// the `Default` trait, because the value on the primary key field will be -/// ignored at the execution time of the insert, and updated with the autogenerated -/// value by the database. -/// -/// By default, the `#[primary_key]` annotation means autogenerated and autoincremental. -/// You can configure not autoincremental via macro annotation parameters (please, -/// refer to the docs [here]() for more info.) -/// -/// If the type hasn't a `#[primary_key]` annotation, or the annotation contains -/// an argument specifying not autoincremental behaviour, all the fields will be -/// inserted on the database and no returning value will be placed in any field. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_insert_operation() { - let mut new_league: League = League { - id: Default::default(), - ext_id: 7892635306594_i64, - slug: "some-new-league".to_string(), - name: "Some New League".to_string(), - region: "Bahía de cochinos".to_string(), - image_url: "https://nobodyspectsandimage.io".to_string(), - }; - - // We insert the instance on the database, on the `League` entity - new_league.insert().await.expect("Failed insert operation"); - - // Now, in the `id` field of the instance, we have the autogenerated - // value for the primary key field, which is id. So, we can query the - // database again with the find by primary key operation to check if - // the value was really inserted - let inserted_league = League::find_by_pk(&new_league.id) - .await - .expect("Failed the query to the database") - .expect("No entity found for the primary key value passed in"); - - assert_eq!(new_league.id, inserted_league.id); -} - -/// Same as the insert operation above, but targeting the database defined in -/// the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_insert_with_mssql_operation() { - let mut new_league: League = League { - id: Default::default(), - ext_id: 7892635306594_i64, - slug: "some-new-league".to_string(), - name: "Some New League".to_string(), - region: "Bahía de cochinos".to_string(), - image_url: "https://nobodyspectsandimage.io".to_string(), - }; - - // We insert the instance on the database, on the `League` entity - new_league - .insert_with(SQL_SERVER_DS) - .await - .expect("Failed insert datasource operation"); - - // Now, in the `id` field of the instance, we have the autogenerated - // value for the primary key field, which is id. So, we can query the - // database again with the find by primary key operation to check if - // the value was really inserted - let inserted_league = League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) - .await - .expect("Failed the query to the database") - .expect("No entity found for the primary key value passed in"); - - assert_eq!(new_league.id, inserted_league.id); -} - -/// Same as the insert operation above, but targeting the database defined in -/// the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_insert_with_mysql_operation() { - let mut new_league: League = League { - id: Default::default(), - ext_id: 7892635306594_i64, - slug: "some-new-league".to_string(), - name: "Some New League".to_string(), - region: "Bahía de cochinos".to_string(), - image_url: "https://nobodyspectsandimage.io".to_string(), - }; - - // We insert the instance on the database, on the `League` entity - new_league - .insert_with(MYSQL_DS) - .await - .expect("Failed insert datasource operation"); - - // Now, in the `id` field of the instance, we have the autogenerated - // value for the primary key field, which is id. So, we can query the - // database again with the find by primary key operation to check if - // the value was really inserted - let inserted_league = League::find_by_pk_with(&new_league.id, MYSQL_DS) - .await - .expect("Failed the query to the database") - .expect("No entity found for the primary key value passed in"); - - assert_eq!(new_league.id, inserted_league.id); -} - -/// The multi insert operation is a shorthand for insert multiple instances of *T* -/// in the database at once. -/// -/// It works pretty much the same that the insert operation, with the same behaviour -/// of the `#[primary_key]` annotation over some field. It will auto set the primary -/// key field with the autogenerated value on the database on the insert operation, but -/// for every entity passed in as an array of mutable instances of `T`. -/// -/// The instances without `#[primary_key]` inserts all the values on the instaqce fields -/// on the database. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_multi_insert_operation() { - let mut new_league_mi: League = League { - id: Default::default(), - ext_id: 54376478_i64, - slug: "some-new-random-league".to_string(), - name: "Some New Random League".to_string(), - region: "Unknown".to_string(), - image_url: "https://what-a-league.io".to_string(), - }; - let mut new_league_mi_2: League = League { - id: Default::default(), - ext_id: 3475689769678906_i64, - slug: "new-league-2".to_string(), - name: "New League 2".to_string(), - region: "Really unknown".to_string(), - image_url: "https://what-an-unknown-league.io".to_string(), - }; - let mut new_league_mi_3: League = League { - id: Default::default(), - ext_id: 46756867_i64, - slug: "a-new-multinsert".to_string(), - name: "New League 3".to_string(), - region: "The dark side of the moon".to_string(), - image_url: "https://interplanetary-league.io".to_string(), - }; - - // Insert the instance as database entities - new_league_mi - .insert() - .await - .expect("Failed insert datasource operation"); - new_league_mi_2 - .insert() - .await - .expect("Failed insert datasource operation"); - new_league_mi_3 - .insert() - .await - .expect("Failed insert datasource operation"); - - // Recover the inserted data by primary key - let inserted_league = League::find_by_pk(&new_league_mi.id) - .await - .expect("[1] - Failed the query to the database") - .expect("[1] - No entity found for the primary key value passed in"); - let inserted_league_2 = League::find_by_pk(&new_league_mi_2.id) - .await - .expect("[2] - Failed the query to the database") - .expect("[2] - No entity found for the primary key value passed in"); - let inserted_league_3 = League::find_by_pk(&new_league_mi_3.id) - .await - .expect("[3] - Failed the query to the database") - .expect("[3] - No entity found for the primary key value passed in"); - - assert_eq!(new_league_mi.id, inserted_league.id); - assert_eq!(new_league_mi_2.id, inserted_league_2.id); - assert_eq!(new_league_mi_3.id, inserted_league_3.id); -} - -/// Same as the multi insert above, but with the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_multi_insert_with_mssql_operation() { - let mut new_league_mi: League = League { - id: Default::default(), - ext_id: 54376478_i64, - slug: "some-new-random-league".to_string(), - name: "Some New Random League".to_string(), - region: "Unknown".to_string(), - image_url: "https://what-a-league.io".to_string(), - }; - let mut new_league_mi_2: League = League { - id: Default::default(), - ext_id: 3475689769678906_i64, - slug: "new-league-2".to_string(), - name: "New League 2".to_string(), - region: "Really unknown".to_string(), - image_url: "https://what-an-unknown-league.io".to_string(), - }; - let mut new_league_mi_3: League = League { - id: Default::default(), - ext_id: 46756867_i64, - slug: "a-new-multinsert".to_string(), - name: "New League 3".to_string(), - region: "The dark side of the moon".to_string(), - image_url: "https://interplanetary-league.io".to_string(), - }; - - // Insert the instance as database entities - new_league_mi - .insert_with(SQL_SERVER_DS) - .await - .expect("Failed insert datasource operation"); - new_league_mi_2 - .insert_with(SQL_SERVER_DS) - .await - .expect("Failed insert datasource operation"); - new_league_mi_3 - .insert_with(SQL_SERVER_DS) - .await - .expect("Failed insert datasource operation"); - - // Recover the inserted data by primary key - let inserted_league = League::find_by_pk_with(&new_league_mi.id, SQL_SERVER_DS) - .await - .expect("[1] - Failed the query to the database") - .expect("[1] - No entity found for the primary key value passed in"); - let inserted_league_2 = League::find_by_pk_with(&new_league_mi_2.id, SQL_SERVER_DS) - .await - .expect("[2] - Failed the query to the database") - .expect("[2] - No entity found for the primary key value passed in"); - let inserted_league_3 = League::find_by_pk_with(&new_league_mi_3.id, SQL_SERVER_DS) - .await - .expect("[3] - Failed the query to the database") - .expect("[3] - No entity found for the primary key value passed in"); - - assert_eq!(new_league_mi.id, inserted_league.id); - assert_eq!(new_league_mi_2.id, inserted_league_2.id); - assert_eq!(new_league_mi_3.id, inserted_league_3.id); -} - -/// Same as the multi insert above, but with the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_multi_insert_with_mysql_operation() { - let mut new_league_mi: League = League { - id: Default::default(), - ext_id: 54376478_i64, - slug: "some-new-random-league".to_string(), - name: "Some New Random League".to_string(), - region: "Unknown".to_string(), - image_url: "https://what-a-league.io".to_string(), - }; - let mut new_league_mi_2: League = League { - id: Default::default(), - ext_id: 3475689769678906_i64, - slug: "new-league-2".to_string(), - name: "New League 2".to_string(), - region: "Really unknown".to_string(), - image_url: "https://what-an-unknown-league.io".to_string(), - }; - let mut new_league_mi_3: League = League { - id: Default::default(), - ext_id: 46756867_i64, - slug: "a-new-multinsert".to_string(), - name: "New League 3".to_string(), - region: "The dark side of the moon".to_string(), - image_url: "https://interplanetary-league.io".to_string(), - }; - - // Insert the instance as database entities - new_league_mi - .insert_with(MYSQL_DS) - .await - .expect("Failed insert datasource operation"); - new_league_mi_2 - .insert_with(MYSQL_DS) - .await - .expect("Failed insert datasource operation"); - new_league_mi_3 - .insert_with(MYSQL_DS) - .await - .expect("Failed insert datasource operation"); - - // Recover the inserted data by primary key - let inserted_league = League::find_by_pk_with(&new_league_mi.id, MYSQL_DS) - .await - .expect("[1] - Failed the query to the database") - .expect("[1] - No entity found for the primary key value passed in"); - let inserted_league_2 = League::find_by_pk_with(&new_league_mi_2.id, MYSQL_DS) - .await - .expect("[2] - Failed the query to the database") - .expect("[2] - No entity found for the primary key value passed in"); - let inserted_league_3 = League::find_by_pk_with(&new_league_mi_3.id, MYSQL_DS) - .await - .expect("[3] - Failed the query to the database") - .expect("[3] - No entity found for the primary key value passed in"); - - assert_eq!(new_league_mi.id, inserted_league.id); - assert_eq!(new_league_mi_2.id, inserted_league_2.id); - assert_eq!(new_league_mi_3.id, inserted_league_3.id); -} +// //! Integration tests for the CRUD operations available in `Canyon` that +// //! generates and executes *INSERT* statements +// use canyon_sql::crud::CrudOperations; +// +// #[cfg(feature = "mysql")] +// use crate::constants::MYSQL_DS; +// #[cfg(feature = "mssql")] +// use crate::constants::SQL_SERVER_DS; +// +// use crate::tests_models::league::*; +// +// /// Inserts a new record on the database, given an entity that is +// /// annotated with `#[canyon_entity]` macro over a *T* type. +// /// +// /// For insert a new record on a database, the *insert* operation needs +// /// some special requirements: +// /// > - We need a mutable instance of `T`. If the operation completes +// /// successfully, the insert operation will automatically set the autogenerated +// /// value for the `primary_key` annotated field in it. +// /// +// /// > - It's considered a good practice to initialize that concrete field with +// /// the `Default` trait, because the value on the primary key field will be +// /// ignored at the execution time of the insert, and updated with the autogenerated +// /// value by the database. +// /// +// /// By default, the `#[primary_key]` annotation means autogenerated and autoincremental. +// /// You can configure not autoincremental via macro annotation parameters (please, +// /// refer to the docs [here]() for more info.) +// /// +// /// If the type hasn't a `#[primary_key]` annotation, or the annotation contains +// /// an argument specifying not autoincremental behaviour, all the fields will be +// /// inserted on the database and no returning value will be placed in any field. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_insert_operation() { +// let mut new_league: League = League { +// id: Default::default(), +// ext_id: 7892635306594_i64, +// slug: "some-new-league".to_string(), +// name: "Some New League".to_string(), +// region: "Bahía de cochinos".to_string(), +// image_url: "https://nobodyspectsandimage.io".to_string(), +// }; +// +// // We insert the instance on the database, on the `League` entity +// new_league.insert().await.expect("Failed insert operation"); +// +// // Now, in the `id` field of the instance, we have the autogenerated +// // value for the primary key field, which is id. So, we can query the +// // database again with the find by primary key operation to check if +// // the value was really inserted +// let inserted_league = League::find_by_pk(&new_league.id) +// .await +// .expect("Failed the query to the database") +// .expect("No entity found for the primary key value passed in"); +// +// assert_eq!(new_league.id, inserted_league.id); +// } +// +// /// Same as the insert operation above, but targeting the database defined in +// /// the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_insert_with_mssql_operation() { +// let mut new_league: League = League { +// id: Default::default(), +// ext_id: 7892635306594_i64, +// slug: "some-new-league".to_string(), +// name: "Some New League".to_string(), +// region: "Bahía de cochinos".to_string(), +// image_url: "https://nobodyspectsandimage.io".to_string(), +// }; +// +// // We insert the instance on the database, on the `League` entity +// new_league +// .insert_with(SQL_SERVER_DS) +// .await +// .expect("Failed insert datasource operation"); +// +// // Now, in the `id` field of the instance, we have the autogenerated +// // value for the primary key field, which is id. So, we can query the +// // database again with the find by primary key operation to check if +// // the value was really inserted +// let inserted_league = League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) +// .await +// .expect("Failed the query to the database") +// .expect("No entity found for the primary key value passed in"); +// +// assert_eq!(new_league.id, inserted_league.id); +// } +// +// /// Same as the insert operation above, but targeting the database defined in +// /// the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_insert_with_mysql_operation() { +// let mut new_league: League = League { +// id: Default::default(), +// ext_id: 7892635306594_i64, +// slug: "some-new-league".to_string(), +// name: "Some New League".to_string(), +// region: "Bahía de cochinos".to_string(), +// image_url: "https://nobodyspectsandimage.io".to_string(), +// }; +// +// // We insert the instance on the database, on the `League` entity +// new_league +// .insert_with(MYSQL_DS) +// .await +// .expect("Failed insert datasource operation"); +// +// // Now, in the `id` field of the instance, we have the autogenerated +// // value for the primary key field, which is id. So, we can query the +// // database again with the find by primary key operation to check if +// // the value was really inserted +// let inserted_league = League::find_by_pk_with(&new_league.id, MYSQL_DS) +// .await +// .expect("Failed the query to the database") +// .expect("No entity found for the primary key value passed in"); +// +// assert_eq!(new_league.id, inserted_league.id); +// } +// +// /// The multi insert operation is a shorthand for insert multiple instances of *T* +// /// in the database at once. +// /// +// /// It works pretty much the same that the insert operation, with the same behaviour +// /// of the `#[primary_key]` annotation over some field. It will auto set the primary +// /// key field with the autogenerated value on the database on the insert operation, but +// /// for every entity passed in as an array of mutable instances of `T`. +// /// +// /// The instances without `#[primary_key]` inserts all the values on the instaqce fields +// /// on the database. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_multi_insert_operation() { +// let mut new_league_mi: League = League { +// id: Default::default(), +// ext_id: 54376478_i64, +// slug: "some-new-random-league".to_string(), +// name: "Some New Random League".to_string(), +// region: "Unknown".to_string(), +// image_url: "https://what-a-league.io".to_string(), +// }; +// let mut new_league_mi_2: League = League { +// id: Default::default(), +// ext_id: 3475689769678906_i64, +// slug: "new-league-2".to_string(), +// name: "New League 2".to_string(), +// region: "Really unknown".to_string(), +// image_url: "https://what-an-unknown-league.io".to_string(), +// }; +// let mut new_league_mi_3: League = League { +// id: Default::default(), +// ext_id: 46756867_i64, +// slug: "a-new-multinsert".to_string(), +// name: "New League 3".to_string(), +// region: "The dark side of the moon".to_string(), +// image_url: "https://interplanetary-league.io".to_string(), +// }; +// +// // Insert the instance as database entities +// new_league_mi +// .insert() +// .await +// .expect("Failed insert datasource operation"); +// new_league_mi_2 +// .insert() +// .await +// .expect("Failed insert datasource operation"); +// new_league_mi_3 +// .insert() +// .await +// .expect("Failed insert datasource operation"); +// +// // Recover the inserted data by primary key +// let inserted_league = League::find_by_pk(&new_league_mi.id) +// .await +// .expect("[1] - Failed the query to the database") +// .expect("[1] - No entity found for the primary key value passed in"); +// let inserted_league_2 = League::find_by_pk(&new_league_mi_2.id) +// .await +// .expect("[2] - Failed the query to the database") +// .expect("[2] - No entity found for the primary key value passed in"); +// let inserted_league_3 = League::find_by_pk(&new_league_mi_3.id) +// .await +// .expect("[3] - Failed the query to the database") +// .expect("[3] - No entity found for the primary key value passed in"); +// +// assert_eq!(new_league_mi.id, inserted_league.id); +// assert_eq!(new_league_mi_2.id, inserted_league_2.id); +// assert_eq!(new_league_mi_3.id, inserted_league_3.id); +// } +// +// /// Same as the multi insert above, but with the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_multi_insert_with_mssql_operation() { +// let mut new_league_mi: League = League { +// id: Default::default(), +// ext_id: 54376478_i64, +// slug: "some-new-random-league".to_string(), +// name: "Some New Random League".to_string(), +// region: "Unknown".to_string(), +// image_url: "https://what-a-league.io".to_string(), +// }; +// let mut new_league_mi_2: League = League { +// id: Default::default(), +// ext_id: 3475689769678906_i64, +// slug: "new-league-2".to_string(), +// name: "New League 2".to_string(), +// region: "Really unknown".to_string(), +// image_url: "https://what-an-unknown-league.io".to_string(), +// }; +// let mut new_league_mi_3: League = League { +// id: Default::default(), +// ext_id: 46756867_i64, +// slug: "a-new-multinsert".to_string(), +// name: "New League 3".to_string(), +// region: "The dark side of the moon".to_string(), +// image_url: "https://interplanetary-league.io".to_string(), +// }; +// +// // Insert the instance as database entities +// new_league_mi +// .insert_with(SQL_SERVER_DS) +// .await +// .expect("Failed insert datasource operation"); +// new_league_mi_2 +// .insert_with(SQL_SERVER_DS) +// .await +// .expect("Failed insert datasource operation"); +// new_league_mi_3 +// .insert_with(SQL_SERVER_DS) +// .await +// .expect("Failed insert datasource operation"); +// +// // Recover the inserted data by primary key +// let inserted_league = League::find_by_pk_with(&new_league_mi.id, SQL_SERVER_DS) +// .await +// .expect("[1] - Failed the query to the database") +// .expect("[1] - No entity found for the primary key value passed in"); +// let inserted_league_2 = League::find_by_pk_with(&new_league_mi_2.id, SQL_SERVER_DS) +// .await +// .expect("[2] - Failed the query to the database") +// .expect("[2] - No entity found for the primary key value passed in"); +// let inserted_league_3 = League::find_by_pk_with(&new_league_mi_3.id, SQL_SERVER_DS) +// .await +// .expect("[3] - Failed the query to the database") +// .expect("[3] - No entity found for the primary key value passed in"); +// +// assert_eq!(new_league_mi.id, inserted_league.id); +// assert_eq!(new_league_mi_2.id, inserted_league_2.id); +// assert_eq!(new_league_mi_3.id, inserted_league_3.id); +// } +// +// /// Same as the multi insert above, but with the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_multi_insert_with_mysql_operation() { +// let mut new_league_mi: League = League { +// id: Default::default(), +// ext_id: 54376478_i64, +// slug: "some-new-random-league".to_string(), +// name: "Some New Random League".to_string(), +// region: "Unknown".to_string(), +// image_url: "https://what-a-league.io".to_string(), +// }; +// let mut new_league_mi_2: League = League { +// id: Default::default(), +// ext_id: 3475689769678906_i64, +// slug: "new-league-2".to_string(), +// name: "New League 2".to_string(), +// region: "Really unknown".to_string(), +// image_url: "https://what-an-unknown-league.io".to_string(), +// }; +// let mut new_league_mi_3: League = League { +// id: Default::default(), +// ext_id: 46756867_i64, +// slug: "a-new-multinsert".to_string(), +// name: "New League 3".to_string(), +// region: "The dark side of the moon".to_string(), +// image_url: "https://interplanetary-league.io".to_string(), +// }; +// +// // Insert the instance as database entities +// new_league_mi +// .insert_with(MYSQL_DS) +// .await +// .expect("Failed insert datasource operation"); +// new_league_mi_2 +// .insert_with(MYSQL_DS) +// .await +// .expect("Failed insert datasource operation"); +// new_league_mi_3 +// .insert_with(MYSQL_DS) +// .await +// .expect("Failed insert datasource operation"); +// +// // Recover the inserted data by primary key +// let inserted_league = League::find_by_pk_with(&new_league_mi.id, MYSQL_DS) +// .await +// .expect("[1] - Failed the query to the database") +// .expect("[1] - No entity found for the primary key value passed in"); +// let inserted_league_2 = League::find_by_pk_with(&new_league_mi_2.id, MYSQL_DS) +// .await +// .expect("[2] - Failed the query to the database") +// .expect("[2] - No entity found for the primary key value passed in"); +// let inserted_league_3 = League::find_by_pk_with(&new_league_mi_3.id, MYSQL_DS) +// .await +// .expect("[3] - Failed the query to the database") +// .expect("[3] - No entity found for the primary key value passed in"); +// +// assert_eq!(new_league_mi.id, inserted_league.id); +// assert_eq!(new_league_mi_2.id, inserted_league_2.id); +// assert_eq!(new_league_mi_3.id, inserted_league_3.id); +// } diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index 5ef2bda6..7aa9312d 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -1,454 +1,454 @@ -#[cfg(feature = "mysql")] -use crate::constants::MYSQL_DS; -#[cfg(feature = "mssql")] -use crate::constants::SQL_SERVER_DS; - -/// Tests for the QueryBuilder available operations within Canyon. -/// -/// QueryBuilder are the way of obtain more flexibility that with -/// the default generated queries, essentially for build the queries -/// with the SQL filters -/// -use canyon_sql::{ - crud::CrudOperations, - query::{operators::Comp, operators::Like, ops::QueryBuilder}, -}; - -use crate::tests_models::league::*; -use crate::tests_models::player::*; -use crate::tests_models::tournament::*; - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[canyon_sql::macros::canyon_tokio_test] -fn test_generated_sql_by_the_select_querybuilder() { - let select_with_joins = League::select_query() - .inner_join("tournament", "league.id", "tournament.league_id") - .left_join("team", "tournament.id", "player.tournament_id") - .r#where(LeagueFieldValue::id(&7), Comp::Gt) - .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) - .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); - // .query() - // .await; - // NOTE: We don't have in the docker the generated relationships - // with the joins, so for now, we are just going to check that the - // generated SQL by the SelectQueryBuilder is the expected - assert_eq!( - select_with_joins.read_sql(), - "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder() { - // Find all the leagues with ID less or equals that 7 - // and where it's region column value is equals to 'Korea' - let filtered_leagues_result: Result, _> = League::select_query() - .r#where(LeagueFieldValue::id(&50), Comp::LtEq) - .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) - .query() - .await; - - let filtered_leagues: Vec = filtered_leagues_result.unwrap(); - assert!(!filtered_leagues.is_empty()); - - let league_idx_0 = filtered_leagues.first().unwrap(); - assert_eq!(league_idx_0.id, 34); - assert_eq!(league_idx_0.region, "KOREA"); -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_fulllike() { - // Find all the leagues with "LC" in their name - let filtered_leagues_result = - League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Full); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_fulllike_with_mssql() { - // Find all the leagues with "LC" in their name - let filtered_leagues_result = - League::select_query_with(SQL_SERVER_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Full); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_fulllike_with_mysql() { - // Find all the leagues with "LC" in their name - let filtered_leagues_result = - League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Full); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR) ,'%')" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_leftlike() { - // Find all the leagues whose name ends with "CK" - let filtered_leagues_result = - League::select_query().r#where(LeagueFieldValue::name(&"CK"), Like::Left); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_leftlike_with_mssql() { - // Find all the leagues whose name ends with "CK" - let filtered_leagues_result = - League::select_query().r#where(LeagueFieldValue::name(&"CK"), Like::Left); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_leftlike_with_mysql() { - // Find all the leagues whose name ends with "CK" - let filtered_leagues_result = - League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"CK"), Like::Left); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR))" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_rightlike() { - // Find all the leagues whose name starts with "LC" - let filtered_leagues_result = - League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Right); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_rightlike_with_mssql() { - // Find all the leagues whose name starts with "LC" - let filtered_leagues_result = League::select_query_with(SQL_SERVER_DS) - .r#where(LeagueFieldValue::name(&"LC"), Like::Right); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_rightlike_with_mysql() { - // Find all the leagues whose name starts with "LC" - let filtered_leagues_result = - League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Right); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS CHAR) ,'%')" - ) -} - -/// Same than the above but with the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_with_mssql() { - // Find all the players where its ID column value is greater than 50 - let filtered_find_players = Player::select_query_with(SQL_SERVER_DS) - .r#where(PlayerFieldValue::id(&50), Comp::Gt) - .query() - .await; - - assert!(!filtered_find_players.unwrap().is_empty()); -} - -/// Same than the above but with the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_with_mysql() { - // Find all the players where its ID column value is greater than 50 - let filtered_find_players = Player::select_query_with(MYSQL_DS) - .r#where(PlayerFieldValue::id(&50), Comp::Gt) - .query() - .await; - - assert!(!filtered_find_players.unwrap().is_empty()); -} - -/// Updates the values of the range on entries defined by the constraint parameters -/// in the database entity -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_update_with_querybuilder() { - // Find all the leagues with ID less or equals that 7 - // and where it's region column value is equals to 'Korea' - let q = League::update_query() - .set(&[ - (LeagueField::slug, "Updated with the QueryBuilder"), - (LeagueField::name, "Random"), - ]) - .r#where(LeagueFieldValue::id(&1), Comp::Gt) - .and(LeagueFieldValue::id(&8), Comp::Lt); - - /* NOTE: Family of QueryBuilders are clone, useful in case of need to read the generated SQL - let qpr = q.clone(); - println!("PSQL: {:?}", qpr.read_sql()); - */ - q.query() - .await - .expect("Failed to update records with the querybuilder"); - - let found_updated_values = League::select_query() - .r#where(LeagueFieldValue::id(&1), Comp::Gt) - .and(LeagueFieldValue::id(&7), Comp::Lt) - .query() - .await - .expect("Failed to retrieve database League entries with the querybuilder"); - - found_updated_values - .iter() - .for_each(|league| assert_eq!(league.slug, "Updated with the QueryBuilder")); -} - -/// Same as above, but with the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_update_with_querybuilder_with_mssql() { - // Find all the leagues with ID less or equals that 7 - // and where it's region column value is equals to 'Korea' - let q = Player::update_query_with(SQL_SERVER_DS); - q.set(&[ - (PlayerField::summoner_name, "Random updated player name"), - (PlayerField::first_name, "I am an updated first name"), - ]) - .r#where(PlayerFieldValue::id(&1), Comp::Gt) - .and(PlayerFieldValue::id(&8), Comp::Lt) - .query() - .await - .expect("Failed to update records with the querybuilder"); - - let found_updated_values = Player::select_query_with(SQL_SERVER_DS) - .r#where(PlayerFieldValue::id(&1), Comp::Gt) - .and(PlayerFieldValue::id(&7), Comp::LtEq) - .query() - .await - .expect("Failed to retrieve database League entries with the querybuilder"); - - found_updated_values.iter().for_each(|player| { - assert_eq!(player.summoner_name, "Random updated player name"); - assert_eq!(player.first_name, "I am an updated first name"); - }); -} - -/// Same as above, but with the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_update_with_querybuilder_with_mysql() { - // Find all the leagues with ID less or equals that 7 - // and where it's region column value is equals to 'Korea' - - let q = Player::update_query_with(MYSQL_DS); - q.set(&[ - (PlayerField::summoner_name, "Random updated player name"), - (PlayerField::first_name, "I am an updated first name"), - ]) - .r#where(PlayerFieldValue::id(&1), Comp::Gt) - .and(PlayerFieldValue::id(&8), Comp::Lt) - .query() - .await - .expect("Failed to update records with the querybuilder"); - - let found_updated_values = Player::select_query_with(MYSQL_DS) - .r#where(PlayerFieldValue::id(&1), Comp::Gt) - .and(PlayerFieldValue::id(&7), Comp::LtEq) - .query() - .await - .expect("Failed to retrieve database League entries with the querybuilder"); - - found_updated_values.iter().for_each(|player| { - assert_eq!(player.summoner_name, "Random updated player name"); - assert_eq!(player.first_name, "I am an updated first name"); - }); -} - -/// Deletes entries from the mapped entity `T` that are in the ranges filtered -/// with the QueryBuilder -/// -/// Note if the database is persisted (not created and destroyed on every docker or -/// GitHub Action wake up), it won't delete things that already have been deleted, -/// but this isn't an error. They just don't exists. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_delete_with_querybuilder() { - Tournament::delete_query() - .r#where(TournamentFieldValue::id(&14), Comp::Gt) - .and(TournamentFieldValue::id(&16), Comp::Lt) - .query() - .await - .expect("Error connecting with the database on the delete operation"); - - assert_eq!(Tournament::find_by_pk(&15).await.unwrap(), None); -} - -/// Same as the above delete, but with the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_delete_with_querybuilder_with_mssql() { - Player::delete_query_with(SQL_SERVER_DS) - .r#where(PlayerFieldValue::id(&120), Comp::Gt) - .and(PlayerFieldValue::id(&130), Comp::Lt) - .query() - .await - .expect("Error connecting with the database when we are going to delete data! :)"); - - assert!(Player::select_query_with(SQL_SERVER_DS) - .r#where(PlayerFieldValue::id(&122), Comp::Eq) - .query() - .await - .unwrap() - .is_empty()); -} - -/// Same as the above delete, but with the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_delete_with_querybuilder_with_mysql() { - Player::delete_query_with(MYSQL_DS) - .r#where(PlayerFieldValue::id(&120), Comp::Gt) - .and(PlayerFieldValue::id(&130), Comp::Lt) - .query() - .await - .expect("Error connecting with the database when we are going to delete data! :)"); - - assert!(Player::select_query_with(MYSQL_DS) - .r#where(PlayerFieldValue::id(&122), Comp::Eq) - .query() - .await - .unwrap() - .is_empty()); -} - -/// Tests for the generated SQL query after use the -/// WHERE clause -#[canyon_sql::macros::canyon_tokio_test] -fn test_where_clause() { - let l = League::select_query().r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq); - - assert_eq!(l.read_sql(), "SELECT * FROM league WHERE name = $1") -} - -/// Tests for the generated SQL query after use the -/// AND clause -#[canyon_sql::macros::canyon_tokio_test] -fn test_and_clause() { - let l = League::select_query() - .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) - .and(LeagueFieldValue::id(&10), Comp::LtEq); - - assert_eq!( - l.read_sql().trim(), - "SELECT * FROM league WHERE name = $1 AND id <= $2" - ) -} - -/// Tests for the generated SQL query after use the -/// AND clause -#[canyon_sql::macros::canyon_tokio_test] -fn test_and_clause_with_in_constraint() { - let l = League::select_query() - .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) - .and_values_in(LeagueField::id, &[1, 7, 10]); - - assert_eq!( - l.read_sql().trim(), - "SELECT * FROM league WHERE name = $1 AND id IN ($1, $2, $3)" - ) -} - -/// Tests for the generated SQL query after use the -/// AND clause -#[canyon_sql::macros::canyon_tokio_test] -fn test_or_clause() { - let l = League::select_query() - .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) - .or(LeagueFieldValue::id(&10), Comp::LtEq); - - assert_eq!( - l.read_sql().trim(), - "SELECT * FROM league WHERE name = $1 OR id <= $2" - ) -} - -/// Tests for the generated SQL query after use the -/// AND clause -#[canyon_sql::macros::canyon_tokio_test] -fn test_or_clause_with_in_constraint() { - let l = League::select_query() - .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) - .or_values_in(LeagueField::id, &[1, 7, 10]); - - assert_eq!( - l.read_sql(), - "SELECT * FROM league WHERE name = $1 OR id IN ($1, $2, $3)" - ) -} - -/// Tests for the generated SQL query after use the -/// AND clause -#[canyon_sql::macros::canyon_tokio_test] -fn test_order_by_clause() { - let l = League::select_query() - .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) - .order_by(LeagueField::id, false); - - assert_eq!( - l.read_sql(), - "SELECT * FROM league WHERE name = $1 ORDER BY id" - ) -} +// #[cfg(feature = "mysql")] +// use crate::constants::MYSQL_DS; +// #[cfg(feature = "mssql")] +// use crate::constants::SQL_SERVER_DS; +// +// /// Tests for the QueryBuilder available operations within Canyon. +// /// +// /// QueryBuilder are the way of obtain more flexibility that with +// /// the default generated queries, essentially for build the queries +// /// with the SQL filters +// /// +// use canyon_sql::{ +// crud::CrudOperations, +// query::{operators::Comp, operators::Like, ops::QueryBuilder}, +// }; +// +// use crate::tests_models::league::*; +// use crate::tests_models::player::*; +// use crate::tests_models::tournament::*; +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_generated_sql_by_the_select_querybuilder() { +// let select_with_joins = League::select_query() +// .inner_join("tournament", "league.id", "tournament.league_id") +// .left_join("team", "tournament.id", "player.tournament_id") +// .r#where(LeagueFieldValue::id(&7), Comp::Gt) +// .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) +// .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); +// // .query() +// // .await; +// // NOTE: We don't have in the docker the generated relationships +// // with the joins, so for now, we are just going to check that the +// // generated SQL by the SelectQueryBuilder is the expected +// assert_eq!( +// select_with_joins.read_sql(), +// "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" +// ) +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder() { +// // Find all the leagues with ID less or equals that 7 +// // and where it's region column value is equals to 'Korea' +// let filtered_leagues_result: Result, _> = League::select_query() +// .r#where(LeagueFieldValue::id(&50), Comp::LtEq) +// .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) +// .query() +// .await; +// +// let filtered_leagues: Vec = filtered_leagues_result.unwrap(); +// assert!(!filtered_leagues.is_empty()); +// +// let league_idx_0 = filtered_leagues.first().unwrap(); +// assert_eq!(league_idx_0.id, 34); +// assert_eq!(league_idx_0.region, "KOREA"); +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_fulllike() { +// // Find all the leagues with "LC" in their name +// let filtered_leagues_result = +// League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Full); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" +// ) +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_fulllike_with_mssql() { +// // Find all the leagues with "LC" in their name +// let filtered_leagues_result = +// League::select_query_with(SQL_SERVER_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Full); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" +// ) +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_fulllike_with_mysql() { +// // Find all the leagues with "LC" in their name +// let filtered_leagues_result = +// League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Full); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR) ,'%')" +// ) +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_leftlike() { +// // Find all the leagues whose name ends with "CK" +// let filtered_leagues_result = +// League::select_query().r#where(LeagueFieldValue::name(&"CK"), Like::Left); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" +// ) +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_leftlike_with_mssql() { +// // Find all the leagues whose name ends with "CK" +// let filtered_leagues_result = +// League::select_query().r#where(LeagueFieldValue::name(&"CK"), Like::Left); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" +// ) +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_leftlike_with_mysql() { +// // Find all the leagues whose name ends with "CK" +// let filtered_leagues_result = +// League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"CK"), Like::Left); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR))" +// ) +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_rightlike() { +// // Find all the leagues whose name starts with "LC" +// let filtered_leagues_result = +// League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Right); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" +// ) +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_rightlike_with_mssql() { +// // Find all the leagues whose name starts with "LC" +// let filtered_leagues_result = League::select_query_with(SQL_SERVER_DS) +// .r#where(LeagueFieldValue::name(&"LC"), Like::Right); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" +// ) +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_rightlike_with_mysql() { +// // Find all the leagues whose name starts with "LC" +// let filtered_leagues_result = +// League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Right); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS CHAR) ,'%')" +// ) +// } +// +// /// Same than the above but with the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_with_mssql() { +// // Find all the players where its ID column value is greater than 50 +// let filtered_find_players = Player::select_query_with(SQL_SERVER_DS) +// .r#where(PlayerFieldValue::id(&50), Comp::Gt) +// .query() +// .await; +// +// assert!(!filtered_find_players.unwrap().is_empty()); +// } +// +// /// Same than the above but with the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_with_mysql() { +// // Find all the players where its ID column value is greater than 50 +// let filtered_find_players = Player::select_query_with(MYSQL_DS) +// .r#where(PlayerFieldValue::id(&50), Comp::Gt) +// .query() +// .await; +// +// assert!(!filtered_find_players.unwrap().is_empty()); +// } +// +// /// Updates the values of the range on entries defined by the constraint parameters +// /// in the database entity +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_update_with_querybuilder() { +// // Find all the leagues with ID less or equals that 7 +// // and where it's region column value is equals to 'Korea' +// let q = League::update_query() +// .set(&[ +// (LeagueField::slug, "Updated with the QueryBuilder"), +// (LeagueField::name, "Random"), +// ]) +// .r#where(LeagueFieldValue::id(&1), Comp::Gt) +// .and(LeagueFieldValue::id(&8), Comp::Lt); +// +// /* NOTE: Family of QueryBuilders are clone, useful in case of need to read the generated SQL +// let qpr = q.clone(); +// println!("PSQL: {:?}", qpr.read_sql()); +// */ +// q.query() +// .await +// .expect("Failed to update records with the querybuilder"); +// +// let found_updated_values = League::select_query() +// .r#where(LeagueFieldValue::id(&1), Comp::Gt) +// .and(LeagueFieldValue::id(&7), Comp::Lt) +// .query() +// .await +// .expect("Failed to retrieve database League entries with the querybuilder"); +// +// found_updated_values +// .iter() +// .for_each(|league| assert_eq!(league.slug, "Updated with the QueryBuilder")); +// } +// +// /// Same as above, but with the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_update_with_querybuilder_with_mssql() { +// // Find all the leagues with ID less or equals that 7 +// // and where it's region column value is equals to 'Korea' +// let q = Player::update_query_with(SQL_SERVER_DS); +// q.set(&[ +// (PlayerField::summoner_name, "Random updated player name"), +// (PlayerField::first_name, "I am an updated first name"), +// ]) +// .r#where(PlayerFieldValue::id(&1), Comp::Gt) +// .and(PlayerFieldValue::id(&8), Comp::Lt) +// .query() +// .await +// .expect("Failed to update records with the querybuilder"); +// +// let found_updated_values = Player::select_query_with(SQL_SERVER_DS) +// .r#where(PlayerFieldValue::id(&1), Comp::Gt) +// .and(PlayerFieldValue::id(&7), Comp::LtEq) +// .query() +// .await +// .expect("Failed to retrieve database League entries with the querybuilder"); +// +// found_updated_values.iter().for_each(|player| { +// assert_eq!(player.summoner_name, "Random updated player name"); +// assert_eq!(player.first_name, "I am an updated first name"); +// }); +// } +// +// /// Same as above, but with the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_update_with_querybuilder_with_mysql() { +// // Find all the leagues with ID less or equals that 7 +// // and where it's region column value is equals to 'Korea' +// +// let q = Player::update_query_with(MYSQL_DS); +// q.set(&[ +// (PlayerField::summoner_name, "Random updated player name"), +// (PlayerField::first_name, "I am an updated first name"), +// ]) +// .r#where(PlayerFieldValue::id(&1), Comp::Gt) +// .and(PlayerFieldValue::id(&8), Comp::Lt) +// .query() +// .await +// .expect("Failed to update records with the querybuilder"); +// +// let found_updated_values = Player::select_query_with(MYSQL_DS) +// .r#where(PlayerFieldValue::id(&1), Comp::Gt) +// .and(PlayerFieldValue::id(&7), Comp::LtEq) +// .query() +// .await +// .expect("Failed to retrieve database League entries with the querybuilder"); +// +// found_updated_values.iter().for_each(|player| { +// assert_eq!(player.summoner_name, "Random updated player name"); +// assert_eq!(player.first_name, "I am an updated first name"); +// }); +// } +// +// /// Deletes entries from the mapped entity `T` that are in the ranges filtered +// /// with the QueryBuilder +// /// +// /// Note if the database is persisted (not created and destroyed on every docker or +// /// GitHub Action wake up), it won't delete things that already have been deleted, +// /// but this isn't an error. They just don't exists. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_delete_with_querybuilder() { +// Tournament::delete_query() +// .r#where(TournamentFieldValue::id(&14), Comp::Gt) +// .and(TournamentFieldValue::id(&16), Comp::Lt) +// .query() +// .await +// .expect("Error connecting with the database on the delete operation"); +// +// assert_eq!(Tournament::find_by_pk(&15).await.unwrap(), None); +// } +// +// /// Same as the above delete, but with the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_delete_with_querybuilder_with_mssql() { +// Player::delete_query_with(SQL_SERVER_DS) +// .r#where(PlayerFieldValue::id(&120), Comp::Gt) +// .and(PlayerFieldValue::id(&130), Comp::Lt) +// .query() +// .await +// .expect("Error connecting with the database when we are going to delete data! :)"); +// +// assert!(Player::select_query_with(SQL_SERVER_DS) +// .r#where(PlayerFieldValue::id(&122), Comp::Eq) +// .query() +// .await +// .unwrap() +// .is_empty()); +// } +// +// /// Same as the above delete, but with the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_delete_with_querybuilder_with_mysql() { +// Player::delete_query_with(MYSQL_DS) +// .r#where(PlayerFieldValue::id(&120), Comp::Gt) +// .and(PlayerFieldValue::id(&130), Comp::Lt) +// .query() +// .await +// .expect("Error connecting with the database when we are going to delete data! :)"); +// +// assert!(Player::select_query_with(MYSQL_DS) +// .r#where(PlayerFieldValue::id(&122), Comp::Eq) +// .query() +// .await +// .unwrap() +// .is_empty()); +// } +// +// /// Tests for the generated SQL query after use the +// /// WHERE clause +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_where_clause() { +// let l = League::select_query().r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq); +// +// assert_eq!(l.read_sql(), "SELECT * FROM league WHERE name = $1") +// } +// +// /// Tests for the generated SQL query after use the +// /// AND clause +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_and_clause() { +// let l = League::select_query() +// .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) +// .and(LeagueFieldValue::id(&10), Comp::LtEq); +// +// assert_eq!( +// l.read_sql().trim(), +// "SELECT * FROM league WHERE name = $1 AND id <= $2" +// ) +// } +// +// /// Tests for the generated SQL query after use the +// /// AND clause +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_and_clause_with_in_constraint() { +// let l = League::select_query() +// .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) +// .and_values_in(LeagueField::id, &[1, 7, 10]); +// +// assert_eq!( +// l.read_sql().trim(), +// "SELECT * FROM league WHERE name = $1 AND id IN ($1, $2, $3)" +// ) +// } +// +// /// Tests for the generated SQL query after use the +// /// AND clause +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_or_clause() { +// let l = League::select_query() +// .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) +// .or(LeagueFieldValue::id(&10), Comp::LtEq); +// +// assert_eq!( +// l.read_sql().trim(), +// "SELECT * FROM league WHERE name = $1 OR id <= $2" +// ) +// } +// +// /// Tests for the generated SQL query after use the +// /// AND clause +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_or_clause_with_in_constraint() { +// let l = League::select_query() +// .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) +// .or_values_in(LeagueField::id, &[1, 7, 10]); +// +// assert_eq!( +// l.read_sql(), +// "SELECT * FROM league WHERE name = $1 OR id IN ($1, $2, $3)" +// ) +// } +// +// /// Tests for the generated SQL query after use the +// /// AND clause +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_order_by_clause() { +// let l = League::select_query() +// .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) +// .order_by(LeagueField::id, false); +// +// assert_eq!( +// l.read_sql(), +// "SELECT * FROM league WHERE name = $1 ORDER BY id" +// ) +// } diff --git a/tests/crud/read_operations.rs b/tests/crud/read_operations.rs index fed05601..ee908f31 100644 --- a/tests/crud/read_operations.rs +++ b/tests/crud/read_operations.rs @@ -11,7 +11,7 @@ use crate::Error; use canyon_sql::crud::CrudOperations; use crate::tests_models::league::*; -use crate::tests_models::player::*; +// use crate::tests_models::player::*; /// Tests the behaviour of a SELECT * FROM {table_name} within Canyon, through the /// `::find_all()` associated function derived with the `CanyonCrud` derive proc-macro @@ -26,9 +26,9 @@ fn test_crud_find_all() { assert!(!find_all_result.is_err()); assert!(!find_all_result.unwrap().is_empty()); - let find_all_players: Result, Box> = - Player::find_all().await; - assert!(!find_all_players.unwrap().is_empty()); + // let find_all_players: Result, Box> = + // Player::find_all().await; + // assert!(!find_all_players.unwrap().is_empty()); } /// Same as the `find_all()`, but with the unchecked variant, which directly returns `Vec` not @@ -64,112 +64,112 @@ fn test_crud_find_all_with_mysql() { assert!(!find_all_result.unwrap().is_empty()); } -/// Same as the `find_all_with()`, but with the unchecked variant and the specified dataosource, -/// returning directly `Vec` and not `Result, Err>` -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_all_unchecked_with() { - let find_all_result: Vec = League::find_all_unchecked_with(SQL_SERVER_DS).await; - assert!(!find_all_result.is_empty()); -} - -/// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -/// defined with the #[primary_key] attribute over some field of the type. -/// -/// Uses the *default datasource*. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_by_pk() { - let find_by_pk_result: Result, Box> = - League::find_by_pk(&1).await; - assert!(find_by_pk_result.as_ref().unwrap().is_some()); - - let some_league = find_by_pk_result.unwrap().unwrap(); - assert_eq!(some_league.id, 1); - assert_eq!(some_league.ext_id, 100695891328981122_i64); - assert_eq!(some_league.slug, "european-masters"); - assert_eq!(some_league.name, "European Masters"); - assert_eq!(some_league.region, "EUROPE"); - assert_eq!( - some_league.image_url, - "http://static.lolesports.com/leagues/EM_Bug_Outline1.png" - ); -} - -/// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -/// defined with the #[primary_key] attribute over some field of the type. -/// -/// Uses the *specified datasource mssql* in the second parameter of the function call. -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_by_pk_with_mssql() { - let find_by_pk_result: Result, Box> = - League::find_by_pk_with(&27, SQL_SERVER_DS).await; - assert!(find_by_pk_result.as_ref().unwrap().is_some()); - - let some_league = find_by_pk_result.unwrap().unwrap(); - assert_eq!(some_league.id, 27); - assert_eq!(some_league.ext_id, 107898214974993351_i64); - assert_eq!(some_league.slug, "college_championship"); - assert_eq!(some_league.name, "College Championship"); - assert_eq!(some_league.region, "NORTH AMERICA"); - assert_eq!( - some_league.image_url, - "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" - ); -} - -/// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -/// defined with the #[primary_key] attribute over some field of the type. -/// -/// Uses the *specified datasource mysql* in the second parameter of the function call. -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_by_pk_with_mysql() { - let find_by_pk_result: Result, Box> = - League::find_by_pk_with(&27, MYSQL_DS).await; - assert!(find_by_pk_result.as_ref().unwrap().is_some()); - - let some_league = find_by_pk_result.unwrap().unwrap(); - assert_eq!(some_league.id, 27); - assert_eq!(some_league.ext_id, 107898214974993351_i64); - assert_eq!(some_league.slug, "college_championship"); - assert_eq!(some_league.name, "College Championship"); - assert_eq!(some_league.region, "NORTH AMERICA"); - assert_eq!( - some_league.image_url, - "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" - ); -} - -/// Counts how many rows contains an entity on the target database. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_count_operation() { - assert_eq!( - League::find_all().await.unwrap().len() as i64, - League::count().await.unwrap() - ); -} - -/// Counts how many rows contains an entity on the target database using -/// the specified datasource mssql -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_count_with_operation_mssql() { - assert_eq!( - League::find_all_with(SQL_SERVER_DS).await.unwrap().len() as i64, - League::count_with(SQL_SERVER_DS).await.unwrap() - ); -} - -/// Counts how many rows contains an entity on the target database using -/// the specified datasource mysql -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_count_with_operation_mysql() { - assert_eq!( - League::find_all_with(MYSQL_DS).await.unwrap().len() as i64, - League::count_with(MYSQL_DS).await.unwrap() - ); -} +// /// Same as the `find_all_with()`, but with the unchecked variant and the specified dataosource, +// /// returning directly `Vec` and not `Result, Err>` +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_all_unchecked_with() { +// let find_all_result: Vec = League::find_all_unchecked_with(SQL_SERVER_DS).await; +// assert!(!find_all_result.is_empty()); +// } +// +// /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +// /// defined with the #[primary_key] attribute over some field of the type. +// /// +// /// Uses the *default datasource*. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_by_pk() { +// let find_by_pk_result: Result, Box> = +// League::find_by_pk(&1).await; +// assert!(find_by_pk_result.as_ref().unwrap().is_some()); +// +// let some_league = find_by_pk_result.unwrap().unwrap(); +// assert_eq!(some_league.id, 1); +// assert_eq!(some_league.ext_id, 100695891328981122_i64); +// assert_eq!(some_league.slug, "european-masters"); +// assert_eq!(some_league.name, "European Masters"); +// assert_eq!(some_league.region, "EUROPE"); +// assert_eq!( +// some_league.image_url, +// "http://static.lolesports.com/leagues/EM_Bug_Outline1.png" +// ); +// } +// +// /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +// /// defined with the #[primary_key] attribute over some field of the type. +// /// +// /// Uses the *specified datasource mssql* in the second parameter of the function call. +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_by_pk_with_mssql() { +// let find_by_pk_result: Result, Box> = +// League::find_by_pk_with(&27, SQL_SERVER_DS).await; +// assert!(find_by_pk_result.as_ref().unwrap().is_some()); +// +// let some_league = find_by_pk_result.unwrap().unwrap(); +// assert_eq!(some_league.id, 27); +// assert_eq!(some_league.ext_id, 107898214974993351_i64); +// assert_eq!(some_league.slug, "college_championship"); +// assert_eq!(some_league.name, "College Championship"); +// assert_eq!(some_league.region, "NORTH AMERICA"); +// assert_eq!( +// some_league.image_url, +// "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" +// ); +// } +// +// /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +// /// defined with the #[primary_key] attribute over some field of the type. +// /// +// /// Uses the *specified datasource mysql* in the second parameter of the function call. +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_by_pk_with_mysql() { +// let find_by_pk_result: Result, Box> = +// League::find_by_pk_with(&27, MYSQL_DS).await; +// assert!(find_by_pk_result.as_ref().unwrap().is_some()); +// +// let some_league = find_by_pk_result.unwrap().unwrap(); +// assert_eq!(some_league.id, 27); +// assert_eq!(some_league.ext_id, 107898214974993351_i64); +// assert_eq!(some_league.slug, "college_championship"); +// assert_eq!(some_league.name, "College Championship"); +// assert_eq!(some_league.region, "NORTH AMERICA"); +// assert_eq!( +// some_league.image_url, +// "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" +// ); +// } +// +// /// Counts how many rows contains an entity on the target database. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_count_operation() { +// assert_eq!( +// League::find_all().await.unwrap().len() as i64, +// League::count().await.unwrap() +// ); +// } +// +// /// Counts how many rows contains an entity on the target database using +// /// the specified datasource mssql +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_count_with_operation_mssql() { +// assert_eq!( +// League::find_all_with(SQL_SERVER_DS).await.unwrap().len() as i64, +// League::count_with(SQL_SERVER_DS).await.unwrap() +// ); +// } +// +// /// Counts how many rows contains an entity on the target database using +// /// the specified datasource mysql +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_count_with_operation_mysql() { +// assert_eq!( +// League::find_all_with(MYSQL_DS).await.unwrap().len() as i64, +// League::count_with(MYSQL_DS).await.unwrap() +// ); +// } diff --git a/tests/crud/update_operations.rs b/tests/crud/update_operations.rs index 6904d34f..7b1466f1 100644 --- a/tests/crud/update_operations.rs +++ b/tests/crud/update_operations.rs @@ -1,142 +1,142 @@ -use crate::tests_models::league::*; -// Integration tests for the CRUD operations available in `Canyon` that -/// generates and executes *UPDATE* statements -use canyon_sql::crud::CrudOperations; - -#[cfg(feature = "mysql")] -use crate::constants::MYSQL_DS; -#[cfg(feature = "mssql")] -use crate::constants::SQL_SERVER_DS; - -/// Update operation is a *CRUD* method defined for some entity `T`, that works by appliying -/// some change to a Rust's entity instance, and persisting them into the database. -/// -/// The `t.update(&self)` operation is only enabled for types that -/// has, at least, one of it's fields annotated with a `#[primary_key]` -/// operation, because we use that concrete field to construct the clause that targets -/// that entity. -/// -/// Attempt of usage the `t.update(&self)` method on an entity without `#[primary_key]` -/// will raise a runtime error. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_update_method_operation() { - // We first retrieve some entity from the database. Note that we must make - // the retrieved instance mutable of clone it to a new mutable resource - let mut updt_candidate: League = League::find_by_pk(&1) - .await - .expect("[1] - Failed the query to the database") - .expect("[1] - No entity found for the primary key value passed in"); - - // The ext_id field value is extracted from the sql scripts under the - // docker/sql folder. We are retrieving the first entity inserted at the - // wake-up time of the database, and now checking some of its properties. - assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); - - // Modify the value, and perform the update - let updt_value: i64 = 593064_i64; - updt_candidate.ext_id = updt_value; - updt_candidate - .update() - .await - .expect("Failed the update operation"); - - // Retrieve it again, and check if the value was really updated - let updt_entity: League = League::find_by_pk(&1) - .await - .expect("[2] - Failed the query to the database") - .expect("[2] - No entity found for the primary key value passed in"); - - assert_eq!(updt_entity.ext_id, updt_value); - - // We roll back the changes to the initial value to don't broke other tests - // the next time that will run - updt_candidate.ext_id = 100695891328981122_i64; - updt_candidate - .update() - .await - .expect("Failed to restore the initial value in the psql update operation"); -} - -/// Same as the above test, but with the specified datasource. -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_update_with_mssql_method_operation() { - // We first retrieve some entity from the database. Note that we must make - // the retrieved instance mutable of clone it to a new mutable resource - let mut updt_candidate: League = League::find_by_pk_with(&1, SQL_SERVER_DS) - .await - .expect("[1] - Failed the query to the database") - .expect("[1] - No entity found for the primary key value passed in"); - - // The ext_id field value is extracted from the sql scripts under the - // docker/sql folder. We are retrieving the first entity inserted at the - // wake-up time of the database, and now checking some of its properties. - assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); - - // Modify the value, and perform the update - let updt_value: i64 = 59306442534_i64; - updt_candidate.ext_id = updt_value; - updt_candidate - .update_with(SQL_SERVER_DS) - .await - .expect("Failed the update operation"); - - // Retrieve it again, and check if the value was really updated - let updt_entity: League = League::find_by_pk_with(&1, SQL_SERVER_DS) - .await - .expect("[2] - Failed the query to the database") - .expect("[2] - No entity found for the primary key value passed in"); - - assert_eq!(updt_entity.ext_id, updt_value); - - // We rollback the changes to the initial value to don't broke other tests - // the next time that will run - updt_candidate.ext_id = 100695891328981122_i64; - updt_candidate - .update_with(SQL_SERVER_DS) - .await - .expect("Failed to restablish the initial value update operation"); -} - -/// Same as the above test, but with the specified datasource. -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_update_with_mysql_method_operation() { - // We first retrieve some entity from the database. Note that we must make - // the retrieved instance mutable of clone it to a new mutable resource - - let mut updt_candidate: League = League::find_by_pk_with(&1, MYSQL_DS) - .await - .expect("[1] - Failed the query to the database") - .expect("[1] - No entity found for the primary key value passed in"); - - // The ext_id field value is extracted from the sql scripts under the - // docker/sql folder. We are retrieving the first entity inserted at the - // wake up time of the database, and now checking some of its properties. - assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); - - // Modify the value, and perform the update - let updt_value: i64 = 59306442534_i64; - updt_candidate.ext_id = updt_value; - updt_candidate - .update_with(MYSQL_DS) - .await - .expect("Failed the update operation"); - - // Retrieve it again, and check if the value was really updated - let updt_entity: League = League::find_by_pk_with(&1, MYSQL_DS) - .await - .expect("[2] - Failed the query to the database") - .expect("[2] - No entity found for the primary key value passed in"); - - assert_eq!(updt_entity.ext_id, updt_value); - - // We rollback the changes to the initial value to don't broke other tests - // the next time that will run - updt_candidate.ext_id = 100695891328981122_i64; - updt_candidate - .update_with(MYSQL_DS) - .await - .expect("Failed to restablish the initial value update operation"); -} +// use crate::tests_models::league::*; +// // Integration tests for the CRUD operations available in `Canyon` that +// /// generates and executes *UPDATE* statements +// use canyon_sql::crud::CrudOperations; +// +// #[cfg(feature = "mysql")] +// use crate::constants::MYSQL_DS; +// #[cfg(feature = "mssql")] +// use crate::constants::SQL_SERVER_DS; +// +// /// Update operation is a *CRUD* method defined for some entity `T`, that works by appliying +// /// some change to a Rust's entity instance, and persisting them into the database. +// /// +// /// The `t.update(&self)` operation is only enabled for types that +// /// has, at least, one of it's fields annotated with a `#[primary_key]` +// /// operation, because we use that concrete field to construct the clause that targets +// /// that entity. +// /// +// /// Attempt of usage the `t.update(&self)` method on an entity without `#[primary_key]` +// /// will raise a runtime error. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_update_method_operation() { +// // We first retrieve some entity from the database. Note that we must make +// // the retrieved instance mutable of clone it to a new mutable resource +// let mut updt_candidate: League = League::find_by_pk(&1) +// .await +// .expect("[1] - Failed the query to the database") +// .expect("[1] - No entity found for the primary key value passed in"); +// +// // The ext_id field value is extracted from the sql scripts under the +// // docker/sql folder. We are retrieving the first entity inserted at the +// // wake-up time of the database, and now checking some of its properties. +// assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); +// +// // Modify the value, and perform the update +// let updt_value: i64 = 593064_i64; +// updt_candidate.ext_id = updt_value; +// updt_candidate +// .update() +// .await +// .expect("Failed the update operation"); +// +// // Retrieve it again, and check if the value was really updated +// let updt_entity: League = League::find_by_pk(&1) +// .await +// .expect("[2] - Failed the query to the database") +// .expect("[2] - No entity found for the primary key value passed in"); +// +// assert_eq!(updt_entity.ext_id, updt_value); +// +// // We roll back the changes to the initial value to don't broke other tests +// // the next time that will run +// updt_candidate.ext_id = 100695891328981122_i64; +// updt_candidate +// .update() +// .await +// .expect("Failed to restore the initial value in the psql update operation"); +// } +// +// /// Same as the above test, but with the specified datasource. +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_update_with_mssql_method_operation() { +// // We first retrieve some entity from the database. Note that we must make +// // the retrieved instance mutable of clone it to a new mutable resource +// let mut updt_candidate: League = League::find_by_pk_with(&1, SQL_SERVER_DS) +// .await +// .expect("[1] - Failed the query to the database") +// .expect("[1] - No entity found for the primary key value passed in"); +// +// // The ext_id field value is extracted from the sql scripts under the +// // docker/sql folder. We are retrieving the first entity inserted at the +// // wake-up time of the database, and now checking some of its properties. +// assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); +// +// // Modify the value, and perform the update +// let updt_value: i64 = 59306442534_i64; +// updt_candidate.ext_id = updt_value; +// updt_candidate +// .update_with(SQL_SERVER_DS) +// .await +// .expect("Failed the update operation"); +// +// // Retrieve it again, and check if the value was really updated +// let updt_entity: League = League::find_by_pk_with(&1, SQL_SERVER_DS) +// .await +// .expect("[2] - Failed the query to the database") +// .expect("[2] - No entity found for the primary key value passed in"); +// +// assert_eq!(updt_entity.ext_id, updt_value); +// +// // We rollback the changes to the initial value to don't broke other tests +// // the next time that will run +// updt_candidate.ext_id = 100695891328981122_i64; +// updt_candidate +// .update_with(SQL_SERVER_DS) +// .await +// .expect("Failed to restablish the initial value update operation"); +// } +// +// /// Same as the above test, but with the specified datasource. +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_update_with_mysql_method_operation() { +// // We first retrieve some entity from the database. Note that we must make +// // the retrieved instance mutable of clone it to a new mutable resource +// +// let mut updt_candidate: League = League::find_by_pk_with(&1, MYSQL_DS) +// .await +// .expect("[1] - Failed the query to the database") +// .expect("[1] - No entity found for the primary key value passed in"); +// +// // The ext_id field value is extracted from the sql scripts under the +// // docker/sql folder. We are retrieving the first entity inserted at the +// // wake up time of the database, and now checking some of its properties. +// assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); +// +// // Modify the value, and perform the update +// let updt_value: i64 = 59306442534_i64; +// updt_candidate.ext_id = updt_value; +// updt_candidate +// .update_with(MYSQL_DS) +// .await +// .expect("Failed the update operation"); +// +// // Retrieve it again, and check if the value was really updated +// let updt_entity: League = League::find_by_pk_with(&1, MYSQL_DS) +// .await +// .expect("[2] - Failed the query to the database") +// .expect("[2] - No entity found for the primary key value passed in"); +// +// assert_eq!(updt_entity.ext_id, updt_value); +// +// // We rollback the changes to the initial value to don't broke other tests +// // the next time that will run +// updt_candidate.ext_id = 100695891328981122_i64; +// updt_candidate +// .update_with(MYSQL_DS) +// .await +// .expect("Failed to restablish the initial value update operation"); +// } diff --git a/tests/migrations/mod.rs b/tests/migrations/mod.rs index ebbdec5a..5ff741f1 100644 --- a/tests/migrations/mod.rs +++ b/tests/migrations/mod.rs @@ -14,7 +14,7 @@ fn test_migrations_postgresql_status_query() { assert!(conn_res.is_ok()); let db_conn = &mut conn_res.unwrap(); - let results = Migrations::query(constants::FETCH_PUBLIC_SCHEMA, [], db_conn).await; + let results = Migrations::query_rows(constants::FETCH_PUBLIC_SCHEMA, [], db_conn).await; assert!(results.is_ok()); let res = results.unwrap(); diff --git a/tests/tests_models/player.rs b/tests/tests_models/player.rs index 0cba50ec..56c34a3b 100644 --- a/tests/tests_models/player.rs +++ b/tests/tests_models/player.rs @@ -1,24 +1,24 @@ -use canyon_sql::macros::*; - -#[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] -#[canyon_entity] -/// Data model that represents a database entity for Players. -/// -/// For test the behaviour of Canyon with entities that no declares primary keys, -/// or that is configuration isn't autoincremental, we will use this class. -/// Note that this entity has a primary key declared in the database, but we will -/// omit this in Canyon, so for us, is like if the primary key wasn't set up. -/// -/// Remember that the entities that does not declare at least a field as `#[primary_key]` -/// does not have all the CRUD operations available, only the ones that doesn't -/// require of a primary key. -pub struct Player { - // #[primary_key] We will omit this to use it as a mock of entities that doesn't declare primary key - id: i32, - ext_id: i64, - first_name: String, - last_name: String, - summoner_name: String, - image_url: Option, - role: String, -} +// use canyon_sql::macros::*; +// +// #[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] +// #[canyon_entity] +// /// Data model that represents a database entity for Players. +// /// +// /// For test the behaviour of Canyon with entities that no declares primary keys, +// /// or that is configuration isn't autoincremental, we will use this class. +// /// Note that this entity has a primary key declared in the database, but we will +// /// omit this in Canyon, so for us, is like if the primary key wasn't set up. +// /// +// /// Remember that the entities that does not declare at least a field as `#[primary_key]` +// /// does not have all the CRUD operations available, only the ones that doesn't +// /// require of a primary key. +// pub struct Player { +// // #[primary_key] We will omit this to use it as a mock of entities that doesn't declare primary key +// id: i32, +// ext_id: i64, +// first_name: String, +// last_name: String, +// summoner_name: String, +// image_url: Option, +// role: String, +// } diff --git a/tests/tests_models/tournament.rs b/tests/tests_models/tournament.rs index 880076f4..001a87b5 100644 --- a/tests/tests_models/tournament.rs +++ b/tests/tests_models/tournament.rs @@ -1,15 +1,15 @@ -use crate::tests_models::league::League; -use canyon_sql::{date_time::NaiveDate, macros::*}; - -#[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] -#[canyon_entity] -pub struct Tournament { - #[primary_key] - id: i32, - ext_id: i64, - slug: String, - start_date: NaiveDate, - end_date: NaiveDate, - #[foreign_key(table = "league", column = "id")] - league: i32, -} +// use crate::tests_models::league::League; +// use canyon_sql::{date_time::NaiveDate, macros::*}; +// +// #[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] +// #[canyon_entity] +// pub struct Tournament { +// #[primary_key] +// id: i32, +// ext_id: i64, +// slug: String, +// start_date: NaiveDate, +// end_date: NaiveDate, +// #[foreign_key(table = "league", column = "id")] +// league: i32, +// } From c1bfafb466aa6a13aade0bf2022e72be4da37327 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 5 Feb 2025 17:20:24 +0100 Subject: [PATCH 067/155] feat(wip): only postgres understand relaxing lifetime bounds --- .../src/connection/db_clients/mssql.rs | 38 +++++++++++++++---- .../src/connection/db_clients/mysql.rs | 3 +- canyon_core/src/transaction.rs | 6 +-- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs index dd9c7514..2703f073 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -26,11 +26,12 @@ impl DbConnection for SqlServerConnection { } fn query<'a, S, R: RowMapper>(&self, stmt: S, params: &[&'a (dyn QueryParameter<'_>)]) - -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send + -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send + 'a where S: AsRef + Display + Send { - sqlserver_query_launcher::query(stmt, params, self) + // sqlserver_query_launcher::query(stmt, params, self) + async move { todo!() } } fn query_one<'a, R>(&self, stmt: &str, params: &[&'a (dyn QueryParameter<'a>)]) @@ -48,7 +49,7 @@ impl DbConnection for SqlServerConnection { #[cfg(feature = "mssql")] pub(crate) mod sqlserver_query_launcher { - use tiberius::QueryStream; + use tiberius::{ColumnData, QueryStream}; use crate::mapper::RowMapper; use super::*; @@ -107,12 +108,10 @@ pub(crate) mod sqlserver_query_launcher { } } - async fn execute_query<'a, S>(stmt: S, params: &[&'a (dyn QueryParameter<'_>)], conn: &SqlServerConnection) + async fn execute_query<'a>(stmt: &str, params: &[&'a (dyn QueryParameter<'_>)], conn: &SqlServerConnection) -> Result, Box<(dyn Error + Send + Sync)>> - where - S: AsRef + Display { - let mut stmt = String::from(stmt.as_ref()); + let mut stmt = String::from(stmt); if stmt.contains("RETURNING") { let c = stmt.clone(); let temp = c.split_once("RETURNING").unwrap(); @@ -130,7 +129,30 @@ pub(crate) mod sqlserver_query_launcher { // replace below. We may use our own type Query to address this concerns when the query // is generated let mut mssql_query = Query::new(stmt.to_owned().replace('$', "@P")); - params.iter().for_each(|param| mssql_query.bind(*param)); + params.iter().for_each(|param| { + let column_data = param.as_sqlserver_param(); + match column_data { + ColumnData::U8(v) => { mssql_query.bind(v) } + ColumnData::I16(v) => { mssql_query.bind(v) } + ColumnData::I32(v) => { mssql_query.bind(v) } + ColumnData::I64(v) => { mssql_query.bind(v) } + ColumnData::F32(v) => { mssql_query.bind(v) } + ColumnData::F64(v) => { mssql_query.bind(v) } + ColumnData::Bit(v) => { mssql_query.bind(v) } + ColumnData::String(v) => { mssql_query.bind(v) } + ColumnData::Guid(v) => { mssql_query.bind(v) } + ColumnData::Binary(v) => { mssql_query.bind(v) } + ColumnData::Numeric(v) => { mssql_query.bind(v) } + ColumnData::Xml(v) => { mssql_query.bind(v.as_deref().map(ToString::to_string)) } + ColumnData::DateTime(v) => { todo!() } + ColumnData::SmallDateTime(v) => { todo!() } + ColumnData::Time(v) => { todo!() } + ColumnData::Date(v) => { todo!() } + ColumnData::DateTime2(v) => { todo!() } + ColumnData::DateTimeOffset(v) => { todo!() } + } + // mssql_query.bind() + }); #[allow(mutable_transmutes)] // TODO: pls solve this elegantly someday :( let sqlservconn = diff --git a/canyon_core/src/connection/db_clients/mysql.rs b/canyon_core/src/connection/db_clients/mysql.rs index 2cb31f2d..4ef12b0f 100644 --- a/canyon_core/src/connection/db_clients/mysql.rs +++ b/canyon_core/src/connection/db_clients/mysql.rs @@ -32,7 +32,8 @@ impl DbConnection for MysqlConnection { where S: AsRef + Display + Send { - mysql_query_launcher::query(stmt, params, self) + // mysql_query_launcher::query(stmt, params, self) + async move { todo!() } } fn query_one<'a, R: RowMapper>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index 8dc48339..65ff09d5 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -20,12 +20,12 @@ pub trait Transaction { async move { input.query_rows(stmt.as_ref(), params.as_ref()).await } } - fn query<'a, R: RowMapper>( - stmt: &str, + fn query<'a, S, R: RowMapper>( + stmt: S, params: &[&'a (dyn QueryParameter<'a>)], input: impl DbConnection + Send, ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> - where + where S: AsRef + Display + Send, { async move { input.query(stmt, params).await } } From 67613a3d34148b4ccb59254c6b2f94271d79aa14 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 10 Feb 2025 09:06:14 +0100 Subject: [PATCH 068/155] feat(wip): re-enabling mysql queries --- canyon_core/src/connection/db_clients/mssql.rs | 2 +- canyon_core/src/connection/db_clients/mysql.rs | 6 +++--- canyon_core/src/connection/db_clients/postgresql.rs | 4 ++-- canyon_core/src/connection/db_connector.rs | 8 ++++---- canyon_core/src/transaction.rs | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs index 2703f073..d58618a4 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -26,7 +26,7 @@ impl DbConnection for SqlServerConnection { } fn query<'a, S, R: RowMapper>(&self, stmt: S, params: &[&'a (dyn QueryParameter<'_>)]) - -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send + 'a + -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where S: AsRef + Display + Send { diff --git a/canyon_core/src/connection/db_clients/mysql.rs b/canyon_core/src/connection/db_clients/mysql.rs index 4ef12b0f..0f5bf1ea 100644 --- a/canyon_core/src/connection/db_clients/mysql.rs +++ b/canyon_core/src/connection/db_clients/mysql.rs @@ -28,12 +28,12 @@ impl DbConnection for MysqlConnection { } fn query<'a, S, R: RowMapper>(&self, stmt: S, params: &[&'a (dyn QueryParameter<'_>)],) - -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send + -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where S: AsRef + Display + Send { - // mysql_query_launcher::query(stmt, params, self) - async move { todo!() } + mysql_query_launcher::query(stmt, params, self) + // async move { todo!() } } fn query_one<'a, R: RowMapper>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) diff --git a/canyon_core/src/connection/db_clients/postgresql.rs b/canyon_core/src/connection/db_clients/postgresql.rs index 5cabe5d1..03f6337f 100644 --- a/canyon_core/src/connection/db_clients/postgresql.rs +++ b/canyon_core/src/connection/db_clients/postgresql.rs @@ -29,7 +29,7 @@ impl DbConnection for PostgreSqlConnection { &self, stmt: S, params: &[&'a (dyn QueryParameter<'_>)], - ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send + ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where S: AsRef + Display + Send { @@ -83,7 +83,7 @@ pub(crate) mod postgres_query_launcher { stmt: S, params: &[&'a (dyn QueryParameter<'_>)], conn: &PostgreSqlConnection, - ) -> Result, Box<(dyn Error + Sync + Send + 'a)>> + ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send { diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index e9f8b7d4..02bb9d5d 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -26,7 +26,7 @@ pub trait DbConnection { &self, stmt: S, params: &[&'a (dyn QueryParameter<'_>)], - ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send + ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where S: AsRef + Display + Send; @@ -57,7 +57,7 @@ impl DbConnection for &str { &self, stmt: S, params: &[&'a (dyn QueryParameter<'_>)], - ) -> Result, Box<(dyn Error + Sync + Send + 'a)>> + ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, { @@ -105,7 +105,7 @@ impl DbConnection for DatabaseConnection { &self, stmt: S, params: &[&'a (dyn QueryParameter<'_>)], - ) -> Result, Box<(dyn Error + Sync + Send + 'a)>> + ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, { @@ -144,7 +144,7 @@ impl DbConnection for &mut DatabaseConnection { &self, stmt: S, params: &[&'a (dyn QueryParameter<'_>)], - ) -> Result, Box<(dyn Error + Sync + Send + 'a)>> + ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, { diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index 65ff09d5..26d4e779 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -24,7 +24,7 @@ pub trait Transaction { stmt: S, params: &[&'a (dyn QueryParameter<'a>)], input: impl DbConnection + Send, - ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + ) -> impl Future, Box<(dyn Error + Sync + Send)>>> where S: AsRef + Display + Send, { async move { input.query(stmt, params).await } From 4fbd755bcdea699210d4b102557a5850b51b999d Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 10 Feb 2025 09:19:16 +0100 Subject: [PATCH 069/155] feat(wip): re-enabling sqlserver on query --- .../src/connection/db_clients/mssql.rs | 3 +- canyon_core/src/transaction.rs | 30 ++--- tests/crud/init_mssql.rs | 124 +++++++++--------- 3 files changed, 78 insertions(+), 79 deletions(-) diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs index d58618a4..f2ec49a6 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -30,8 +30,7 @@ impl DbConnection for SqlServerConnection { where S: AsRef + Display + Send { - // sqlserver_query_launcher::query(stmt, params, self) - async move { todo!() } + sqlserver_query_launcher::query(stmt, params, self) } fn query_one<'a, R>(&self, stmt: &str, params: &[&'a (dyn QueryParameter<'a>)]) diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index 26d4e779..ce752ac5 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -5,21 +5,6 @@ use std::{fmt::Display, future::Future}; use crate::mapper::RowMapper; pub trait Transaction { - /// Performs a query against the targeted database by the selected or - /// the defaulted datasource, wrapping the resultant collection of entities - /// in [`super::rows::CanyonRows`] - fn query_rows<'a, S, Z>( - stmt: S, - params: Z, - input: impl DbConnection + Send + 'a, - ) -> impl Future>> + Send - where - S: AsRef + Display + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send + 'a, - { - async move { input.query_rows(stmt.as_ref(), params.as_ref()).await } - } - fn query<'a, S, R: RowMapper>( stmt: S, params: &[&'a (dyn QueryParameter<'a>)], @@ -41,4 +26,19 @@ pub trait Transaction { { async move { input.query_one(stmt.as_ref(), params.as_ref()).await } } + + /// Performs a query against the targeted database by the selected or + /// the defaulted datasource, wrapping the resultant collection of entities + /// in [`super::rows::CanyonRows`] + fn query_rows<'a, S, Z>( + stmt: S, + params: Z, + input: impl DbConnection + Send + 'a, + ) -> impl Future>> + Send + where + S: AsRef + Display + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send + 'a, + { + async move { input.query_rows(stmt.as_ref(), params.as_ref()).await } + } } diff --git a/tests/crud/init_mssql.rs b/tests/crud/init_mssql.rs index db8ff69f..2be533bf 100644 --- a/tests/crud/init_mssql.rs +++ b/tests/crud/init_mssql.rs @@ -1,62 +1,62 @@ -// use crate::constants::SQL_SERVER_CREATE_TABLES; -// use crate::constants::SQL_SERVER_DS; -// use crate::constants::SQL_SERVER_FILL_TABLE_VALUES; -// use crate::tests_models::league::League; -// -// use canyon_sql::crud::CrudOperations; -// use canyon_sql::db_clients::tiberius::{Client, Config}; -// use canyon_sql::runtime::tokio::net::TcpStream; -// use canyon_sql::runtime::tokio_util::compat::TokioAsyncWriteCompatExt; -// -// /// In order to initialize data on `SqlServer`. we must manually insert it -// /// when the docker starts. SqlServer official docker from Microsoft does -// /// not allow you to run `.sql` files against the database (not at least, without) -// /// using a workaround. So, we are going to query the `SqlServer` to check if already -// /// has some data (other processes, persistence or multi-threading envs), af if not, -// /// we are going to retrieve the inserted data on the `postgreSQL` at start-up and -// /// inserting into the `SqlServer` instance. -// /// -// /// This will be marked as `#[ignore]`, so we can force to run first the marked as -// /// ignored, check the data available, perform the necessary init operations and -// /// then *cargo test * the real integration tests -// #[canyon_sql::macros::canyon_tokio_test] -// #[ignore] -// fn initialize_sql_server_docker_instance() { -// static CONN_STR: &str = // TODO: change this for the DS when will be in the public API -// "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true"; -// -// canyon_sql::runtime::futures::executor::block_on(async { -// let config = Config::from_ado_string(CONN_STR).unwrap(); -// -// let tcp = TcpStream::connect(config.get_addr()).await.unwrap(); -// let tcp2 = TcpStream::connect(config.get_addr()).await.unwrap(); -// tcp.set_nodelay(true).ok(); -// -// let mut client = Client::connect(config.clone(), tcp.compat_write()) -// .await -// .unwrap(); -// -// // Create the tables -// let query_result = client.query(SQL_SERVER_CREATE_TABLES, &[]).await; -// assert!(query_result.is_ok()); -// -// let leagues_sql = League::find_all_with(SQL_SERVER_DS).await; -// println!("LSQL ERR: {leagues_sql:?}"); -// assert!(leagues_sql.is_ok()); -// -// match leagues_sql { -// Ok(ref leagues) => { -// let leagues_len = leagues.len(); -// println!("Leagues already inserted on SQLSERVER: {:?}", &leagues_len); -// if leagues.len() < 10 { -// let mut client2 = Client::connect(config, tcp2.compat_write()) -// .await -// .expect("Can't connect to MSSQL"); -// let result = client2.query(SQL_SERVER_FILL_TABLE_VALUES, &[]).await; -// assert!(result.is_ok()); -// } -// } -// Err(e) => eprintln!("Error retrieving the leagues: {e}"), -// } -// }); -// } +use crate::constants::SQL_SERVER_CREATE_TABLES; +use crate::constants::SQL_SERVER_DS; +use crate::constants::SQL_SERVER_FILL_TABLE_VALUES; +use crate::tests_models::league::League; + +use canyon_sql::crud::CrudOperations; +use canyon_sql::db_clients::tiberius::{Client, Config}; +use canyon_sql::runtime::tokio::net::TcpStream; +use canyon_sql::runtime::tokio_util::compat::TokioAsyncWriteCompatExt; + +/// In order to initialize data on `SqlServer`. we must manually insert it +/// when the docker starts. SqlServer official docker from Microsoft does +/// not allow you to run `.sql` files against the database (not at least, without) +/// using a workaround. So, we are going to query the `SqlServer` to check if already +/// has some data (other processes, persistence or multi-threading envs), af if not, +/// we are going to retrieve the inserted data on the `postgreSQL` at start-up and +/// inserting into the `SqlServer` instance. +/// +/// This will be marked as `#[ignore]`, so we can force to run first the marked as +/// ignored, check the data available, perform the necessary init operations and +/// then *cargo test * the real integration tests +#[canyon_sql::macros::canyon_tokio_test] +#[ignore] +fn initialize_sql_server_docker_instance() { + static CONN_STR: &str = // TODO: change this for the DS when will be in the public API + "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true"; + + canyon_sql::runtime::futures::executor::block_on(async { + let config = Config::from_ado_string(CONN_STR).unwrap(); + + let tcp = TcpStream::connect(config.get_addr()).await.unwrap(); + let tcp2 = TcpStream::connect(config.get_addr()).await.unwrap(); + tcp.set_nodelay(true).ok(); + + let mut client = Client::connect(config.clone(), tcp.compat_write()) + .await + .unwrap(); + + // Create the tables + let query_result = client.query(SQL_SERVER_CREATE_TABLES, &[]).await; + assert!(query_result.is_ok()); + + let leagues_sql = League::find_all_with(SQL_SERVER_DS).await; + println!("LSqlServer: {leagues_sql:?}"); + assert!(leagues_sql.is_ok()); + + match leagues_sql { + Ok(ref leagues) => { + let leagues_len = leagues.len(); + println!("Leagues already inserted on SQLSERVER: {:?}", &leagues_len); + if leagues.len() < 10 { + let mut client2 = Client::connect(config, tcp2.compat_write()) + .await + .expect("Can't connect to MSSQL"); + let result = client2.query(SQL_SERVER_FILL_TABLE_VALUES, &[]).await; + assert!(result.is_ok()); + } + } + Err(e) => eprintln!("Error retrieving the leagues: {e}"), + } + }); +} From 9d0739a631300a5bc8015929f744816396519829 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 10 Feb 2025 16:14:40 +0100 Subject: [PATCH 070/155] feat(wip): re-enabling all the read operations --- .../src/connection/db_clients/mssql.rs | 144 +++-- .../src/connection/db_clients/mysql.rs | 92 ++-- .../src/connection/db_clients/postgresql.rs | 77 ++- canyon_core/src/connection/db_connector.rs | 107 +++- canyon_core/src/rows.rs | 9 + canyon_core/src/transaction.rs | 18 +- canyon_crud/src/crud.rs | 80 +-- canyon_macros/src/canyon_mapper_macro.rs | 1 + .../src/query_operations/macro_template.rs | 36 +- canyon_macros/src/query_operations/read.rs | 85 ++- tests/crud/querybuilder_operations.rs | 502 +++++++++--------- tests/crud/read_operations.rs | 76 +-- tests/tests_models/player.rs | 48 +- 13 files changed, 738 insertions(+), 537 deletions(-) diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs index f2ec49a6..6bf8ec9d 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -1,13 +1,14 @@ +use crate::connection::database_type::DatabaseType; +use crate::connection::db_connector::DbConnection; +use crate::mapper::RowMapper; +use crate::rows::{FromSql, FromSqlOwnedValue}; +use crate::{query_parameters::QueryParameter, rows::CanyonRows}; #[cfg(feature = "mssql")] use async_std::net::TcpStream; use std::error::Error; use std::fmt::Display; use std::future::Future; -use crate::connection::database_type::DatabaseType; -use crate::connection::db_connector::DbConnection; -use crate::{query_parameters::QueryParameter, rows::CanyonRows}; use tiberius::Query; -use crate::mapper::RowMapper; /// A connection with a `SqlServer` database #[cfg(feature = "mssql")] @@ -20,27 +21,40 @@ impl DbConnection for SqlServerConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send - { + ) -> impl Future>> + Send { sqlserver_query_launcher::query_rows(stmt, params, self) } - fn query<'a, S, R: RowMapper>(&self, stmt: S, params: &[&'a (dyn QueryParameter<'_>)]) - -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send + fn query<'a, S, R: RowMapper>( + &self, + stmt: S, + params: &[&'a (dyn QueryParameter<'_>)], + ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where - S: AsRef + Display + Send + S: AsRef + Display + Send, { sqlserver_query_launcher::query(stmt, params, self) } - fn query_one<'a, R>(&self, stmt: &str, params: &[&'a (dyn QueryParameter<'a>)]) - -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send + fn query_one<'a, R>( + &self, + stmt: &str, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where - R: RowMapper + R: RowMapper, { sqlserver_query_launcher::query_one(stmt, params, self) } - + + fn query_one_for<'a, T: FromSqlOwnedValue>( + &self, + stmt: &str, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> impl Future>> + Send { + sqlserver_query_launcher::query_one_for(stmt, params, self) + } + fn get_database_type(&self) -> Result> { Ok(DatabaseType::SqlServer) } @@ -48,10 +62,11 @@ impl DbConnection for SqlServerConnection { #[cfg(feature = "mssql")] pub(crate) mod sqlserver_query_launcher { - use tiberius::{ColumnData, QueryStream}; - use crate::mapper::RowMapper; use super::*; - + use crate::mapper::RowMapper; + use crate::rows::FromSqlOwnedValue; + use tiberius::{ColumnData, QueryStream}; + #[inline(always)] pub(crate) async fn query<'a, S, R: RowMapper>( stmt: S, @@ -59,7 +74,7 @@ pub(crate) mod sqlserver_query_launcher { conn: &SqlServerConnection, ) -> Result, Box<(dyn Error + Sync + Send)>> where - S: AsRef + Display + Send + S: AsRef + Display + Send, { Ok(execute_query(stmt.as_ref(), params, conn) .await? @@ -70,7 +85,7 @@ pub(crate) mod sqlserver_query_launcher { .map(|row| R::deserialize_sqlserver(&row)) .collect::>()) } - + #[inline(always)] pub(crate) async fn query_rows<'a>( stmt: &str, @@ -94,22 +109,41 @@ pub(crate) mod sqlserver_query_launcher { conn: &SqlServerConnection, ) -> Result, Box<(dyn Error + Send + Sync)>> where - R: RowMapper + R: RowMapper, { - let result = execute_query(stmt, params, conn) - .await? - .into_row() - .await?; + let result = execute_query(stmt, params, conn).await?.into_row().await?; match result { - Some(r) => { Ok(Some(R::deserialize_sqlserver(&r))) } - None => { Ok(None) } + Some(r) => Ok(Some(R::deserialize_sqlserver(&r))), + None => Ok(None), } } - async fn execute_query<'a>(stmt: &str, params: &[&'a (dyn QueryParameter<'_>)], conn: &SqlServerConnection) - -> Result, Box<(dyn Error + Send + Sync)>> - { + pub(crate) async fn query_one_for<'a, T: FromSqlOwnedValue>( + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + conn: &SqlServerConnection, + ) -> Result> { + let row = execute_query(stmt, params, conn) + .await? + .into_row() + .await? + .ok_or_else(|| format!("Failure executing 'query_one_for' while retrieving the first row with stmt: {:?}", stmt))?; + + Ok(row + .into_iter() + .map(T::from_sql_owned) + .collect::>() + .remove(0)? + .ok_or_else(|| format!("Failure executing 'query_one_for' while retrieving the first column value on the first row with stmt: {:?}", stmt))? + ) + } + + async fn execute_query<'a>( + stmt: &str, + params: &[&'a (dyn QueryParameter<'_>)], + conn: &SqlServerConnection, + ) -> Result, Box<(dyn Error + Send + Sync)>> { let mut stmt = String::from(stmt); if stmt.contains("RETURNING") { let c = stmt.clone(); @@ -123,7 +157,7 @@ pub(crate) mod sqlserver_query_launcher { temp2.1.trim() ); } - + // TODO: We must address the query generation. Look at the returning example, or the // replace below. We may use our own type Query to address this concerns when the query // is generated @@ -131,24 +165,36 @@ pub(crate) mod sqlserver_query_launcher { params.iter().for_each(|param| { let column_data = param.as_sqlserver_param(); match column_data { - ColumnData::U8(v) => { mssql_query.bind(v) } - ColumnData::I16(v) => { mssql_query.bind(v) } - ColumnData::I32(v) => { mssql_query.bind(v) } - ColumnData::I64(v) => { mssql_query.bind(v) } - ColumnData::F32(v) => { mssql_query.bind(v) } - ColumnData::F64(v) => { mssql_query.bind(v) } - ColumnData::Bit(v) => { mssql_query.bind(v) } - ColumnData::String(v) => { mssql_query.bind(v) } - ColumnData::Guid(v) => { mssql_query.bind(v) } - ColumnData::Binary(v) => { mssql_query.bind(v) } - ColumnData::Numeric(v) => { mssql_query.bind(v) } - ColumnData::Xml(v) => { mssql_query.bind(v.as_deref().map(ToString::to_string)) } - ColumnData::DateTime(v) => { todo!() } - ColumnData::SmallDateTime(v) => { todo!() } - ColumnData::Time(v) => { todo!() } - ColumnData::Date(v) => { todo!() } - ColumnData::DateTime2(v) => { todo!() } - ColumnData::DateTimeOffset(v) => { todo!() } + ColumnData::U8(v) => mssql_query.bind(v), + ColumnData::I16(v) => mssql_query.bind(v), + ColumnData::I32(v) => mssql_query.bind(v), + ColumnData::I64(v) => mssql_query.bind(v), + ColumnData::F32(v) => mssql_query.bind(v), + ColumnData::F64(v) => mssql_query.bind(v), + ColumnData::Bit(v) => mssql_query.bind(v), + ColumnData::String(v) => mssql_query.bind(v), + ColumnData::Guid(v) => mssql_query.bind(v), + ColumnData::Binary(v) => mssql_query.bind(v), + ColumnData::Numeric(v) => mssql_query.bind(v), + ColumnData::Xml(v) => mssql_query.bind(v.as_deref().map(ToString::to_string)), + ColumnData::DateTime(v) => { + todo!() + } + ColumnData::SmallDateTime(v) => { + todo!() + } + ColumnData::Time(v) => { + todo!() + } + ColumnData::Date(v) => { + todo!() + } + ColumnData::DateTime2(v) => { + todo!() + } + ColumnData::DateTimeOffset(v) => { + todo!() + } } // mssql_query.bind() }); @@ -156,8 +202,6 @@ pub(crate) mod sqlserver_query_launcher { #[allow(mutable_transmutes)] // TODO: pls solve this elegantly someday :( let sqlservconn = unsafe { std::mem::transmute::<&SqlServerConnection, &mut SqlServerConnection>(conn) }; - Ok(mssql_query - .query(sqlservconn.client) - .await?) + Ok(mssql_query.query(sqlservconn.client).await?) } } diff --git a/canyon_core/src/connection/db_clients/mysql.rs b/canyon_core/src/connection/db_clients/mysql.rs index 0f5bf1ea..645e6b72 100644 --- a/canyon_core/src/connection/db_clients/mysql.rs +++ b/canyon_core/src/connection/db_clients/mysql.rs @@ -1,15 +1,16 @@ -#[cfg(feature = "mysql")] -use mysql_async::Pool; -use std::error::Error; -use std::fmt::Display; -use std::future::Future; use crate::connection::database_type::DatabaseType; use crate::connection::db_connector::DbConnection; +use crate::mapper::RowMapper; +use crate::rows::{FromSql, FromSqlOwnedValue}; use crate::{query_parameters::QueryParameter, rows::CanyonRows}; +#[cfg(feature = "mysql")] +use mysql_async::Pool; use mysql_async::Row; use mysql_common::constants::ColumnType; use mysql_common::row; -use crate::mapper::RowMapper; +use std::error::Error; +use std::fmt::Display; +use std::future::Future; /// A connection with a `Mysql` database #[cfg(feature = "mysql")] @@ -22,26 +23,38 @@ impl DbConnection for MysqlConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send - { + ) -> impl Future>> + Send { mysql_query_launcher::query_rows(stmt, params, self) } - fn query<'a, S, R: RowMapper>(&self, stmt: S, params: &[&'a (dyn QueryParameter<'_>)],) - -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send + fn query<'a, S, R: RowMapper>( + &self, + stmt: S, + params: &[&'a (dyn QueryParameter<'_>)], + ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where - S: AsRef + Display + Send + S: AsRef + Display + Send, { mysql_query_launcher::query(stmt, params, self) // async move { todo!() } } - fn query_one<'a, R: RowMapper>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) - -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send - { + fn query_one<'a, R: RowMapper>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send { mysql_query_launcher::query_one(stmt, params, self) } + fn query_one_for<'a, T: FromSqlOwnedValue>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> impl Future>> + Send { + mysql_query_launcher::query_one_for(stmt, params, self) + } + fn get_database_type(&self) -> Result> { Ok(DatabaseType::MySQL) } @@ -69,14 +82,13 @@ pub(crate) mod mysql_query_launcher { conn: &MysqlConnection, ) -> Result, Box<(dyn Error + Sync + Send)>> where - S: AsRef + Display + Send + S: AsRef + Display + Send, { - Ok( - execute_query(stmt, params, conn).await? - .iter() - .map(|row| R::deserialize_mysql(row)) - .collect() - ) + Ok(execute_query(stmt, params, conn) + .await? + .iter() + .map(|row| R::deserialize_mysql(row)) + .collect()) } #[inline(always)] @@ -85,9 +97,7 @@ pub(crate) mod mysql_query_launcher { params: &[&'a dyn QueryParameter<'a>], conn: &MysqlConnection, ) -> Result> { - Ok(CanyonRows::MySQL( - execute_query(stmt, params, conn).await? - )) + Ok(CanyonRows::MySQL(execute_query(stmt, params, conn).await?)) } #[inline(always)] @@ -96,24 +106,38 @@ pub(crate) mod mysql_query_launcher { params: &[&'a dyn QueryParameter<'a>], conn: &MysqlConnection, ) -> Result, Box<(dyn Error + Sync + Send)>> { - let result = execute_query(stmt, params, conn) - .await?; - + let result = execute_query(stmt, params, conn).await?; + match result.first() { Some(r) => Ok(Some(R::deserialize_mysql(r))), None => Ok(None), } } + #[inline(always)] + pub(crate) async fn query_one_for<'a, T: FromSqlOwnedValue>( + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + conn: &MysqlConnection, + ) -> Result> { + Ok(execute_query(stmt, params, conn) + .await? + .first() + .ok_or_else(|| format!("Failure executing 'query_one_for' while retrieving the first row with stmt: {:?}", stmt))? + .get::(0) + .ok_or_else(|| format!("Failure executing 'query_one_for' while retrieving the first column value on the first row with stmt: {:?}", stmt))? + ) + } + #[inline(always)] // TODO: very provisional implementation! care! - // TODO: would be better to launch a simple query for the last id? + // TODO: would be better to launch a simple query for the last id? async fn execute_query<'a, S>( stmt: S, params: &[&'a dyn QueryParameter<'_>], conn: &MysqlConnection, ) -> Result, Box<(dyn Error + Sync + Send)>> where - S: AsRef + Display + Send + S: AsRef + Display + Send, { let mysql_connection = conn.client.get_conn().await?; @@ -141,9 +165,7 @@ pub(crate) mod mysql_query_launcher { params: params_query, }; - let mut query_result = query_with_params - .run(mysql_connection) - .await?; + let mut query_result = query_with_params.run(mysql_connection).await?; let result_rows = if is_insert { let last_insert = query_result @@ -156,11 +178,9 @@ pub(crate) mod mysql_query_launcher { Arc::new([mysql_async::Column::new(ColumnType::MYSQL_TYPE_UNKNOWN)]), )] } else { - query_result - .collect::() - .await? + query_result.collect::().await? }; - + Ok(result_rows) } } diff --git a/canyon_core/src/connection/db_clients/postgresql.rs b/canyon_core/src/connection/db_clients/postgresql.rs index 03f6337f..375aac66 100644 --- a/canyon_core/src/connection/db_clients/postgresql.rs +++ b/canyon_core/src/connection/db_clients/postgresql.rs @@ -1,12 +1,13 @@ +use crate::connection::database_type::DatabaseType; +use crate::connection::db_connector::DbConnection; +use crate::mapper::RowMapper; +use crate::rows::{FromSql, FromSqlOwnedValue}; use crate::{query_parameters::QueryParameter, rows::CanyonRows}; use std::error::Error; use std::fmt::Display; use std::future::Future; -use crate::connection::database_type::DatabaseType; -use crate::connection::db_connector::DbConnection; #[cfg(feature = "postgres")] use tokio_postgres::Client; -use crate::mapper::RowMapper; /// A connection with a `PostgreSQL` database #[cfg(feature = "postgres")] @@ -20,8 +21,7 @@ impl DbConnection for PostgreSqlConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send - { + ) -> impl Future>> + Send { postgres_query_launcher::query_rows(stmt, params, self) } @@ -31,19 +31,30 @@ impl DbConnection for PostgreSqlConnection { params: &[&'a (dyn QueryParameter<'_>)], ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where - S: AsRef + Display + Send + S: AsRef + Display + Send, { postgres_query_launcher::query(stmt, params, self) } - fn query_one<'a, R>(&self, stmt: &str, params: &[&'a (dyn QueryParameter<'a>)]) - -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send + fn query_one<'a, R>( + &self, + stmt: &str, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where - R: RowMapper + R: RowMapper, { postgres_query_launcher::query_one(stmt, params, self) } - + + fn query_one_for<'a, T: FromSqlOwnedValue>( + &self, + stmt: &str, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> impl Future>> + Send { + postgres_query_launcher::query_one_for(stmt, params, self) + } + fn get_database_type(&self) -> Result> { Ok(DatabaseType::PostgreSql) } @@ -51,10 +62,10 @@ impl DbConnection for PostgreSqlConnection { #[cfg(feature = "postgres")] pub(crate) mod postgres_query_launcher { - - - use tokio_postgres::types::ToSql; + use super::*; + use crate::rows::{FromSql, FromSqlOwnedValue}; + use tokio_postgres::types::ToSql; #[inline(always)] pub(crate) async fn query_rows<'a>( @@ -62,7 +73,10 @@ pub(crate) mod postgres_query_launcher { params: &[&'a dyn QueryParameter<'a>], conn: &PostgreSqlConnection, ) -> Result> { - let m_params: Vec<_> = params.iter().map(|param| param.as_postgres_param()).collect(); + let m_params: Vec<_> = params + .iter() + .map(|param| param.as_postgres_param()) + .collect(); let r = conn.client.query(stmt, m_params.as_slice()).await?; Ok(CanyonRows::Postgres(r)) } @@ -73,11 +87,28 @@ pub(crate) mod postgres_query_launcher { params: &[&'a dyn QueryParameter<'a>], conn: &PostgreSqlConnection, ) -> Result, Box<(dyn Error + Sync + Send)>> { - let m_params: Vec<_> = params.iter().map(|param| param.as_postgres_param()).collect(); + let m_params: Vec<_> = params + .iter() + .map(|param| param.as_postgres_param()) + .collect(); let r = conn.client.query_one(stmt, m_params.as_slice()).await?; Ok(Some(T::deserialize_postgresql(&r))) } + #[inline(always)] + pub(crate) async fn query_one_for<'a, T: FromSqlOwnedValue>( + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + conn: &PostgreSqlConnection, + ) -> Result> { + let m_params: Vec<_> = params + .iter() + .map(|param| param.as_postgres_param()) + .collect(); + let r = conn.client.query_one(stmt, m_params.as_slice()).await?; + r.try_get::(0).map_err(From::from) + } + #[inline(always)] pub(crate) async fn query<'a, S, R: RowMapper>( stmt: S, @@ -85,20 +116,18 @@ pub(crate) mod postgres_query_launcher { conn: &PostgreSqlConnection, ) -> Result, Box<(dyn Error + Sync + Send)>> where - S: AsRef + Display + Send + S: AsRef + Display + Send, { - Ok(conn.client + Ok(conn + .client .query(stmt.as_ref(), &get_psql_params(params)) .await? .iter() - .map(|row| { R::deserialize_postgresql(row) }) - .collect() - ) + .map(|row| R::deserialize_postgresql(row)) + .collect()) } - - fn get_psql_params<'a>(params: &[&'a (dyn QueryParameter<'_>)],) - -> Vec<&'a (dyn ToSql + Sync)> - { + + fn get_psql_params<'a>(params: &[&'a (dyn QueryParameter<'_>)]) -> Vec<&'a (dyn ToSql + Sync)> { params .as_ref() .iter() diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index 02bb9d5d..0990f703 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -6,14 +6,16 @@ use crate::connection::db_clients::mssql::SqlServerConnection; use crate::connection::db_clients::mysql::MysqlConnection; #[cfg(feature = "postgres")] use crate::connection::db_clients::postgresql::PostgreSqlConnection; +use crate::connection::db_connector::connection_helpers::{ + db_conn_launch_impl, db_conn_query_one_impl, +}; use crate::connection::{find_datasource_by_name_or_try_default, get_database_connection_by_ds}; +use crate::mapper::RowMapper; use crate::query_parameters::QueryParameter; -use crate::rows::CanyonRows; +use crate::rows::{CanyonRows, FromSql, FromSqlOwnedValue}; use std::error::Error; use std::fmt::Display; use std::future::Future; -use crate::connection::db_connector::connection_helpers::{db_conn_launch_impl, db_conn_query_one_impl}; -use crate::mapper::RowMapper; pub trait DbConnection { fn query_rows<'a>( @@ -36,6 +38,18 @@ pub trait DbConnection { params: &[&'a dyn QueryParameter<'a>], ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send; + /// Flexible and general method that queries the target database for a concrete instance + /// of some type T. + /// + /// This is useful on statements that won't be mapped to user types (impl RowMapper) but + /// there's a need for more flexibility on the return type. Ex: SELECT COUNT(*) from , + /// where there will be a single result of some numerical type + fn query_one_for<'a, T: FromSqlOwnedValue>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> impl Future>> + Send; + fn get_database_type(&self) -> Result>; } @@ -48,7 +62,7 @@ impl DbConnection for &str { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result>{ + ) -> Result> { let conn = get_database_connection_by_ds(Some(self)).await?; conn.query_rows(stmt, params).await } @@ -65,14 +79,26 @@ impl DbConnection for &str { conn.query(stmt, params).await } - async fn query_one<'a, R: RowMapper>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) - -> Result, Box<(dyn Error + Sync + Send)>> - { + async fn query_one<'a, R: RowMapper>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result, Box<(dyn Error + Sync + Send)>> { let sane_ds_name = if !self.is_empty() { Some(*self) } else { None }; let conn = get_database_connection_by_ds(sane_ds_name).await?; conn.query_one(stmt, params).await } + async fn query_one_for<'a, T: FromSqlOwnedValue>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + let sane_ds_name = if !self.is_empty() { Some(*self) } else { None }; + let conn = get_database_connection_by_ds(sane_ds_name).await?; + conn.query_one_for(stmt, params).await + } + fn get_database_type(&self) -> Result> { Ok(find_datasource_by_name_or_try_default(Some(*self))?.get_db_type()) } @@ -121,12 +147,31 @@ impl DbConnection for DatabaseConnection { } } - async fn query_one<'a, R: RowMapper>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) - -> Result, Box<(dyn Error + Sync + Send)>> - { + async fn query_one<'a, R: RowMapper>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result, Box<(dyn Error + Sync + Send)>> { db_conn_query_one_impl(self, stmt, params).await } + async fn query_one_for<'a, T: FromSqlOwnedValue>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + match self { + #[cfg(feature = "postgres")] + DatabaseConnection::Postgres(client) => client.query_one_for(stmt, params).await, + + #[cfg(feature = "mssql")] + DatabaseConnection::SqlServer(client) => client.query_one_for(stmt, params).await, + + #[cfg(feature = "mysql")] + DatabaseConnection::MySQL(client) => client.query_one_for(stmt, params).await, + } + } + fn get_database_type(&self) -> Result> { Ok(self.get_db_type()) } @@ -160,12 +205,31 @@ impl DbConnection for &mut DatabaseConnection { } } - async fn query_one<'a, R: RowMapper>(&self, stmt: &str, params: &[&'a dyn QueryParameter<'a>]) - -> Result, Box<(dyn Error + Sync + Send)>> - { + async fn query_one<'a, R: RowMapper>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result, Box<(dyn Error + Sync + Send)>> { db_conn_query_one_impl(self, stmt, params).await } + async fn query_one_for<'a, T: FromSqlOwnedValue>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + match self { + #[cfg(feature = "postgres")] + DatabaseConnection::Postgres(client) => client.query_one_for(stmt, params).await, + + #[cfg(feature = "mssql")] + DatabaseConnection::SqlServer(client) => client.query_one_for(stmt, params).await, + + #[cfg(feature = "mysql")] + DatabaseConnection::MySQL(client) => client.query_one_for(stmt, params).await, + } + } + fn get_database_type(&self) -> Result> { Ok(self.get_db_type()) } @@ -236,7 +300,6 @@ impl DatabaseConnection { mod connection_helpers { use super::*; use tokio_postgres::NoTls; - #[cfg(feature = "postgres")] pub async fn create_postgres_connection( @@ -319,7 +382,11 @@ mod connection_helpers { ) } - pub(crate) async fn db_conn_launch_impl<'a>(c: &DatabaseConnection, stmt: &str, params: &[&'a (dyn QueryParameter<'a> + 'a)]) -> Result>{ + pub(crate) async fn db_conn_launch_impl<'a>( + c: &DatabaseConnection, + stmt: &str, + params: &[&'a (dyn QueryParameter<'a> + 'a)], + ) -> Result> { match c { #[cfg(feature = "postgres")] DatabaseConnection::Postgres(client) => client.query_rows(stmt, params).await, @@ -332,11 +399,11 @@ mod connection_helpers { } } - pub(crate) async fn db_conn_query_one_impl<'a, T: RowMapper>(c: &DatabaseConnection, - stmt: &str, - params: &[&'a (dyn QueryParameter<'a> + 'a)]) - -> Result, Box> - { + pub(crate) async fn db_conn_query_one_impl<'a, T: RowMapper>( + c: &DatabaseConnection, + stmt: &str, + params: &[&'a (dyn QueryParameter<'a> + 'a)], + ) -> Result, Box> { match c { #[cfg(feature = "postgres")] DatabaseConnection::Postgres(client) => client.query_one(stmt, params).await, diff --git a/canyon_core/src/rows.rs b/canyon_core/src/rows.rs index 3342264f..b2e84f02 100644 --- a/canyon_core/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -30,6 +30,15 @@ cfg_if! { + tiberius::FromSql<'a> + mysql_async::prelude::FromValue {} + + pub trait FromSqlOwnedValue: tokio_postgres::types::FromSqlOwned + + tiberius::FromSqlOwned + + mysql_async::prelude::FromValue {} + impl FromSqlOwnedValue for T where T: + tokio_postgres::types::FromSqlOwned + + tiberius::FromSqlOwned + + mysql_async::prelude::FromValue + {} } // TODO: missing combinations else } diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index ce752ac5..b681b960 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -1,8 +1,9 @@ use crate::connection::db_connector::DbConnection; +use crate::mapper::RowMapper; +use crate::rows::FromSqlOwnedValue; use crate::{query_parameters::QueryParameter, rows::CanyonRows}; use std::error::Error; use std::{fmt::Display, future::Future}; -use crate::mapper::RowMapper; pub trait Transaction { fn query<'a, S, R: RowMapper>( @@ -10,7 +11,8 @@ pub trait Transaction { params: &[&'a (dyn QueryParameter<'a>)], input: impl DbConnection + Send, ) -> impl Future, Box<(dyn Error + Sync + Send)>>> - where S: AsRef + Display + Send, + where + S: AsRef + Display + Send, { async move { input.query(stmt, params).await } } @@ -27,6 +29,18 @@ pub trait Transaction { async move { input.query_one(stmt.as_ref(), params.as_ref()).await } } + fn query_one_for<'a, S, Z, F: FromSqlOwnedValue>( + stmt: S, + params: Z, + input: impl DbConnection + Send + 'a, + ) -> impl Future>> + Send + where + S: AsRef + Display + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send + 'a, + { + async move { input.query_one_for(stmt.as_ref(), params.as_ref()).await } + } + /// Performs a query against the targeted database by the selected or /// the defaulted datasource, wrapping the resultant collection of entities /// in [`super::rows::CanyonRows`] diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index f4977394..b4d7186a 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -27,92 +27,92 @@ where T: CrudOperations + RowMapper, { fn find_all() -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send; - + fn find_all_with<'a, I>( input: I, ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send where I: DbConnection + Send + 'a; - + fn find_all_unchecked() -> impl Future> + Send; - + fn find_all_unchecked_with<'a, I>(input: I) -> impl Future> + Send where I: DbConnection + Send + 'a; - - // fn select_query<'a>() -> SelectQueryBuilder<'a, T, &'a str>; - // - // fn select_query_with<'a, I>(input: I) -> SelectQueryBuilder<'a, T, I> - // where - // I: DbConnection + Send + 'a; - // - // fn count() -> impl Future>> + Send; - // - // fn count_with<'a, I>( - // input: I, - // ) -> impl Future>> + Send - // where - // I: DbConnection + Send + 'a; - // - // fn find_by_pk<'a>( - // value: &'a dyn QueryParameter<'a>, - // ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send; - // - // fn find_by_pk_with<'a, I>( - // value: &'a dyn QueryParameter<'a>, - // input: I, - // ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send - // where - // I: DbConnection + Send + 'a; - // + + fn select_query<'a>() -> SelectQueryBuilder<'a, T, &'a str>; + + fn select_query_with<'a, I>(input: I) -> SelectQueryBuilder<'a, T, I> + where + I: DbConnection + Send + 'a; + + fn count() -> impl Future>> + Send; + + fn count_with<'a, I>( + input: I, + ) -> impl Future>> + Send + where + I: DbConnection + Send + 'a; + + fn find_by_pk<'a>( + value: &'a dyn QueryParameter<'a>, + ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send; + + fn find_by_pk_with<'a, I>( + value: &'a dyn QueryParameter<'a>, + input: I, + ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send + where + I: DbConnection + Send + 'a; + // fn insert<'a>( // &'a mut self, // ) -> impl Future>> + Send; - // + // // fn insert_with<'a, I>( // &mut self, // input: I, // ) -> impl Future>> + Send // where // I: DbConnection + Send + 'a; - // + // // fn multi_insert<'a>( // instances: &'a mut [&'a mut T], // ) -> impl Future>> + Send; - // + // // fn multi_insert_with<'a, I>( // instances: &'a mut [&'a mut T], // input: I, // ) -> impl Future>> + Send // where // I: DbConnection + Send + 'a; - // + // // fn update(&self) -> impl Future>> + Send; - // + // // fn update_with<'a, I>( // &self, // input: I, // ) -> impl Future>> + Send // where // I: DbConnection + Send + 'a; - // + // // fn update_query<'a>() -> UpdateQueryBuilder<'a, T, &'a str>; - // + // // fn update_query_with<'a, I>(input: I) -> UpdateQueryBuilder<'a, T, I> // where // I: DbConnection + Send + 'a; - // + // // fn delete(&self) -> impl Future>> + Send; - // + // // fn delete_with<'a, I>( // &self, // input: I, // ) -> impl Future>> + Send // where // I: DbConnection + Send + 'a; - // + // // fn delete_query<'a>() -> DeleteQueryBuilder<'a, T, &'a str>; - // + // // fn delete_query_with<'a, I>(input: I) -> DeleteQueryBuilder<'a, T, I> // where // I: DbConnection + Send + 'a; diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index 263c72f5..85de4c09 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -130,6 +130,7 @@ fn handle_stupid_tiberius_sql_conversions(target_type: &str, ident_name: &str) - }; quote! { + // TODO: try_get row.get::<#deserializing_type, &str>(#ident_name) #handle_opt #to_owned diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index f5e4ec08..a070359a 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -1,6 +1,25 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; +#[derive(Debug, Copy, Clone)] +pub enum TransactionMethod { + Query, + QueryOne, + QueryOneFor, + QueryRows, +} + +impl ToTokens for TransactionMethod { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + TransactionMethod::Query => tokens.extend(quote! {query}), + TransactionMethod::QueryOne => tokens.extend(quote! {query_one}), + TransactionMethod::QueryOneFor => tokens.extend(quote! {query_one_for}), + TransactionMethod::QueryRows => tokens.extend(quote! {query_rows}), + } + } +} + pub struct MacroOperationBuilder { fn_name: Option, user_type: Option, @@ -16,6 +35,7 @@ pub struct MacroOperationBuilder { query_string: Option, input_parameters: Option, forwarded_parameters: Option, + transaction_method: TransactionMethod, single_result: bool, with_unwrap: bool, with_no_result_value: bool, // Ok(()) @@ -50,6 +70,7 @@ impl MacroOperationBuilder { query_string: None, input_parameters: None, forwarded_parameters: None, + transaction_method: TransactionMethod::Query, single_result: false, with_unwrap: false, with_no_result_value: false, @@ -255,6 +276,18 @@ impl MacroOperationBuilder { } } + pub fn with_transaction_method(mut self, transaction_method: TransactionMethod) -> Self { + self.transaction_method = transaction_method; + match &self.transaction_method { + TransactionMethod::QueryOne => self.single_result(), + _ => self, + } + } + + fn get_transaction_method(&self) -> TransactionMethod { + self.transaction_method + } + fn get_unwrap(&self) -> TokenStream { if self.with_unwrap { quote! { .unwrap() } @@ -320,10 +353,11 @@ impl MacroOperationBuilder { let forwarded_parameters = self.get_forwarded_parameters(); let return_type = self.get_return_type(); let where_clause = self.get_where_clause_bounds(); + let transaction_method = self.get_transaction_method(); let unwrap = self.get_unwrap(); let mut base_body_tokens = quote! { - <#ty as canyon_sql::core::Transaction<#ty>>::query( + <#ty as canyon_sql::core::Transaction<#ty>>::#transaction_method( #query_string, #forwarded_parameters, #input_fwd_arg diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index dd95eaa6..598ea305 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -34,13 +34,13 @@ pub fn generate_read_operations_tokens( #find_all_with #find_all_unchecked #find_all_unchecked_with - // - // #count - // #count_with - // - // #find_by_pk_complex_tokens - // - // #read_querybuilder_ops + + #count + #count_with + + #find_by_pk_complex_tokens + + #read_querybuilder_ops } } @@ -126,16 +126,16 @@ fn generate_find_by_pk_tokens( }; } - // TODO: this can be functionally handled, instead of this impl - let result_handling = quote! { - n if n.len() == 0 => Ok(None), - _ => Ok( - Some(transaction_result.into_results::<#ty>().remove(0)) - ) - }; + // // TODO: this can be functionally handled, instead of this impl + // let result_handling = quote! { + // n if n.len() == 0 => Ok(None), + // _ => Ok( + // Some(transaction_result.into_results::<#ty>().remove(0)) + // ) + // }; - let find_by_pk = create_find_by_pk_macro(ty, &stmt, &result_handling); - let find_by_pk_with = create_find_by_pk_with(ty, &stmt, &result_handling); + let find_by_pk = create_find_by_pk_macro(ty, &stmt); + let find_by_pk_with = create_find_by_pk_with(ty, &stmt); quote! { #find_by_pk @@ -204,10 +204,15 @@ mod __details { } pub mod count_generators { - use proc_macro2::TokenStream; - use super::*; + use crate::query_operations::macro_template::TransactionMethod; + use proc_macro2::TokenStream; + // NOTE: We can't use the QueryOneFor here due that the Tiberius `.get::(0)` for + // some reason returns an I32(Some(v)), instead of I64, so we need to manually mapped the wrapped + // type as i64. Also, we don't have in the count macro datasource info to match it by database, + // so isn't worth to refactor for the other two drivers and then do some dirty magic on mssql, + // since we don't distinguish them as the returned type isn't a CanyonRows wrapped one fn generate_count_manual_result_handling(ty: &Ident) -> TokenStream { let ty_str = ty.to_string(); @@ -227,8 +232,6 @@ mod __details { canyon_sql::core::CanyonRows::MySQL(mut v) => v.remove(0) .get::(0) .ok_or(format!("Failure in the COUNT query for MYSQL for: {}", #ty_str).into()), - - _ => panic!() // TODO remove when the generics will be refactored } } @@ -244,6 +247,7 @@ mod __details { ) .add_doc_comment("Executed with the default datasource") .query_string(stmt) + .with_transaction_method(TransactionMethod::QueryRows) .transaction_as_variable(quote! { match transaction_result { // NOTE: dark magic. Should be refactored #result_handling @@ -266,8 +270,9 @@ mod __details { ) .add_doc_comment("It will be executed with the specified datasource") .query_string(stmt) + .with_transaction_method(TransactionMethod::QueryRows) .transaction_as_variable(quote! { - match transaction_result { + match transaction_result { // NOTE: dark magic. Should be refactored #result_handling } }) @@ -277,15 +282,11 @@ mod __details { } pub mod pk_generators { - use proc_macro2::TokenStream; - use super::*; + use crate::query_operations::macro_template::TransactionMethod; + use proc_macro2::TokenStream; - pub fn create_find_by_pk_macro( - ty: &Ident, - stmt: &str, - result_handling: &TokenStream, - ) -> MacroOperationBuilder { + pub fn create_find_by_pk_macro(ty: &Ident, stmt: &str) -> MacroOperationBuilder { MacroOperationBuilder::new() .fn_name("find_by_pk") .with_lifetime() @@ -296,20 +297,10 @@ mod __details { .query_string(stmt) .input_parameters(quote! { value: &'a dyn canyon_sql::core::QueryParameter<'a> }) .forwarded_parameters(quote! { vec![value] }) - .propagate_transaction_result() - .single_result() - .transaction_as_variable(quote! { - match transaction_result { // NOTE: dark magic. Should be refactored - #result_handling - } - }) + .with_transaction_method(TransactionMethod::QueryOne) } - pub fn create_find_by_pk_with( - ty: &Ident, - stmt: &str, - result_handling: &TokenStream, - ) -> MacroOperationBuilder { + pub fn create_find_by_pk_with(ty: &Ident, stmt: &str) -> MacroOperationBuilder { MacroOperationBuilder::new() .fn_name("find_by_pk_with") .with_input_param() @@ -320,13 +311,7 @@ mod __details { .query_string(stmt) .input_parameters(quote! { value: &'a dyn canyon_sql::core::QueryParameter<'a> }) .forwarded_parameters(quote! { vec![value] }) - .propagate_transaction_result() - .single_result() - .transaction_as_variable(quote! { - match transaction_result { // NOTE: dark magic. Should be refactored - #result_handling - } - }) + .with_transaction_method(TransactionMethod::QueryOne) } } } @@ -423,8 +408,7 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_find_by_pk() { let find_by_pk_builder = create_find_by_pk_macro( &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - FIND_BY_PK_STMT, - "e! {}, + FIND_BY_PK_STMT ); let find_by_pk = find_by_pk_builder.generate_tokens().to_string(); @@ -437,8 +421,7 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_find_by_pk_with() { let find_by_pk_with_builder = create_find_by_pk_with( &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - FIND_BY_PK_STMT, - "e! {}, + FIND_BY_PK_STMT ); let find_by_pk_with = find_by_pk_with_builder.generate_tokens().to_string(); diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index 7aa9312d..e713913f 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -1,226 +1,226 @@ -// #[cfg(feature = "mysql")] -// use crate::constants::MYSQL_DS; -// #[cfg(feature = "mssql")] -// use crate::constants::SQL_SERVER_DS; -// -// /// Tests for the QueryBuilder available operations within Canyon. -// /// -// /// QueryBuilder are the way of obtain more flexibility that with -// /// the default generated queries, essentially for build the queries -// /// with the SQL filters -// /// -// use canyon_sql::{ -// crud::CrudOperations, -// query::{operators::Comp, operators::Like, ops::QueryBuilder}, -// }; -// -// use crate::tests_models::league::*; -// use crate::tests_models::player::*; -// use crate::tests_models::tournament::*; -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_generated_sql_by_the_select_querybuilder() { -// let select_with_joins = League::select_query() -// .inner_join("tournament", "league.id", "tournament.league_id") -// .left_join("team", "tournament.id", "player.tournament_id") -// .r#where(LeagueFieldValue::id(&7), Comp::Gt) -// .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) -// .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); -// // .query() -// // .await; -// // NOTE: We don't have in the docker the generated relationships -// // with the joins, so for now, we are just going to check that the -// // generated SQL by the SelectQueryBuilder is the expected -// assert_eq!( -// select_with_joins.read_sql(), -// "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" -// ) -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder() { -// // Find all the leagues with ID less or equals that 7 -// // and where it's region column value is equals to 'Korea' -// let filtered_leagues_result: Result, _> = League::select_query() -// .r#where(LeagueFieldValue::id(&50), Comp::LtEq) -// .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) -// .query() -// .await; -// -// let filtered_leagues: Vec = filtered_leagues_result.unwrap(); -// assert!(!filtered_leagues.is_empty()); -// -// let league_idx_0 = filtered_leagues.first().unwrap(); -// assert_eq!(league_idx_0.id, 34); -// assert_eq!(league_idx_0.region, "KOREA"); -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_fulllike() { -// // Find all the leagues with "LC" in their name -// let filtered_leagues_result = -// League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" -// ) -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_fulllike_with_mssql() { -// // Find all the leagues with "LC" in their name -// let filtered_leagues_result = -// League::select_query_with(SQL_SERVER_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" -// ) -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_fulllike_with_mysql() { -// // Find all the leagues with "LC" in their name -// let filtered_leagues_result = -// League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR) ,'%')" -// ) -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_leftlike() { -// // Find all the leagues whose name ends with "CK" -// let filtered_leagues_result = -// League::select_query().r#where(LeagueFieldValue::name(&"CK"), Like::Left); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" -// ) -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_leftlike_with_mssql() { -// // Find all the leagues whose name ends with "CK" -// let filtered_leagues_result = -// League::select_query().r#where(LeagueFieldValue::name(&"CK"), Like::Left); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" -// ) -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_leftlike_with_mysql() { -// // Find all the leagues whose name ends with "CK" -// let filtered_leagues_result = -// League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"CK"), Like::Left); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR))" -// ) -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_rightlike() { -// // Find all the leagues whose name starts with "LC" -// let filtered_leagues_result = -// League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Right); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" -// ) -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_rightlike_with_mssql() { -// // Find all the leagues whose name starts with "LC" -// let filtered_leagues_result = League::select_query_with(SQL_SERVER_DS) -// .r#where(LeagueFieldValue::name(&"LC"), Like::Right); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" -// ) -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_rightlike_with_mysql() { -// // Find all the leagues whose name starts with "LC" -// let filtered_leagues_result = -// League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Right); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS CHAR) ,'%')" -// ) -// } -// -// /// Same than the above but with the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_with_mssql() { -// // Find all the players where its ID column value is greater than 50 -// let filtered_find_players = Player::select_query_with(SQL_SERVER_DS) -// .r#where(PlayerFieldValue::id(&50), Comp::Gt) -// .query() -// .await; -// -// assert!(!filtered_find_players.unwrap().is_empty()); -// } -// -// /// Same than the above but with the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_with_mysql() { -// // Find all the players where its ID column value is greater than 50 -// let filtered_find_players = Player::select_query_with(MYSQL_DS) -// .r#where(PlayerFieldValue::id(&50), Comp::Gt) -// .query() -// .await; -// -// assert!(!filtered_find_players.unwrap().is_empty()); -// } -// +#[cfg(feature = "mysql")] +use crate::constants::MYSQL_DS; +#[cfg(feature = "mssql")] +use crate::constants::SQL_SERVER_DS; + +/// Tests for the QueryBuilder available operations within Canyon. +/// +/// QueryBuilder are the way of obtain more flexibility that with +/// the default generated queries, essentially for build the queries +/// with the SQL filters +/// +use canyon_sql::{ + crud::CrudOperations, + query::{operators::Comp, operators::Like, ops::QueryBuilder}, +}; + +use crate::tests_models::league::*; +use crate::tests_models::player::*; +use crate::tests_models::tournament::*; + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[canyon_sql::macros::canyon_tokio_test] +fn test_generated_sql_by_the_select_querybuilder() { + let select_with_joins = League::select_query() + .inner_join("tournament", "league.id", "tournament.league_id") + .left_join("team", "tournament.id", "player.tournament_id") + .r#where(LeagueFieldValue::id(&7), Comp::Gt) + .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) + .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); + // .query() + // .await; + // NOTE: We don't have in the docker the generated relationships + // with the joins, so for now, we are just going to check that the + // generated SQL by the SelectQueryBuilder is the expected + assert_eq!( + select_with_joins.read_sql(), + "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder() { + // Find all the leagues with ID less or equals that 7 + // and where it's region column value is equals to 'Korea' + let filtered_leagues_result: Result, _> = League::select_query() + .r#where(LeagueFieldValue::id(&50), Comp::LtEq) + .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) + .query() + .await; + + let filtered_leagues: Vec = filtered_leagues_result.unwrap(); + assert!(!filtered_leagues.is_empty()); + + let league_idx_0 = filtered_leagues.first().unwrap(); + assert_eq!(league_idx_0.id, 34); + assert_eq!(league_idx_0.region, "KOREA"); +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_fulllike() { + // Find all the leagues with "LC" in their name + let filtered_leagues_result = + League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Full); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_fulllike_with_mssql() { + // Find all the leagues with "LC" in their name + let filtered_leagues_result = + League::select_query_with(SQL_SERVER_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Full); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_fulllike_with_mysql() { + // Find all the leagues with "LC" in their name + let filtered_leagues_result = + League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Full); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_leftlike() { + // Find all the leagues whose name ends with "CK" + let filtered_leagues_result = + League::select_query().r#where(LeagueFieldValue::name(&"CK"), Like::Left); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_leftlike_with_mssql() { + // Find all the leagues whose name ends with "CK" + let filtered_leagues_result = + League::select_query().r#where(LeagueFieldValue::name(&"CK"), Like::Left); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_leftlike_with_mysql() { + // Find all the leagues whose name ends with "CK" + let filtered_leagues_result = + League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"CK"), Like::Left); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR))" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_rightlike() { + // Find all the leagues whose name starts with "LC" + let filtered_leagues_result = + League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Right); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_rightlike_with_mssql() { + // Find all the leagues whose name starts with "LC" + let filtered_leagues_result = League::select_query_with(SQL_SERVER_DS) + .r#where(LeagueFieldValue::name(&"LC"), Like::Right); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_rightlike_with_mysql() { + // Find all the leagues whose name starts with "LC" + let filtered_leagues_result = + League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Right); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS CHAR) ,'%')" + ) +} + +/// Same than the above but with the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_with_mssql() { + // Find all the players where its ID column value is greater than 50 + let filtered_find_players = Player::select_query_with(SQL_SERVER_DS) + .r#where(PlayerFieldValue::id(&50), Comp::Gt) + .query() + .await; + + assert!(!filtered_find_players.unwrap().is_empty()); +} + +/// Same than the above but with the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_with_mysql() { + // Find all the players where its ID column value is greater than 50 + let filtered_find_players = Player::select_query_with(MYSQL_DS) + .r#where(PlayerFieldValue::id(&50), Comp::Gt) + .query() + .await; + + assert!(!filtered_find_players.unwrap().is_empty()); +} +// // /// Updates the values of the range on entries defined by the constraint parameters // /// in the database entity // #[cfg(feature = "postgres")] @@ -235,7 +235,7 @@ // ]) // .r#where(LeagueFieldValue::id(&1), Comp::Gt) // .and(LeagueFieldValue::id(&8), Comp::Lt); -// +// // /* NOTE: Family of QueryBuilders are clone, useful in case of need to read the generated SQL // let qpr = q.clone(); // println!("PSQL: {:?}", qpr.read_sql()); @@ -243,19 +243,19 @@ // q.query() // .await // .expect("Failed to update records with the querybuilder"); -// +// // let found_updated_values = League::select_query() // .r#where(LeagueFieldValue::id(&1), Comp::Gt) // .and(LeagueFieldValue::id(&7), Comp::Lt) // .query() // .await // .expect("Failed to retrieve database League entries with the querybuilder"); -// +// // found_updated_values // .iter() // .for_each(|league| assert_eq!(league.slug, "Updated with the QueryBuilder")); // } -// +// // /// Same as above, but with the specified datasource // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] @@ -272,27 +272,27 @@ // .query() // .await // .expect("Failed to update records with the querybuilder"); -// +// // let found_updated_values = Player::select_query_with(SQL_SERVER_DS) // .r#where(PlayerFieldValue::id(&1), Comp::Gt) // .and(PlayerFieldValue::id(&7), Comp::LtEq) // .query() // .await // .expect("Failed to retrieve database League entries with the querybuilder"); -// +// // found_updated_values.iter().for_each(|player| { // assert_eq!(player.summoner_name, "Random updated player name"); // assert_eq!(player.first_name, "I am an updated first name"); // }); // } -// +// // /// Same as above, but with the specified datasource // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] // fn test_crud_update_with_querybuilder_with_mysql() { // // Find all the leagues with ID less or equals that 7 // // and where it's region column value is equals to 'Korea' -// +// // let q = Player::update_query_with(MYSQL_DS); // q.set(&[ // (PlayerField::summoner_name, "Random updated player name"), @@ -303,20 +303,20 @@ // .query() // .await // .expect("Failed to update records with the querybuilder"); -// +// // let found_updated_values = Player::select_query_with(MYSQL_DS) // .r#where(PlayerFieldValue::id(&1), Comp::Gt) // .and(PlayerFieldValue::id(&7), Comp::LtEq) // .query() // .await // .expect("Failed to retrieve database League entries with the querybuilder"); -// +// // found_updated_values.iter().for_each(|player| { // assert_eq!(player.summoner_name, "Random updated player name"); // assert_eq!(player.first_name, "I am an updated first name"); // }); // } -// +// // /// Deletes entries from the mapped entity `T` that are in the ranges filtered // /// with the QueryBuilder // /// @@ -332,10 +332,10 @@ // .query() // .await // .expect("Error connecting with the database on the delete operation"); -// +// // assert_eq!(Tournament::find_by_pk(&15).await.unwrap(), None); // } -// +// // /// Same as the above delete, but with the specified datasource // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] @@ -346,7 +346,7 @@ // .query() // .await // .expect("Error connecting with the database when we are going to delete data! :)"); -// +// // assert!(Player::select_query_with(SQL_SERVER_DS) // .r#where(PlayerFieldValue::id(&122), Comp::Eq) // .query() @@ -354,7 +354,7 @@ // .unwrap() // .is_empty()); // } -// +// // /// Same as the above delete, but with the specified datasource // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] @@ -365,7 +365,7 @@ // .query() // .await // .expect("Error connecting with the database when we are going to delete data! :)"); -// +// // assert!(Player::select_query_with(MYSQL_DS) // .r#where(PlayerFieldValue::id(&122), Comp::Eq) // .query() @@ -373,16 +373,16 @@ // .unwrap() // .is_empty()); // } -// +// // /// Tests for the generated SQL query after use the // /// WHERE clause // #[canyon_sql::macros::canyon_tokio_test] // fn test_where_clause() { // let l = League::select_query().r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq); -// +// // assert_eq!(l.read_sql(), "SELECT * FROM league WHERE name = $1") // } -// +// // /// Tests for the generated SQL query after use the // /// AND clause // #[canyon_sql::macros::canyon_tokio_test] @@ -390,13 +390,13 @@ // let l = League::select_query() // .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) // .and(LeagueFieldValue::id(&10), Comp::LtEq); -// +// // assert_eq!( // l.read_sql().trim(), // "SELECT * FROM league WHERE name = $1 AND id <= $2" // ) // } -// +// // /// Tests for the generated SQL query after use the // /// AND clause // #[canyon_sql::macros::canyon_tokio_test] @@ -404,13 +404,13 @@ // let l = League::select_query() // .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) // .and_values_in(LeagueField::id, &[1, 7, 10]); -// +// // assert_eq!( // l.read_sql().trim(), // "SELECT * FROM league WHERE name = $1 AND id IN ($1, $2, $3)" // ) // } -// +// // /// Tests for the generated SQL query after use the // /// AND clause // #[canyon_sql::macros::canyon_tokio_test] @@ -418,13 +418,13 @@ // let l = League::select_query() // .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) // .or(LeagueFieldValue::id(&10), Comp::LtEq); -// +// // assert_eq!( // l.read_sql().trim(), // "SELECT * FROM league WHERE name = $1 OR id <= $2" // ) // } -// +// // /// Tests for the generated SQL query after use the // /// AND clause // #[canyon_sql::macros::canyon_tokio_test] @@ -432,13 +432,13 @@ // let l = League::select_query() // .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) // .or_values_in(LeagueField::id, &[1, 7, 10]); -// +// // assert_eq!( // l.read_sql(), // "SELECT * FROM league WHERE name = $1 OR id IN ($1, $2, $3)" // ) // } -// +// // /// Tests for the generated SQL query after use the // /// AND clause // #[canyon_sql::macros::canyon_tokio_test] @@ -446,7 +446,7 @@ // let l = League::select_query() // .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) // .order_by(LeagueField::id, false); -// +// // assert_eq!( // l.read_sql(), // "SELECT * FROM league WHERE name = $1 ORDER BY id" diff --git a/tests/crud/read_operations.rs b/tests/crud/read_operations.rs index ee908f31..8c472a57 100644 --- a/tests/crud/read_operations.rs +++ b/tests/crud/read_operations.rs @@ -72,7 +72,7 @@ fn test_crud_find_all_with_mysql() { // let find_all_result: Vec = League::find_all_unchecked_with(SQL_SERVER_DS).await; // assert!(!find_all_result.is_empty()); // } -// +// // /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is // /// defined with the #[primary_key] attribute over some field of the type. // /// @@ -83,7 +83,7 @@ fn test_crud_find_all_with_mysql() { // let find_by_pk_result: Result, Box> = // League::find_by_pk(&1).await; // assert!(find_by_pk_result.as_ref().unwrap().is_some()); -// +// // let some_league = find_by_pk_result.unwrap().unwrap(); // assert_eq!(some_league.id, 1); // assert_eq!(some_league.ext_id, 100695891328981122_i64); @@ -95,7 +95,7 @@ fn test_crud_find_all_with_mysql() { // "http://static.lolesports.com/leagues/EM_Bug_Outline1.png" // ); // } -// +// // /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is // /// defined with the #[primary_key] attribute over some field of the type. // /// @@ -106,7 +106,7 @@ fn test_crud_find_all_with_mysql() { // let find_by_pk_result: Result, Box> = // League::find_by_pk_with(&27, SQL_SERVER_DS).await; // assert!(find_by_pk_result.as_ref().unwrap().is_some()); -// +// // let some_league = find_by_pk_result.unwrap().unwrap(); // assert_eq!(some_league.id, 27); // assert_eq!(some_league.ext_id, 107898214974993351_i64); @@ -118,7 +118,7 @@ fn test_crud_find_all_with_mysql() { // "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" // ); // } -// +// // /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is // /// defined with the #[primary_key] attribute over some field of the type. // /// @@ -129,7 +129,7 @@ fn test_crud_find_all_with_mysql() { // let find_by_pk_result: Result, Box> = // League::find_by_pk_with(&27, MYSQL_DS).await; // assert!(find_by_pk_result.as_ref().unwrap().is_some()); -// +// // let some_league = find_by_pk_result.unwrap().unwrap(); // assert_eq!(some_league.id, 27); // assert_eq!(some_league.ext_id, 107898214974993351_i64); @@ -141,35 +141,35 @@ fn test_crud_find_all_with_mysql() { // "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" // ); // } -// -// /// Counts how many rows contains an entity on the target database. -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_count_operation() { -// assert_eq!( -// League::find_all().await.unwrap().len() as i64, -// League::count().await.unwrap() -// ); -// } -// -// /// Counts how many rows contains an entity on the target database using -// /// the specified datasource mssql -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_count_with_operation_mssql() { -// assert_eq!( -// League::find_all_with(SQL_SERVER_DS).await.unwrap().len() as i64, -// League::count_with(SQL_SERVER_DS).await.unwrap() -// ); -// } -// -// /// Counts how many rows contains an entity on the target database using -// /// the specified datasource mysql -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_count_with_operation_mysql() { -// assert_eq!( -// League::find_all_with(MYSQL_DS).await.unwrap().len() as i64, -// League::count_with(MYSQL_DS).await.unwrap() -// ); -// } + +/// Counts how many rows contains an entity on the target database. +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_count_operation() { + assert_eq!( + League::find_all().await.unwrap().len() as i64, + League::count().await.unwrap() + ); +} + +/// Counts how many rows contains an entity on the target database using +/// the specified datasource mssql +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_count_with_operation_mssql() { + assert_eq!( + League::find_all_with(SQL_SERVER_DS).await.unwrap().len() as i64, + League::count_with(SQL_SERVER_DS).await.unwrap() + ); +} + +/// Counts how many rows contains an entity on the target database using +/// the specified datasource mysql +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_count_with_operation_mysql() { + assert_eq!( + League::find_all_with(MYSQL_DS).await.unwrap().len() as i64, + League::count_with(MYSQL_DS).await.unwrap() + ); +} diff --git a/tests/tests_models/player.rs b/tests/tests_models/player.rs index 56c34a3b..0cba50ec 100644 --- a/tests/tests_models/player.rs +++ b/tests/tests_models/player.rs @@ -1,24 +1,24 @@ -// use canyon_sql::macros::*; -// -// #[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] -// #[canyon_entity] -// /// Data model that represents a database entity for Players. -// /// -// /// For test the behaviour of Canyon with entities that no declares primary keys, -// /// or that is configuration isn't autoincremental, we will use this class. -// /// Note that this entity has a primary key declared in the database, but we will -// /// omit this in Canyon, so for us, is like if the primary key wasn't set up. -// /// -// /// Remember that the entities that does not declare at least a field as `#[primary_key]` -// /// does not have all the CRUD operations available, only the ones that doesn't -// /// require of a primary key. -// pub struct Player { -// // #[primary_key] We will omit this to use it as a mock of entities that doesn't declare primary key -// id: i32, -// ext_id: i64, -// first_name: String, -// last_name: String, -// summoner_name: String, -// image_url: Option, -// role: String, -// } +use canyon_sql::macros::*; + +#[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] +#[canyon_entity] +/// Data model that represents a database entity for Players. +/// +/// For test the behaviour of Canyon with entities that no declares primary keys, +/// or that is configuration isn't autoincremental, we will use this class. +/// Note that this entity has a primary key declared in the database, but we will +/// omit this in Canyon, so for us, is like if the primary key wasn't set up. +/// +/// Remember that the entities that does not declare at least a field as `#[primary_key]` +/// does not have all the CRUD operations available, only the ones that doesn't +/// require of a primary key. +pub struct Player { + // #[primary_key] We will omit this to use it as a mock of entities that doesn't declare primary key + id: i32, + ext_id: i64, + first_name: String, + last_name: String, + summoner_name: String, + image_url: Option, + role: String, +} From c2080c15c7f84572c1efb1d502c9730e769044ad Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 10 Feb 2025 16:59:08 +0100 Subject: [PATCH 071/155] feat(wip): re-enabling all the insert operations --- canyon_crud/src/crud.rs | 46 +- .../src/query_elements/query_builder.rs | 7 +- canyon_macros/src/query_operations/delete.rs | 4 +- canyon_macros/src/query_operations/insert.rs | 34 +- canyon_macros/src/query_operations/update.rs | 4 +- canyon_migrations/src/migrations/handler.rs | 8 +- tests/crud/delete_operations.rs | 30 +- tests/crud/foreign_key_operations.rs | 40 +- tests/crud/insert_operations.rs | 634 +++++++++--------- tests/crud/update_operations.rs | 40 +- tests/tests_models/tournament.rs | 2 +- 11 files changed, 429 insertions(+), 420 deletions(-) diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index b4d7186a..b4983c65 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -13,7 +13,7 @@ use std::future::Future; /// that the user has available, just by deriving the `CanyonCrud` /// derive macro when a struct contains the annotation. /// -/// Also, this traits needs that the type T over what it's generified +/// Also, these traits needs that the type T over what it's generified /// to implement certain types in order to work correctly. /// /// The most notorious one it's the [`RowMapper`] one, which allows @@ -65,28 +65,28 @@ where where I: DbConnection + Send + 'a; - // fn insert<'a>( - // &'a mut self, - // ) -> impl Future>> + Send; - // - // fn insert_with<'a, I>( - // &mut self, - // input: I, - // ) -> impl Future>> + Send - // where - // I: DbConnection + Send + 'a; - // - // fn multi_insert<'a>( - // instances: &'a mut [&'a mut T], - // ) -> impl Future>> + Send; - // - // fn multi_insert_with<'a, I>( - // instances: &'a mut [&'a mut T], - // input: I, - // ) -> impl Future>> + Send - // where - // I: DbConnection + Send + 'a; - // + fn insert<'a>( + &'a mut self, + ) -> impl Future>> + Send; + + fn insert_with<'a, I>( + &mut self, + input: I, + ) -> impl Future>> + Send + where + I: DbConnection + Send + 'a; + + fn multi_insert<'a>( + instances: &'a mut [&'a mut T], + ) -> impl Future>> + Send; + + fn multi_insert_with<'a, I>( + instances: &'a mut [&'a mut T], + input: I, + ) -> impl Future>> + Send + where + I: DbConnection + Send + 'a; + // fn update(&self) -> impl Future>> + Send; // // fn update_with<'a, I>( diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index b4221890..01cb7864 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -177,12 +177,7 @@ where ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> { self.query.sql.push(';'); - T::query( - &self.query.sql, - &self.query.params, - self.input, - ) - .await + T::query(&self.query.sql, &self.query.params, self.input).await } pub fn r#where>(&mut self, r#where: Z, op: impl Operator) { diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index adba376c..c1a50a0d 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -53,10 +53,10 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri // let delete_with_querybuilder = generate_delete_query_tokens(&ty, table_schema_data); // delete_ops_tokens.extend(delete_with_querybuilder); - // + // // delete_ops_tokens - quote!{} + quote! {} } /// Generates the TokenStream for the __delete() CRUD operation as a diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index f896a1a4..4afbc01c 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -40,6 +40,7 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri .fields_with_types() .into_iter() .find(|(i, _t)| Some(i.to_string()) == primary_key); + let insert_transaction = if let Some(pk_data) = &pk_ident_type { let pk_ident = &pk_data.0; let pk_type = &pk_data.1; @@ -49,18 +50,21 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let stmt = format!("{} RETURNING {}", #stmt , #primary_key); - self.#pk_ident = <#ty as canyon_sql::core::Transaction<#ty>>::query( + self.#pk_ident = <#ty as canyon_sql::core::Transaction<#ty>>::query_one_for::< + String, + Vec<&'_ dyn QueryParameter<'_>>, + #pk_type + >( stmt, values, input - ).await? - .get_column_at_row::<#pk_type>(#primary_key, 0)?; + ).await?; Ok(()) } } else { quote! { - <#ty as canyon_sql::core::Transaction<#ty>>::query( + <#ty as canyon_sql::core::Transaction<#ty>>::query_rows( // TODO: this should be execute #stmt, values, input @@ -166,11 +170,10 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri }); - // let multi_insert_tokens = generate_multiple_insert_tokens(macro_data, table_schema_data); - // insert_ops_tokens.extend(multi_insert_tokens); + let multi_insert_tokens = generate_multiple_insert_tokens(macro_data, table_schema_data); + insert_ops_tokens.extend(multi_insert_tokens); - // insert_ops_tokens - quote!{} + insert_ops_tokens } /// Generates the TokenStream for the __insert() CRUD operation, but being available @@ -215,9 +218,18 @@ fn generate_multiple_insert_tokens( let mut split = mapped_fields.split(", ") .collect::>(); + mapped_fields = #column_names + .split(", ") + .map( |column_name| format!("\"{}\"", column_name)) + .collect::>() + .join(", "); + + let mut split = mapped_fields.split(", ") + .collect::>(); + let pk_value_index = split.iter() .position(|pk| *pk == format!("\"{}\"", #pk).as_str()) - .expect("Error. No primary key found when should be there"); + .unwrap(); // ensured that is there split.retain(|pk| *pk != format!("\"{}\"", #pk).as_str()); mapped_fields = split.join(", ").to_string(); @@ -267,7 +279,7 @@ fn generate_multiple_insert_tokens( } } - let multi_insert_result = <#ty as canyon_sql::core::Transaction<#ty>>::query( + let multi_insert_result = <#ty as canyon_sql::core::Transaction<#ty>>::query_rows( stmt, v_arr, input @@ -366,7 +378,7 @@ fn generate_multiple_insert_tokens( } } - <#ty as canyon_sql::core::Transaction<#ty>>::query( + <#ty as canyon_sql::core::Transaction<#ty>>::query_rows( stmt, v_arr, input diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index ca568ec3..34bdfa9f 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -87,8 +87,8 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri update_ops_tokens.extend(querybuilder_update_tokens); update_ops_tokens; - - quote!{} + + quote! {} } /// Generates the TokenStream for the __update() CRUD operation diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index a3b3d65e..bb71b22d 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -105,9 +105,11 @@ impl Migrations { DatabaseType::MySQL => todo!("Not implemented fetch database in mysql"), }; - Self::query_rows(query, [], db_conn).await.unwrap_or_else(|_| { - panic!("Error querying the schema information for the datasource: {ds_name}") - }) + Self::query_rows(query, [], db_conn) + .await + .unwrap_or_else(|_| { + panic!("Error querying the schema information for the datasource: {ds_name}") + }) } /// Handler for parse the result of query the information of some database schema, diff --git a/tests/crud/delete_operations.rs b/tests/crud/delete_operations.rs index ed0dc70a..22ffacca 100644 --- a/tests/crud/delete_operations.rs +++ b/tests/crud/delete_operations.rs @@ -1,16 +1,16 @@ // //! Integration tests for the CRUD operations available in `Canyon` that // //! generates and executes *INSERT* statements // use canyon_sql::crud::CrudOperations; -// +// // #[cfg(feature = "mysql")] // use crate::constants::MYSQL_DS; // #[cfg(feature = "postgres")] // use crate::constants::PSQL_DS; // #[cfg(feature = "mssql")] // use crate::constants::SQL_SERVER_DS; -// +// // use crate::tests_models::league::*; -// +// // /// Deletes a row from the database that is mapped into some instance of a `T` entity. // /// // /// The `t.delete(&self)` operation is only enabled for types that @@ -33,10 +33,10 @@ // region: "Bahía de cochinos".to_string(), // image_url: "https://nobodyspectsandimage.io".to_string(), // }; -// +// // // We insert the instance on the database, on the `League` entity // new_league.insert().await.expect("Failed insert operation"); -// +// // assert_eq!( // new_league.id, // League::find_by_pk_with(&new_league.id, PSQL_DS) @@ -45,14 +45,14 @@ // .expect("None value") // .id // ); -// +// // // Now that we have an instance mapped to some entity by a primary key, we can now // // remove that entry from the database with the delete operation // new_league // .delete() // .await // .expect("Failed to delete the operation"); -// +// // // To check the success, we can query by the primary key value and check if, after unwrap() // // the result of the operation, the find by primary key contains Some(v) or None // // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> @@ -63,7 +63,7 @@ // None // ); // } -// +// // /// Same as the delete test, but performing the operations with the specified datasource // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] @@ -78,7 +78,7 @@ // region: "Bahía de cochinos".to_string(), // image_url: "https://nobodyspectsandimage.io".to_string(), // }; -// +// // // We insert the instance on the database, on the `League` entity // new_league // .insert_with(SQL_SERVER_DS) @@ -92,14 +92,14 @@ // .expect("None value") // .id // ); -// +// // // Now that we have an instance mapped to some entity by a primary key, we can now // // remove that entry from the database with the delete operation // new_league // .delete_with(SQL_SERVER_DS) // .await // .expect("Failed to delete the operation"); -// +// // // To check the success, we can query by the primary key value and check if, after unwrap() // // the result of the operation, the find by primary key contains Some(v) or None // // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> @@ -110,7 +110,7 @@ // None // ); // } -// +// // /// Same as the delete test, but performing the operations with the specified datasource // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] @@ -125,7 +125,7 @@ // region: "Bahía de cochinos".to_string(), // image_url: "https://nobodyspectsandimage.io".to_string(), // }; -// +// // // We insert the instance on the database, on the `League` entity // new_league // .insert_with(MYSQL_DS) @@ -139,14 +139,14 @@ // .expect("None value") // .id // ); -// +// // // Now that we have an instance mapped to some entity by a primary key, we can now // // remove that entry from the database with the delete operation // new_league // .delete_with(MYSQL_DS) // .await // .expect("Failed to delete the operation"); -// +// // // To check the success, we can query by the primary key value and check if, after unwrap() // // the result of the operation, the find by primary key contains Some(v) or None // // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> diff --git a/tests/crud/foreign_key_operations.rs b/tests/crud/foreign_key_operations.rs index 142c3883..e8544df2 100644 --- a/tests/crud/foreign_key_operations.rs +++ b/tests/crud/foreign_key_operations.rs @@ -9,15 +9,15 @@ // /// reverse side of the implementations. // /// For more info: TODO -> Link to the docs of the foreign key chapter // use canyon_sql::crud::CrudOperations; -// +// // #[cfg(feature = "mssql")] // use crate::constants::MYSQL_DS; // #[cfg(feature = "mssql")] // use crate::constants::SQL_SERVER_DS; -// +// // use crate::tests_models::league::*; // use crate::tests_models::tournament::*; -// +// // /// Given an entity `T` which has some field declaring a foreign key relation // /// with some another entity `U`, for example, performs a search to find // /// what is the parent type `U` of `T` @@ -28,20 +28,20 @@ // .await // .expect("Result variant of the query is err") // .expect("No result found for the given parameter"); -// +// // // We can get the parent entity for the retrieved child instance // let parent_entity: Option = some_tournament // .search_league() // .await // .expect("Result variant of the query is err"); -// +// // if let Some(league) = parent_entity { // assert_eq!(some_tournament.league, league.id) // } else { // assert_eq!(parent_entity, None) // } // } -// +// // /// Same as the search by foreign key, but with the specified datasource // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] @@ -50,13 +50,13 @@ // .await // .expect("Result variant of the query is err") // .expect("No result found for the given parameter"); -// +// // // We can get the parent entity for the retrieved child instance // let parent_entity: Option = some_tournament // .search_league_with(SQL_SERVER_DS) // .await // .expect("Result variant of the query is err"); -// +// // // These are tests, and we could unwrap the result contained in the option, because // // it always should exist that search for the data inserted when the docker starts. // // But, just for change the style a little bit and offer more options about how to @@ -67,7 +67,7 @@ // assert_eq!(parent_entity, None) // } // } -// +// // /// Same as the search by foreign key, but with the specified datasource // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] @@ -76,13 +76,13 @@ // .await // .expect("Result variant of the query is err") // .expect("No result found for the given parameter"); -// +// // // We can get the parent entity for the retrieved child instance // let parent_entity: Option = some_tournament // .search_league_with(MYSQL_DS) // .await // .expect("Result variant of the query is err"); -// +// // // These are tests, and we could unwrap the result contained in the option, because // // it always should exist that search for the data inserted when the docker starts. // // But, just for change the style a little bit and offer more options about how to @@ -93,7 +93,7 @@ // assert_eq!(parent_entity, None) // } // } -// +// // /// Given an entity `U` that is know as the "parent" side of the relation with another // /// entity `T`, for example, we can ask to the parent for the childrens that belongs // /// to `U`. @@ -106,18 +106,18 @@ // .await // .expect("Result variant of the query is err") // .expect("No result found for the given parameter"); -// +// // // Computes how many tournaments are pointing to the retrieved league // let child_tournaments: Vec = Tournament::search_league_childrens(&some_league) // .await // .expect("Result variant of the query is err"); -// +// // assert!(!child_tournaments.is_empty()); // child_tournaments // .iter() // .for_each(|t| assert_eq!(t.league, some_league.id)); // } -// +// // /// Same as the search by the reverse side of a foreign key relation // /// but with the specified datasource // #[cfg(feature = "mssql")] @@ -127,19 +127,19 @@ // .await // .expect("Result variant of the query is err") // .expect("No result found for the given parameter"); -// +// // // Computes how many tournaments are pointing to the retrieved league // let child_tournaments: Vec = // Tournament::search_league_childrens_with(&some_league, SQL_SERVER_DS) // .await // .expect("Result variant of the query is err"); -// +// // assert!(!child_tournaments.is_empty()); // child_tournaments // .iter() // .for_each(|t| assert_eq!(t.league, some_league.id)); // } -// +// // /// Same as the search by the reverse side of a foreign key relation // /// but with the specified datasource // #[cfg(feature = "mysql")] @@ -149,13 +149,13 @@ // .await // .expect("Result variant of the query is err") // .expect("No result found for the given parameter"); -// +// // // Computes how many tournaments are pointing to the retrieved league // let child_tournaments: Vec = // Tournament::search_league_childrens_with(&some_league, MYSQL_DS) // .await // .expect("Result variant of the query is err"); -// +// // assert!(!child_tournaments.is_empty()); // child_tournaments // .iter() diff --git a/tests/crud/insert_operations.rs b/tests/crud/insert_operations.rs index 9f92e118..4fb742ca 100644 --- a/tests/crud/insert_operations.rs +++ b/tests/crud/insert_operations.rs @@ -1,317 +1,317 @@ -// //! Integration tests for the CRUD operations available in `Canyon` that -// //! generates and executes *INSERT* statements -// use canyon_sql::crud::CrudOperations; -// -// #[cfg(feature = "mysql")] -// use crate::constants::MYSQL_DS; -// #[cfg(feature = "mssql")] -// use crate::constants::SQL_SERVER_DS; -// -// use crate::tests_models::league::*; -// -// /// Inserts a new record on the database, given an entity that is -// /// annotated with `#[canyon_entity]` macro over a *T* type. -// /// -// /// For insert a new record on a database, the *insert* operation needs -// /// some special requirements: -// /// > - We need a mutable instance of `T`. If the operation completes -// /// successfully, the insert operation will automatically set the autogenerated -// /// value for the `primary_key` annotated field in it. -// /// -// /// > - It's considered a good practice to initialize that concrete field with -// /// the `Default` trait, because the value on the primary key field will be -// /// ignored at the execution time of the insert, and updated with the autogenerated -// /// value by the database. -// /// -// /// By default, the `#[primary_key]` annotation means autogenerated and autoincremental. -// /// You can configure not autoincremental via macro annotation parameters (please, -// /// refer to the docs [here]() for more info.) -// /// -// /// If the type hasn't a `#[primary_key]` annotation, or the annotation contains -// /// an argument specifying not autoincremental behaviour, all the fields will be -// /// inserted on the database and no returning value will be placed in any field. -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_insert_operation() { -// let mut new_league: League = League { -// id: Default::default(), -// ext_id: 7892635306594_i64, -// slug: "some-new-league".to_string(), -// name: "Some New League".to_string(), -// region: "Bahía de cochinos".to_string(), -// image_url: "https://nobodyspectsandimage.io".to_string(), -// }; -// -// // We insert the instance on the database, on the `League` entity -// new_league.insert().await.expect("Failed insert operation"); -// -// // Now, in the `id` field of the instance, we have the autogenerated -// // value for the primary key field, which is id. So, we can query the -// // database again with the find by primary key operation to check if -// // the value was really inserted -// let inserted_league = League::find_by_pk(&new_league.id) -// .await -// .expect("Failed the query to the database") -// .expect("No entity found for the primary key value passed in"); -// -// assert_eq!(new_league.id, inserted_league.id); -// } -// -// /// Same as the insert operation above, but targeting the database defined in -// /// the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_insert_with_mssql_operation() { -// let mut new_league: League = League { -// id: Default::default(), -// ext_id: 7892635306594_i64, -// slug: "some-new-league".to_string(), -// name: "Some New League".to_string(), -// region: "Bahía de cochinos".to_string(), -// image_url: "https://nobodyspectsandimage.io".to_string(), -// }; -// -// // We insert the instance on the database, on the `League` entity -// new_league -// .insert_with(SQL_SERVER_DS) -// .await -// .expect("Failed insert datasource operation"); -// -// // Now, in the `id` field of the instance, we have the autogenerated -// // value for the primary key field, which is id. So, we can query the -// // database again with the find by primary key operation to check if -// // the value was really inserted -// let inserted_league = League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) -// .await -// .expect("Failed the query to the database") -// .expect("No entity found for the primary key value passed in"); -// -// assert_eq!(new_league.id, inserted_league.id); -// } -// -// /// Same as the insert operation above, but targeting the database defined in -// /// the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_insert_with_mysql_operation() { -// let mut new_league: League = League { -// id: Default::default(), -// ext_id: 7892635306594_i64, -// slug: "some-new-league".to_string(), -// name: "Some New League".to_string(), -// region: "Bahía de cochinos".to_string(), -// image_url: "https://nobodyspectsandimage.io".to_string(), -// }; -// -// // We insert the instance on the database, on the `League` entity -// new_league -// .insert_with(MYSQL_DS) -// .await -// .expect("Failed insert datasource operation"); -// -// // Now, in the `id` field of the instance, we have the autogenerated -// // value for the primary key field, which is id. So, we can query the -// // database again with the find by primary key operation to check if -// // the value was really inserted -// let inserted_league = League::find_by_pk_with(&new_league.id, MYSQL_DS) -// .await -// .expect("Failed the query to the database") -// .expect("No entity found for the primary key value passed in"); -// -// assert_eq!(new_league.id, inserted_league.id); -// } -// -// /// The multi insert operation is a shorthand for insert multiple instances of *T* -// /// in the database at once. -// /// -// /// It works pretty much the same that the insert operation, with the same behaviour -// /// of the `#[primary_key]` annotation over some field. It will auto set the primary -// /// key field with the autogenerated value on the database on the insert operation, but -// /// for every entity passed in as an array of mutable instances of `T`. -// /// -// /// The instances without `#[primary_key]` inserts all the values on the instaqce fields -// /// on the database. -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_multi_insert_operation() { -// let mut new_league_mi: League = League { -// id: Default::default(), -// ext_id: 54376478_i64, -// slug: "some-new-random-league".to_string(), -// name: "Some New Random League".to_string(), -// region: "Unknown".to_string(), -// image_url: "https://what-a-league.io".to_string(), -// }; -// let mut new_league_mi_2: League = League { -// id: Default::default(), -// ext_id: 3475689769678906_i64, -// slug: "new-league-2".to_string(), -// name: "New League 2".to_string(), -// region: "Really unknown".to_string(), -// image_url: "https://what-an-unknown-league.io".to_string(), -// }; -// let mut new_league_mi_3: League = League { -// id: Default::default(), -// ext_id: 46756867_i64, -// slug: "a-new-multinsert".to_string(), -// name: "New League 3".to_string(), -// region: "The dark side of the moon".to_string(), -// image_url: "https://interplanetary-league.io".to_string(), -// }; -// -// // Insert the instance as database entities -// new_league_mi -// .insert() -// .await -// .expect("Failed insert datasource operation"); -// new_league_mi_2 -// .insert() -// .await -// .expect("Failed insert datasource operation"); -// new_league_mi_3 -// .insert() -// .await -// .expect("Failed insert datasource operation"); -// -// // Recover the inserted data by primary key -// let inserted_league = League::find_by_pk(&new_league_mi.id) -// .await -// .expect("[1] - Failed the query to the database") -// .expect("[1] - No entity found for the primary key value passed in"); -// let inserted_league_2 = League::find_by_pk(&new_league_mi_2.id) -// .await -// .expect("[2] - Failed the query to the database") -// .expect("[2] - No entity found for the primary key value passed in"); -// let inserted_league_3 = League::find_by_pk(&new_league_mi_3.id) -// .await -// .expect("[3] - Failed the query to the database") -// .expect("[3] - No entity found for the primary key value passed in"); -// -// assert_eq!(new_league_mi.id, inserted_league.id); -// assert_eq!(new_league_mi_2.id, inserted_league_2.id); -// assert_eq!(new_league_mi_3.id, inserted_league_3.id); -// } -// -// /// Same as the multi insert above, but with the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_multi_insert_with_mssql_operation() { -// let mut new_league_mi: League = League { -// id: Default::default(), -// ext_id: 54376478_i64, -// slug: "some-new-random-league".to_string(), -// name: "Some New Random League".to_string(), -// region: "Unknown".to_string(), -// image_url: "https://what-a-league.io".to_string(), -// }; -// let mut new_league_mi_2: League = League { -// id: Default::default(), -// ext_id: 3475689769678906_i64, -// slug: "new-league-2".to_string(), -// name: "New League 2".to_string(), -// region: "Really unknown".to_string(), -// image_url: "https://what-an-unknown-league.io".to_string(), -// }; -// let mut new_league_mi_3: League = League { -// id: Default::default(), -// ext_id: 46756867_i64, -// slug: "a-new-multinsert".to_string(), -// name: "New League 3".to_string(), -// region: "The dark side of the moon".to_string(), -// image_url: "https://interplanetary-league.io".to_string(), -// }; -// -// // Insert the instance as database entities -// new_league_mi -// .insert_with(SQL_SERVER_DS) -// .await -// .expect("Failed insert datasource operation"); -// new_league_mi_2 -// .insert_with(SQL_SERVER_DS) -// .await -// .expect("Failed insert datasource operation"); -// new_league_mi_3 -// .insert_with(SQL_SERVER_DS) -// .await -// .expect("Failed insert datasource operation"); -// -// // Recover the inserted data by primary key -// let inserted_league = League::find_by_pk_with(&new_league_mi.id, SQL_SERVER_DS) -// .await -// .expect("[1] - Failed the query to the database") -// .expect("[1] - No entity found for the primary key value passed in"); -// let inserted_league_2 = League::find_by_pk_with(&new_league_mi_2.id, SQL_SERVER_DS) -// .await -// .expect("[2] - Failed the query to the database") -// .expect("[2] - No entity found for the primary key value passed in"); -// let inserted_league_3 = League::find_by_pk_with(&new_league_mi_3.id, SQL_SERVER_DS) -// .await -// .expect("[3] - Failed the query to the database") -// .expect("[3] - No entity found for the primary key value passed in"); -// -// assert_eq!(new_league_mi.id, inserted_league.id); -// assert_eq!(new_league_mi_2.id, inserted_league_2.id); -// assert_eq!(new_league_mi_3.id, inserted_league_3.id); -// } -// -// /// Same as the multi insert above, but with the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_multi_insert_with_mysql_operation() { -// let mut new_league_mi: League = League { -// id: Default::default(), -// ext_id: 54376478_i64, -// slug: "some-new-random-league".to_string(), -// name: "Some New Random League".to_string(), -// region: "Unknown".to_string(), -// image_url: "https://what-a-league.io".to_string(), -// }; -// let mut new_league_mi_2: League = League { -// id: Default::default(), -// ext_id: 3475689769678906_i64, -// slug: "new-league-2".to_string(), -// name: "New League 2".to_string(), -// region: "Really unknown".to_string(), -// image_url: "https://what-an-unknown-league.io".to_string(), -// }; -// let mut new_league_mi_3: League = League { -// id: Default::default(), -// ext_id: 46756867_i64, -// slug: "a-new-multinsert".to_string(), -// name: "New League 3".to_string(), -// region: "The dark side of the moon".to_string(), -// image_url: "https://interplanetary-league.io".to_string(), -// }; -// -// // Insert the instance as database entities -// new_league_mi -// .insert_with(MYSQL_DS) -// .await -// .expect("Failed insert datasource operation"); -// new_league_mi_2 -// .insert_with(MYSQL_DS) -// .await -// .expect("Failed insert datasource operation"); -// new_league_mi_3 -// .insert_with(MYSQL_DS) -// .await -// .expect("Failed insert datasource operation"); -// -// // Recover the inserted data by primary key -// let inserted_league = League::find_by_pk_with(&new_league_mi.id, MYSQL_DS) -// .await -// .expect("[1] - Failed the query to the database") -// .expect("[1] - No entity found for the primary key value passed in"); -// let inserted_league_2 = League::find_by_pk_with(&new_league_mi_2.id, MYSQL_DS) -// .await -// .expect("[2] - Failed the query to the database") -// .expect("[2] - No entity found for the primary key value passed in"); -// let inserted_league_3 = League::find_by_pk_with(&new_league_mi_3.id, MYSQL_DS) -// .await -// .expect("[3] - Failed the query to the database") -// .expect("[3] - No entity found for the primary key value passed in"); -// -// assert_eq!(new_league_mi.id, inserted_league.id); -// assert_eq!(new_league_mi_2.id, inserted_league_2.id); -// assert_eq!(new_league_mi_3.id, inserted_league_3.id); -// } +//! Integration tests for the CRUD operations available in `Canyon` that +//! generates and executes *INSERT* statements +use canyon_sql::crud::CrudOperations; + +#[cfg(feature = "mysql")] +use crate::constants::MYSQL_DS; +#[cfg(feature = "mssql")] +use crate::constants::SQL_SERVER_DS; + +use crate::tests_models::league::*; + +/// Inserts a new record on the database, given an entity that is +/// annotated with `#[canyon_entity]` macro over a *T* type. +/// +/// For insert a new record on a database, the *insert* operation needs +/// some special requirements: +/// > - We need a mutable instance of `T`. If the operation completes +/// successfully, the insert operation will automatically set the autogenerated +/// value for the `primary_key` annotated field in it. +/// +/// > - It's considered a good practice to initialize that concrete field with +/// the `Default` trait, because the value on the primary key field will be +/// ignored at the execution time of the insert, and updated with the autogenerated +/// value by the database. +/// +/// By default, the `#[primary_key]` annotation means autogenerated and autoincremental. +/// You can configure not autoincremental via macro annotation parameters (please, +/// refer to the docs [here]() for more info.) +/// +/// If the type hasn't a `#[primary_key]` annotation, or the annotation contains +/// an argument specifying not autoincremental behaviour, all the fields will be +/// inserted on the database and no returning value will be placed in any field. +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_insert_operation() { + let mut new_league: League = League { + id: Default::default(), + ext_id: 7892635306594_i64, + slug: "some-new-league".to_string(), + name: "Some New League".to_string(), + region: "Bahía de cochinos".to_string(), + image_url: "https://nobodyspectsandimage.io".to_string(), + }; + + // We insert the instance on the database, on the `League` entity + new_league.insert().await.expect("Failed insert operation"); + + // Now, in the `id` field of the instance, we have the autogenerated + // value for the primary key field, which is id. So, we can query the + // database again with the find by primary key operation to check if + // the value was really inserted + let inserted_league = League::find_by_pk(&new_league.id) + .await + .expect("Failed the query to the database") + .expect("No entity found for the primary key value passed in"); + + assert_eq!(new_league.id, inserted_league.id); +} + +/// Same as the insert operation above, but targeting the database defined in +/// the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_insert_with_mssql_operation() { + let mut new_league: League = League { + id: Default::default(), + ext_id: 7892635306594_i64, + slug: "some-new-league".to_string(), + name: "Some New League".to_string(), + region: "Bahía de cochinos".to_string(), + image_url: "https://nobodyspectsandimage.io".to_string(), + }; + + // We insert the instance on the database, on the `League` entity + new_league + .insert_with(SQL_SERVER_DS) + .await + .expect("Failed insert datasource operation"); + + // Now, in the `id` field of the instance, we have the autogenerated + // value for the primary key field, which is id. So, we can query the + // database again with the find by primary key operation to check if + // the value was really inserted + let inserted_league = League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) + .await + .expect("Failed the query to the database") + .expect("No entity found for the primary key value passed in"); + + assert_eq!(new_league.id, inserted_league.id); +} + +/// Same as the insert operation above, but targeting the database defined in +/// the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_insert_with_mysql_operation() { + let mut new_league: League = League { + id: Default::default(), + ext_id: 7892635306594_i64, + slug: "some-new-league".to_string(), + name: "Some New League".to_string(), + region: "Bahía de cochinos".to_string(), + image_url: "https://nobodyspectsandimage.io".to_string(), + }; + + // We insert the instance on the database, on the `League` entity + new_league + .insert_with(MYSQL_DS) + .await + .expect("Failed insert datasource operation"); + + // Now, in the `id` field of the instance, we have the autogenerated + // value for the primary key field, which is id. So, we can query the + // database again with the find by primary key operation to check if + // the value was really inserted + let inserted_league = League::find_by_pk_with(&new_league.id, MYSQL_DS) + .await + .expect("Failed the query to the database") + .expect("No entity found for the primary key value passed in"); + + assert_eq!(new_league.id, inserted_league.id); +} + +/// The multi insert operation is a shorthand for insert multiple instances of *T* +/// in the database at once. +/// +/// It works pretty much the same that the insert operation, with the same behaviour +/// of the `#[primary_key]` annotation over some field. It will auto set the primary +/// key field with the autogenerated value on the database on the insert operation, but +/// for every entity passed in as an array of mutable instances of `T`. +/// +/// The instances without `#[primary_key]` inserts all the values on the instaqce fields +/// on the database. +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_multi_insert_operation() { + let mut new_league_mi: League = League { + id: Default::default(), + ext_id: 54376478_i64, + slug: "some-new-random-league".to_string(), + name: "Some New Random League".to_string(), + region: "Unknown".to_string(), + image_url: "https://what-a-league.io".to_string(), + }; + let mut new_league_mi_2: League = League { + id: Default::default(), + ext_id: 3475689769678906_i64, + slug: "new-league-2".to_string(), + name: "New League 2".to_string(), + region: "Really unknown".to_string(), + image_url: "https://what-an-unknown-league.io".to_string(), + }; + let mut new_league_mi_3: League = League { + id: Default::default(), + ext_id: 46756867_i64, + slug: "a-new-multinsert".to_string(), + name: "New League 3".to_string(), + region: "The dark side of the moon".to_string(), + image_url: "https://interplanetary-league.io".to_string(), + }; + + // Insert the instance as database entities + new_league_mi + .insert() + .await + .expect("Failed insert datasource operation"); + new_league_mi_2 + .insert() + .await + .expect("Failed insert datasource operation"); + new_league_mi_3 + .insert() + .await + .expect("Failed insert datasource operation"); + + // Recover the inserted data by primary key + let inserted_league = League::find_by_pk(&new_league_mi.id) + .await + .expect("[1] - Failed the query to the database") + .expect("[1] - No entity found for the primary key value passed in"); + let inserted_league_2 = League::find_by_pk(&new_league_mi_2.id) + .await + .expect("[2] - Failed the query to the database") + .expect("[2] - No entity found for the primary key value passed in"); + let inserted_league_3 = League::find_by_pk(&new_league_mi_3.id) + .await + .expect("[3] - Failed the query to the database") + .expect("[3] - No entity found for the primary key value passed in"); + + assert_eq!(new_league_mi.id, inserted_league.id); + assert_eq!(new_league_mi_2.id, inserted_league_2.id); + assert_eq!(new_league_mi_3.id, inserted_league_3.id); +} + +/// Same as the multi insert above, but with the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_multi_insert_with_mssql_operation() { + let mut new_league_mi: League = League { + id: Default::default(), + ext_id: 54376478_i64, + slug: "some-new-random-league".to_string(), + name: "Some New Random League".to_string(), + region: "Unknown".to_string(), + image_url: "https://what-a-league.io".to_string(), + }; + let mut new_league_mi_2: League = League { + id: Default::default(), + ext_id: 3475689769678906_i64, + slug: "new-league-2".to_string(), + name: "New League 2".to_string(), + region: "Really unknown".to_string(), + image_url: "https://what-an-unknown-league.io".to_string(), + }; + let mut new_league_mi_3: League = League { + id: Default::default(), + ext_id: 46756867_i64, + slug: "a-new-multinsert".to_string(), + name: "New League 3".to_string(), + region: "The dark side of the moon".to_string(), + image_url: "https://interplanetary-league.io".to_string(), + }; + + // Insert the instance as database entities + new_league_mi + .insert_with(SQL_SERVER_DS) + .await + .expect("Failed insert datasource operation"); + new_league_mi_2 + .insert_with(SQL_SERVER_DS) + .await + .expect("Failed insert datasource operation"); + new_league_mi_3 + .insert_with(SQL_SERVER_DS) + .await + .expect("Failed insert datasource operation"); + + // Recover the inserted data by primary key + let inserted_league = League::find_by_pk_with(&new_league_mi.id, SQL_SERVER_DS) + .await + .expect("[1] - Failed the query to the database") + .expect("[1] - No entity found for the primary key value passed in"); + let inserted_league_2 = League::find_by_pk_with(&new_league_mi_2.id, SQL_SERVER_DS) + .await + .expect("[2] - Failed the query to the database") + .expect("[2] - No entity found for the primary key value passed in"); + let inserted_league_3 = League::find_by_pk_with(&new_league_mi_3.id, SQL_SERVER_DS) + .await + .expect("[3] - Failed the query to the database") + .expect("[3] - No entity found for the primary key value passed in"); + + assert_eq!(new_league_mi.id, inserted_league.id); + assert_eq!(new_league_mi_2.id, inserted_league_2.id); + assert_eq!(new_league_mi_3.id, inserted_league_3.id); +} + +/// Same as the multi insert above, but with the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_multi_insert_with_mysql_operation() { + let mut new_league_mi: League = League { + id: Default::default(), + ext_id: 54376478_i64, + slug: "some-new-random-league".to_string(), + name: "Some New Random League".to_string(), + region: "Unknown".to_string(), + image_url: "https://what-a-league.io".to_string(), + }; + let mut new_league_mi_2: League = League { + id: Default::default(), + ext_id: 3475689769678906_i64, + slug: "new-league-2".to_string(), + name: "New League 2".to_string(), + region: "Really unknown".to_string(), + image_url: "https://what-an-unknown-league.io".to_string(), + }; + let mut new_league_mi_3: League = League { + id: Default::default(), + ext_id: 46756867_i64, + slug: "a-new-multinsert".to_string(), + name: "New League 3".to_string(), + region: "The dark side of the moon".to_string(), + image_url: "https://interplanetary-league.io".to_string(), + }; + + // Insert the instance as database entities + new_league_mi + .insert_with(MYSQL_DS) + .await + .expect("Failed insert datasource operation"); + new_league_mi_2 + .insert_with(MYSQL_DS) + .await + .expect("Failed insert datasource operation"); + new_league_mi_3 + .insert_with(MYSQL_DS) + .await + .expect("Failed insert datasource operation"); + + // Recover the inserted data by primary key + let inserted_league = League::find_by_pk_with(&new_league_mi.id, MYSQL_DS) + .await + .expect("[1] - Failed the query to the database") + .expect("[1] - No entity found for the primary key value passed in"); + let inserted_league_2 = League::find_by_pk_with(&new_league_mi_2.id, MYSQL_DS) + .await + .expect("[2] - Failed the query to the database") + .expect("[2] - No entity found for the primary key value passed in"); + let inserted_league_3 = League::find_by_pk_with(&new_league_mi_3.id, MYSQL_DS) + .await + .expect("[3] - Failed the query to the database") + .expect("[3] - No entity found for the primary key value passed in"); + + assert_eq!(new_league_mi.id, inserted_league.id); + assert_eq!(new_league_mi_2.id, inserted_league_2.id); + assert_eq!(new_league_mi_3.id, inserted_league_3.id); +} diff --git a/tests/crud/update_operations.rs b/tests/crud/update_operations.rs index 7b1466f1..8eac3031 100644 --- a/tests/crud/update_operations.rs +++ b/tests/crud/update_operations.rs @@ -2,12 +2,12 @@ // // Integration tests for the CRUD operations available in `Canyon` that // /// generates and executes *UPDATE* statements // use canyon_sql::crud::CrudOperations; -// +// // #[cfg(feature = "mysql")] // use crate::constants::MYSQL_DS; // #[cfg(feature = "mssql")] // use crate::constants::SQL_SERVER_DS; -// +// // /// Update operation is a *CRUD* method defined for some entity `T`, that works by appliying // /// some change to a Rust's entity instance, and persisting them into the database. // /// @@ -27,12 +27,12 @@ // .await // .expect("[1] - Failed the query to the database") // .expect("[1] - No entity found for the primary key value passed in"); -// +// // // The ext_id field value is extracted from the sql scripts under the // // docker/sql folder. We are retrieving the first entity inserted at the // // wake-up time of the database, and now checking some of its properties. // assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); -// +// // // Modify the value, and perform the update // let updt_value: i64 = 593064_i64; // updt_candidate.ext_id = updt_value; @@ -40,15 +40,15 @@ // .update() // .await // .expect("Failed the update operation"); -// +// // // Retrieve it again, and check if the value was really updated // let updt_entity: League = League::find_by_pk(&1) // .await // .expect("[2] - Failed the query to the database") // .expect("[2] - No entity found for the primary key value passed in"); -// +// // assert_eq!(updt_entity.ext_id, updt_value); -// +// // // We roll back the changes to the initial value to don't broke other tests // // the next time that will run // updt_candidate.ext_id = 100695891328981122_i64; @@ -57,7 +57,7 @@ // .await // .expect("Failed to restore the initial value in the psql update operation"); // } -// +// // /// Same as the above test, but with the specified datasource. // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] @@ -68,12 +68,12 @@ // .await // .expect("[1] - Failed the query to the database") // .expect("[1] - No entity found for the primary key value passed in"); -// +// // // The ext_id field value is extracted from the sql scripts under the // // docker/sql folder. We are retrieving the first entity inserted at the // // wake-up time of the database, and now checking some of its properties. // assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); -// +// // // Modify the value, and perform the update // let updt_value: i64 = 59306442534_i64; // updt_candidate.ext_id = updt_value; @@ -81,15 +81,15 @@ // .update_with(SQL_SERVER_DS) // .await // .expect("Failed the update operation"); -// +// // // Retrieve it again, and check if the value was really updated // let updt_entity: League = League::find_by_pk_with(&1, SQL_SERVER_DS) // .await // .expect("[2] - Failed the query to the database") // .expect("[2] - No entity found for the primary key value passed in"); -// +// // assert_eq!(updt_entity.ext_id, updt_value); -// +// // // We rollback the changes to the initial value to don't broke other tests // // the next time that will run // updt_candidate.ext_id = 100695891328981122_i64; @@ -98,24 +98,24 @@ // .await // .expect("Failed to restablish the initial value update operation"); // } -// +// // /// Same as the above test, but with the specified datasource. // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] // fn test_crud_update_with_mysql_method_operation() { // // We first retrieve some entity from the database. Note that we must make // // the retrieved instance mutable of clone it to a new mutable resource -// +// // let mut updt_candidate: League = League::find_by_pk_with(&1, MYSQL_DS) // .await // .expect("[1] - Failed the query to the database") // .expect("[1] - No entity found for the primary key value passed in"); -// +// // // The ext_id field value is extracted from the sql scripts under the // // docker/sql folder. We are retrieving the first entity inserted at the // // wake up time of the database, and now checking some of its properties. // assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); -// +// // // Modify the value, and perform the update // let updt_value: i64 = 59306442534_i64; // updt_candidate.ext_id = updt_value; @@ -123,15 +123,15 @@ // .update_with(MYSQL_DS) // .await // .expect("Failed the update operation"); -// +// // // Retrieve it again, and check if the value was really updated // let updt_entity: League = League::find_by_pk_with(&1, MYSQL_DS) // .await // .expect("[2] - Failed the query to the database") // .expect("[2] - No entity found for the primary key value passed in"); -// +// // assert_eq!(updt_entity.ext_id, updt_value); -// +// // // We rollback the changes to the initial value to don't broke other tests // // the next time that will run // updt_candidate.ext_id = 100695891328981122_i64; diff --git a/tests/tests_models/tournament.rs b/tests/tests_models/tournament.rs index 001a87b5..e6fab352 100644 --- a/tests/tests_models/tournament.rs +++ b/tests/tests_models/tournament.rs @@ -1,6 +1,6 @@ // use crate::tests_models::league::League; // use canyon_sql::{date_time::NaiveDate, macros::*}; -// +// // #[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] // #[canyon_entity] // pub struct Tournament { From 1e2ec1574b4bd8adabe758a39c73f3b2d93fa3b0 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 10 Feb 2025 17:37:02 +0100 Subject: [PATCH 072/155] feat(wip): re-enabling all the fk operations --- .../src/query_operations/foreign_key.rs | 25 +- tests/crud/foreign_key_operations.rs | 326 +++++++++--------- tests/tests_models/tournament.rs | 30 +- 3 files changed, 188 insertions(+), 193 deletions(-) diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index cc640e2d..9f78f3c3 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -19,7 +19,7 @@ pub fn generate_find_by_fk_ops( // The tokens for generating the methods that enable Canyon to retrieve the child entities that are of T type // given a parent entity U: ForeignKeyable, as an associated function for the child type (T) let search_by_reverse_fk_tokens: Vec<(TokenStream, TokenStream)> = - generate_find_by_reverse_foreign_key_tokens(macro_data, &table_schema_data); + generate_find_by_reverse_foreign_key_tokens(macro_data, table_schema_data); let rev_fk_method_signatures = search_by_reverse_fk_tokens.iter().map(|(sign, _)| sign); let rev_fk_method_implementations = search_by_reverse_fk_tokens.iter().map(|(_, m_impl)| m_impl); @@ -110,13 +110,11 @@ fn generate_find_by_foreign_key_tokens( /// Searches the parent entity (if exists) for this type #quoted_method_signature { async move { - let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( + <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query_one( #stmt, &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], "" - ).await?; - - #result_handler + ).await } } }, @@ -128,13 +126,11 @@ fn generate_find_by_foreign_key_tokens( /// Searches the parent entity (if exists) for this type with the specified datasource #quoted_with_method_signature { async move { - let result = <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query( + <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query_one( #stmt, &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], input - ).await?; - - #result_handler + ).await } } }, @@ -198,11 +194,11 @@ fn generate_find_by_reverse_foreign_key_tokens( format!("\"{}\"", #f_ident).as_str() ); - Ok(<#ty as canyon_sql::core::Transaction<#ty>>::query( + <#ty as canyon_sql::core::Transaction<#ty>>::query( stmt, &[lookage_value], "" - ).await?.into_results::<#ty>()) + ).await } } }, @@ -228,11 +224,11 @@ fn generate_find_by_reverse_foreign_key_tokens( format!("\"{}\"", #f_ident).as_str() ); - Ok(<#ty as canyon_sql::core::Transaction<#ty>>::query( + <#ty as canyon_sql::core::Transaction<#ty>>::query( stmt, &[lookage_value], input - ).await?.into_results::<#ty>()) + ).await } } }, @@ -240,6 +236,5 @@ fn generate_find_by_reverse_foreign_key_tokens( } } - rev_fk_quotes; - vec![] + rev_fk_quotes } diff --git a/tests/crud/foreign_key_operations.rs b/tests/crud/foreign_key_operations.rs index e8544df2..00f153e3 100644 --- a/tests/crud/foreign_key_operations.rs +++ b/tests/crud/foreign_key_operations.rs @@ -1,163 +1,163 @@ -// /// Integration tests for the CRUD operations available in `Canyon` that -// /// generates and executes *SELECT* statements based on a entity -// /// annotated with the `#[foreign_key(... args)]` annotation looking -// /// for the related data with some entity `U` that acts as is parent, where `U` -// /// impls `ForeignKeyable` (isn't required, but it won't unlock the -// /// reverse search features parent -> child, only the child -> parent ones). -// /// -// /// Names of the foreign key methods are autogenerated for the direct and -// /// reverse side of the implementations. -// /// For more info: TODO -> Link to the docs of the foreign key chapter -// use canyon_sql::crud::CrudOperations; -// -// #[cfg(feature = "mssql")] -// use crate::constants::MYSQL_DS; -// #[cfg(feature = "mssql")] -// use crate::constants::SQL_SERVER_DS; -// -// use crate::tests_models::league::*; -// use crate::tests_models::tournament::*; -// -// /// Given an entity `T` which has some field declaring a foreign key relation -// /// with some another entity `U`, for example, performs a search to find -// /// what is the parent type `U` of `T` -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_by_foreign_key() { -// let some_tournament: Tournament = Tournament::find_by_pk(&1) -// .await -// .expect("Result variant of the query is err") -// .expect("No result found for the given parameter"); -// -// // We can get the parent entity for the retrieved child instance -// let parent_entity: Option = some_tournament -// .search_league() -// .await -// .expect("Result variant of the query is err"); -// -// if let Some(league) = parent_entity { -// assert_eq!(some_tournament.league, league.id) -// } else { -// assert_eq!(parent_entity, None) -// } -// } -// -// /// Same as the search by foreign key, but with the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_by_foreign_key_with_mssql() { -// let some_tournament: Tournament = Tournament::find_by_pk_with(&10, SQL_SERVER_DS) -// .await -// .expect("Result variant of the query is err") -// .expect("No result found for the given parameter"); -// -// // We can get the parent entity for the retrieved child instance -// let parent_entity: Option = some_tournament -// .search_league_with(SQL_SERVER_DS) -// .await -// .expect("Result variant of the query is err"); -// -// // These are tests, and we could unwrap the result contained in the option, because -// // it always should exist that search for the data inserted when the docker starts. -// // But, just for change the style a little bit and offer more options about how to -// // handle things done with Canyon -// if let Some(league) = parent_entity { -// assert_eq!(some_tournament.league, league.id) -// } else { -// assert_eq!(parent_entity, None) -// } -// } -// -// /// Same as the search by foreign key, but with the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_by_foreign_key_with_mysql() { -// let some_tournament: Tournament = Tournament::find_by_pk_with(&10, MYSQL_DS) -// .await -// .expect("Result variant of the query is err") -// .expect("No result found for the given parameter"); -// -// // We can get the parent entity for the retrieved child instance -// let parent_entity: Option = some_tournament -// .search_league_with(MYSQL_DS) -// .await -// .expect("Result variant of the query is err"); -// -// // These are tests, and we could unwrap the result contained in the option, because -// // it always should exist that search for the data inserted when the docker starts. -// // But, just for change the style a little bit and offer more options about how to -// // handle things done with Canyon -// if let Some(league) = parent_entity { -// assert_eq!(some_tournament.league, league.id) -// } else { -// assert_eq!(parent_entity, None) -// } -// } -// -// /// Given an entity `U` that is know as the "parent" side of the relation with another -// /// entity `T`, for example, we can ask to the parent for the childrens that belongs -// /// to `U`. -// /// -// /// For this to work, `U`, the parent, must have derived the `ForeignKeyable` proc macro -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_reverse_side_foreign_key() { -// let some_league: League = League::find_by_pk(&1) -// .await -// .expect("Result variant of the query is err") -// .expect("No result found for the given parameter"); -// -// // Computes how many tournaments are pointing to the retrieved league -// let child_tournaments: Vec = Tournament::search_league_childrens(&some_league) -// .await -// .expect("Result variant of the query is err"); -// -// assert!(!child_tournaments.is_empty()); -// child_tournaments -// .iter() -// .for_each(|t| assert_eq!(t.league, some_league.id)); -// } -// -// /// Same as the search by the reverse side of a foreign key relation -// /// but with the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_reverse_side_foreign_key_with_mssql() { -// let some_league: League = League::find_by_pk_with(&1, SQL_SERVER_DS) -// .await -// .expect("Result variant of the query is err") -// .expect("No result found for the given parameter"); -// -// // Computes how many tournaments are pointing to the retrieved league -// let child_tournaments: Vec = -// Tournament::search_league_childrens_with(&some_league, SQL_SERVER_DS) -// .await -// .expect("Result variant of the query is err"); -// -// assert!(!child_tournaments.is_empty()); -// child_tournaments -// .iter() -// .for_each(|t| assert_eq!(t.league, some_league.id)); -// } -// -// /// Same as the search by the reverse side of a foreign key relation -// /// but with the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_reverse_side_foreign_key_with_mysql() { -// let some_league: League = League::find_by_pk_with(&1, MYSQL_DS) -// .await -// .expect("Result variant of the query is err") -// .expect("No result found for the given parameter"); -// -// // Computes how many tournaments are pointing to the retrieved league -// let child_tournaments: Vec = -// Tournament::search_league_childrens_with(&some_league, MYSQL_DS) -// .await -// .expect("Result variant of the query is err"); -// -// assert!(!child_tournaments.is_empty()); -// child_tournaments -// .iter() -// .for_each(|t| assert_eq!(t.league, some_league.id)); -// } +/// Integration tests for the CRUD operations available in `Canyon` that +/// generates and executes *SELECT* statements based on a entity +/// annotated with the `#[foreign_key(... args)]` annotation looking +/// for the related data with some entity `U` that acts as is parent, where `U` +/// impls `ForeignKeyable` (isn't required, but it won't unlock the +/// reverse search features parent -> child, only the child -> parent ones). +/// +/// Names of the foreign key methods are autogenerated for the direct and +/// reverse side of the implementations. +/// For more info: TODO -> Link to the docs of the foreign key chapter +use canyon_sql::crud::CrudOperations; + +#[cfg(feature = "mssql")] +use crate::constants::MYSQL_DS; +#[cfg(feature = "mssql")] +use crate::constants::SQL_SERVER_DS; + +use crate::tests_models::league::*; +use crate::tests_models::tournament::*; + +/// Given an entity `T` which has some field declaring a foreign key relation +/// with some another entity `U`, for example, performs a search to find +/// what is the parent type `U` of `T` +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_search_by_foreign_key() { + let some_tournament: Tournament = Tournament::find_by_pk(&1) + .await + .expect("Result variant of the query is err") + .expect("No result found for the given parameter"); + + // We can get the parent entity for the retrieved child instance + let parent_entity: Option = some_tournament + .search_league() + .await + .expect("Result variant of the query is err"); + + if let Some(league) = parent_entity { + assert_eq!(some_tournament.league, league.id) + } else { + assert_eq!(parent_entity, None) + } +} + +/// Same as the search by foreign key, but with the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_search_by_foreign_key_with_mssql() { + let some_tournament: Tournament = Tournament::find_by_pk_with(&10, SQL_SERVER_DS) + .await + .expect("Result variant of the query is err") + .expect("No result found for the given parameter"); + + // We can get the parent entity for the retrieved child instance + let parent_entity: Option = some_tournament + .search_league_with(SQL_SERVER_DS) + .await + .expect("Result variant of the query is err"); + + // These are tests, and we could unwrap the result contained in the option, because + // it always should exist that search for the data inserted when the docker starts. + // But, just for change the style a little bit and offer more options about how to + // handle things done with Canyon + if let Some(league) = parent_entity { + assert_eq!(some_tournament.league, league.id) + } else { + assert_eq!(parent_entity, None) + } +} + +/// Same as the search by foreign key, but with the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_search_by_foreign_key_with_mysql() { + let some_tournament: Tournament = Tournament::find_by_pk_with(&10, MYSQL_DS) + .await + .expect("Result variant of the query is err") + .expect("No result found for the given parameter"); + + // We can get the parent entity for the retrieved child instance + let parent_entity: Option = some_tournament + .search_league_with(MYSQL_DS) + .await + .expect("Result variant of the query is err"); + + // These are tests, and we could unwrap the result contained in the option, because + // it always should exist that search for the data inserted when the docker starts. + // But, just for change the style a little bit and offer more options about how to + // handle things done with Canyon + if let Some(league) = parent_entity { + assert_eq!(some_tournament.league, league.id) + } else { + assert_eq!(parent_entity, None) + } +} + +/// Given an entity `U` that is know as the "parent" side of the relation with another +/// entity `T`, for example, we can ask to the parent for the childrens that belongs +/// to `U`. +/// +/// For this to work, `U`, the parent, must have derived the `ForeignKeyable` proc macro +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_search_reverse_side_foreign_key() { + let some_league: League = League::find_by_pk(&1) + .await + .expect("Result variant of the query is err") + .expect("No result found for the given parameter"); + + // Computes how many tournaments are pointing to the retrieved league + let child_tournaments: Vec = Tournament::search_league_childrens(&some_league) + .await + .expect("Result variant of the query is err"); + + assert!(!child_tournaments.is_empty()); + child_tournaments + .iter() + .for_each(|t| assert_eq!(t.league, some_league.id)); +} + +/// Same as the search by the reverse side of a foreign key relation +/// but with the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_search_reverse_side_foreign_key_with_mssql() { + let some_league: League = League::find_by_pk_with(&1, SQL_SERVER_DS) + .await + .expect("Result variant of the query is err") + .expect("No result found for the given parameter"); + + // Computes how many tournaments are pointing to the retrieved league + let child_tournaments: Vec = + Tournament::search_league_childrens_with(&some_league, SQL_SERVER_DS) + .await + .expect("Result variant of the query is err"); + + assert!(!child_tournaments.is_empty()); + child_tournaments + .iter() + .for_each(|t| assert_eq!(t.league, some_league.id)); +} + +/// Same as the search by the reverse side of a foreign key relation +/// but with the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_search_reverse_side_foreign_key_with_mysql() { + let some_league: League = League::find_by_pk_with(&1, MYSQL_DS) + .await + .expect("Result variant of the query is err") + .expect("No result found for the given parameter"); + + // Computes how many tournaments are pointing to the retrieved league + let child_tournaments: Vec = + Tournament::search_league_childrens_with(&some_league, MYSQL_DS) + .await + .expect("Result variant of the query is err"); + + assert!(!child_tournaments.is_empty()); + child_tournaments + .iter() + .for_each(|t| assert_eq!(t.league, some_league.id)); +} diff --git a/tests/tests_models/tournament.rs b/tests/tests_models/tournament.rs index e6fab352..880076f4 100644 --- a/tests/tests_models/tournament.rs +++ b/tests/tests_models/tournament.rs @@ -1,15 +1,15 @@ -// use crate::tests_models::league::League; -// use canyon_sql::{date_time::NaiveDate, macros::*}; -// -// #[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] -// #[canyon_entity] -// pub struct Tournament { -// #[primary_key] -// id: i32, -// ext_id: i64, -// slug: String, -// start_date: NaiveDate, -// end_date: NaiveDate, -// #[foreign_key(table = "league", column = "id")] -// league: i32, -// } +use crate::tests_models::league::League; +use canyon_sql::{date_time::NaiveDate, macros::*}; + +#[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] +#[canyon_entity] +pub struct Tournament { + #[primary_key] + id: i32, + ext_id: i64, + slug: String, + start_date: NaiveDate, + end_date: NaiveDate, + #[foreign_key(table = "league", column = "id")] + league: i32, +} From c95bd1f2f253d0e537838ab73bc0ba8965b5bace Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 11 Feb 2025 12:37:11 +0100 Subject: [PATCH 073/155] feat: using the execute new op on the update to return the affected rows after the operation --- .../src/connection/db_clients/mssql.rs | 43 ++- .../src/connection/db_clients/mysql.rs | 136 +++++---- .../src/connection/db_clients/postgresql.rs | 44 ++- canyon_core/src/connection/db_connector.rs | 51 ++++ canyon_core/src/lib.rs | 1 + canyon_core/src/transaction.rs | 12 + canyon_crud/src/crud.rs | 40 +-- canyon_macros/src/query_operations/read.rs | 4 +- canyon_macros/src/query_operations/update.rs | 32 +- tests/crud/update_operations.rs | 284 +++++++++--------- 10 files changed, 389 insertions(+), 258 deletions(-) diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs index 6bf8ec9d..33055325 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -55,6 +55,14 @@ impl DbConnection for SqlServerConnection { sqlserver_query_launcher::query_one_for(stmt, params, self) } + fn execute<'a>( + &self, + stmt: &str, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> impl Future>> + Send { + sqlserver_query_launcher::execute(stmt, params, self) + } + fn get_database_type(&self) -> Result> { Ok(DatabaseType::SqlServer) } @@ -139,11 +147,41 @@ pub(crate) mod sqlserver_query_launcher { ) } + pub(crate) async fn execute<'a>( + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + conn: &SqlServerConnection, + ) -> Result> { + let mssql_query = generate_mssql_stmt(stmt, params).await; + + #[allow(mutable_transmutes)] // TODO: pls solve this elegantly someday :( + let sqlservconn = + unsafe { std::mem::transmute::<&SqlServerConnection, &mut SqlServerConnection>(conn) }; + + mssql_query + .execute(sqlservconn.client) + .await + .map(|r| r.total()) + .map_err(From::from) + } + async fn execute_query<'a>( stmt: &str, params: &[&'a (dyn QueryParameter<'_>)], conn: &SqlServerConnection, ) -> Result, Box<(dyn Error + Send + Sync)>> { + let mssql_query = generate_mssql_stmt(stmt, params).await; + + #[allow(mutable_transmutes)] // TODO: pls solve this elegantly someday :( + let sqlservconn = + unsafe { std::mem::transmute::<&SqlServerConnection, &mut SqlServerConnection>(conn) }; + Ok(mssql_query.query(sqlservconn.client).await?) + } + + async fn generate_mssql_stmt<'a>( + stmt: &str, + params: &[&'a dyn QueryParameter<'_>], + ) -> Query<'a> { let mut stmt = String::from(stmt); if stmt.contains("RETURNING") { let c = stmt.clone(); @@ -199,9 +237,6 @@ pub(crate) mod sqlserver_query_launcher { // mssql_query.bind() }); - #[allow(mutable_transmutes)] // TODO: pls solve this elegantly someday :( - let sqlservconn = - unsafe { std::mem::transmute::<&SqlServerConnection, &mut SqlServerConnection>(conn) }; - Ok(mssql_query.query(sqlservconn.client).await?) + mssql_query } } diff --git a/canyon_core/src/connection/db_clients/mysql.rs b/canyon_core/src/connection/db_clients/mysql.rs index 645e6b72..8c56c3af 100644 --- a/canyon_core/src/connection/db_clients/mysql.rs +++ b/canyon_core/src/connection/db_clients/mysql.rs @@ -36,7 +36,6 @@ impl DbConnection for MysqlConnection { S: AsRef + Display + Send, { mysql_query_launcher::query(stmt, params, self) - // async move { todo!() } } fn query_one<'a, R: RowMapper>( @@ -55,6 +54,14 @@ impl DbConnection for MysqlConnection { mysql_query_launcher::query_one_for(stmt, params, self) } + fn execute<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> impl Future>> + Send { + mysql_query_launcher::execute(stmt, params, self) + } + fn get_database_type(&self) -> Result> { Ok(DatabaseType::MySQL) } @@ -129,19 +136,57 @@ pub(crate) mod mysql_query_launcher { ) } - #[inline(always)] // TODO: very provisional implementation! care! - // TODO: would be better to launch a simple query for the last id? - async fn execute_query<'a, S>( + #[inline(always)] + async fn execute_query( stmt: S, - params: &[&'a dyn QueryParameter<'_>], + params: &[&'_ dyn QueryParameter<'_>], conn: &MysqlConnection, ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, { let mysql_connection = conn.client.get_conn().await?; + let is_insert = stmt.as_ref().find(" RETURNING"); + let mysql_stmt = generate_mysql_stmt(stmt.as_ref(), params)?; + + let mut query_result = mysql_stmt.run(mysql_connection).await?; + let result_rows = if is_insert.is_some() { + let last_insert = query_result + .last_insert_id() + .map(Value::UInt) + .ok_or("MySQL: Error getting the id in insert")?; + + vec![row::new_row( + vec![last_insert], + Arc::new([mysql_async::Column::new(ColumnType::MYSQL_TYPE_UNKNOWN)]), + )] + } else { + query_result.collect::().await? + }; + + Ok(result_rows) + } + + pub(crate) async fn execute( + stmt: S, + params: &[&'_ dyn QueryParameter<'_>], + conn: &MysqlConnection, + ) -> Result> + where + S: AsRef + Display + Send, + { + let mysql_connection = conn.client.get_conn().await?; + let mysql_stmt = generate_mysql_stmt(stmt.as_ref(), params)?; + + Ok(mysql_stmt.run(mysql_connection).await?.affected_rows()) + } - let stmt_with_escape_characters = regex::escape(stmt.as_ref()); + #[cfg(feature = "mysql")] + fn generate_mysql_stmt( + stmt: &str, + params: &[&'_ dyn QueryParameter<'_>], + ) -> Result>, Box> { + let stmt_with_escape_characters = regex::escape(stmt); let query_string = Regex::new(DETECT_PARAMS_IN_QUERY)?.replace_all(&stmt_with_escape_characters, "?"); @@ -149,66 +194,43 @@ pub(crate) mod mysql_query_launcher { .replace_all(&query_string, "") .to_string(); - let mut is_insert = false; - // TODO: take care of this ugly replace for the concrete client syntax by using canyon - // Query if let Some(index_start_clausule_returning) = query_string.find(" RETURNING") { query_string.truncate(index_start_clausule_returning); - is_insert = true; } let params_query: Vec = - reorder_params(stmt.as_ref(), params, |f| (*f).as_mysql_param().to_value()); + reorder_params(stmt, params, |f| (*f).as_mysql_param().to_value())?; - let query_with_params = QueryWithParams { + Ok(QueryWithParams { query: query_string, params: params_query, - }; - - let mut query_result = query_with_params.run(mysql_connection).await?; - - let result_rows = if is_insert { - let last_insert = query_result - .last_insert_id() - .map(Value::UInt) - .expect("Error getting pk id in insert"); - - vec![row::new_row( - vec![last_insert], - Arc::new([mysql_async::Column::new(ColumnType::MYSQL_TYPE_UNKNOWN)]), - )] - } else { - query_result.collect::().await? - }; - - Ok(result_rows) + }) } -} -#[cfg(feature = "mysql")] -fn reorder_params( - stmt: &str, - params: &[&'_ dyn QueryParameter<'_>], - fn_parser: impl Fn(&&dyn QueryParameter<'_>) -> T, -) -> Vec { - use mysql_query_launcher::DETECT_PARAMS_IN_QUERY; - - let mut ordered_params = vec![]; - let rg = regex::Regex::new(DETECT_PARAMS_IN_QUERY) - .expect("Error create regex with detect params pattern expression"); - - for positional_param in rg.find_iter(stmt) { - let pp: &str = positional_param.as_str(); - let pp_index = pp[1..] // param $1 -> get 1 - .parse::() - .expect("Error parse mapped parameter to usized.") - - 1; - - let element = params - .get(pp_index) - .expect("Error obtaining the element of the mapping against parameters."); - ordered_params.push(fn_parser(element)); - } + #[cfg(feature = "mysql")] + fn reorder_params( + stmt: &str, + params: &[&'_ dyn QueryParameter<'_>], + fn_parser: impl Fn(&&dyn QueryParameter<'_>) -> T, + ) -> Result, Box> { + use mysql_query_launcher::DETECT_PARAMS_IN_QUERY; + + let mut ordered_params = vec![]; + let rg = Regex::new(DETECT_PARAMS_IN_QUERY) + .expect("Error create regex with detect params pattern expression"); + + for positional_param in rg.find_iter(stmt) { + let pp: &str = positional_param.as_str(); + let pp_index = pp[1..] // param $1 -> get 1 + .parse::()? + - 1; + + let element = params + .get(pp_index) + .expect("Error obtaining the element of the mapping against parameters."); + ordered_params.push(fn_parser(element)); + } - ordered_params + Ok(ordered_params) + } } diff --git a/canyon_core/src/connection/db_clients/postgresql.rs b/canyon_core/src/connection/db_clients/postgresql.rs index 375aac66..ceaff086 100644 --- a/canyon_core/src/connection/db_clients/postgresql.rs +++ b/canyon_core/src/connection/db_clients/postgresql.rs @@ -55,6 +55,14 @@ impl DbConnection for PostgreSqlConnection { postgres_query_launcher::query_one_for(stmt, params, self) } + fn execute<'a>( + &self, + stmt: &str, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> impl Future>> + Send { + postgres_query_launcher::execute(stmt, params, self) + } + fn get_database_type(&self) -> Result> { Ok(DatabaseType::PostgreSql) } @@ -62,11 +70,28 @@ impl DbConnection for PostgreSqlConnection { #[cfg(feature = "postgres")] pub(crate) mod postgres_query_launcher { - use super::*; - use crate::rows::{FromSql, FromSqlOwnedValue}; + use crate::rows::FromSqlOwnedValue; use tokio_postgres::types::ToSql; + #[inline(always)] + pub(crate) async fn query<'a, S, R: RowMapper>( + stmt: S, + params: &[&'a (dyn QueryParameter<'_>)], + conn: &PostgreSqlConnection, + ) -> Result, Box<(dyn Error + Sync + Send)>> + where + S: AsRef + Display + Send, + { + Ok(conn + .client + .query(stmt.as_ref(), &get_psql_params(params)) + .await? + .iter() + .map(|row| R::deserialize_postgresql(row)) + .collect()) + } + #[inline(always)] pub(crate) async fn query_rows<'a>( stmt: &str, @@ -110,21 +135,18 @@ pub(crate) mod postgres_query_launcher { } #[inline(always)] - pub(crate) async fn query<'a, S, R: RowMapper>( + pub(crate) async fn execute<'a, S>( stmt: S, params: &[&'a (dyn QueryParameter<'_>)], conn: &PostgreSqlConnection, - ) -> Result, Box<(dyn Error + Sync + Send)>> + ) -> Result> where S: AsRef + Display + Send, { - Ok(conn - .client - .query(stmt.as_ref(), &get_psql_params(params)) - .await? - .iter() - .map(|row| R::deserialize_postgresql(row)) - .collect()) + conn.client + .execute(stmt.as_ref(), &get_psql_params(params)) + .await + .map_err(From::from) } fn get_psql_params<'a>(params: &[&'a (dyn QueryParameter<'_>)]) -> Vec<&'a (dyn ToSql + Sync)> { diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index 0990f703..191cadea 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -50,6 +50,14 @@ pub trait DbConnection { params: &[&'a dyn QueryParameter<'a>], ) -> impl Future>> + Send; + /// Executes the given SQL statement against the target database, being any implementor of self, + /// returning only a numerical positive integer number reflecting the number of affected rows + fn execute<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> impl Future>> + Send; + fn get_database_type(&self) -> Result>; } @@ -99,6 +107,16 @@ impl DbConnection for &str { conn.query_one_for(stmt, params).await } + async fn execute<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + let sane_ds_name = if !self.is_empty() { Some(*self) } else { None }; + let conn = get_database_connection_by_ds(sane_ds_name).await?; + conn.execute(stmt, params).await + } + fn get_database_type(&self) -> Result> { Ok(find_datasource_by_name_or_try_default(Some(*self))?.get_db_type()) } @@ -171,6 +189,22 @@ impl DbConnection for DatabaseConnection { DatabaseConnection::MySQL(client) => client.query_one_for(stmt, params).await, } } + async fn execute<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + match self { + #[cfg(feature = "postgres")] + DatabaseConnection::Postgres(client) => client.execute(stmt, params).await, + + #[cfg(feature = "mssql")] + DatabaseConnection::SqlServer(client) => client.execute(stmt, params).await, + + #[cfg(feature = "mysql")] + DatabaseConnection::MySQL(client) => client.execute(stmt, params).await, + } + } fn get_database_type(&self) -> Result> { Ok(self.get_db_type()) @@ -230,6 +264,23 @@ impl DbConnection for &mut DatabaseConnection { } } + async fn execute<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + match self { + #[cfg(feature = "postgres")] + DatabaseConnection::Postgres(client) => client.execute(stmt, params).await, + + #[cfg(feature = "mssql")] + DatabaseConnection::SqlServer(client) => client.execute(stmt, params).await, + + #[cfg(feature = "mysql")] + DatabaseConnection::MySQL(client) => client.execute(stmt, params).await, + } + } + fn get_database_type(&self) -> Result> { Ok(self.get_db_type()) } diff --git a/canyon_core/src/lib.rs b/canyon_core/src/lib.rs index a49602d8..8b9af66c 100644 --- a/canyon_core/src/lib.rs +++ b/canyon_core/src/lib.rs @@ -9,6 +9,7 @@ pub extern crate tiberius; #[cfg(feature = "mysql")] pub extern crate mysql_async; +extern crate core; pub extern crate lazy_static; // extern crate cfg_if; diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index b681b960..fc4bb339 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -55,4 +55,16 @@ pub trait Transaction { { async move { input.query_rows(stmt.as_ref(), params.as_ref()).await } } + + fn execute<'a, S, Z>( + stmt: S, + params: Z, + input: impl DbConnection + Send + 'a, + ) -> impl Future>> + Send + where + S: AsRef + Display + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send + 'a, + { + async move { input.execute(stmt.as_ref(), params.as_ref()).await } + } } diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index b4983c65..2b6c116b 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -68,42 +68,42 @@ where fn insert<'a>( &'a mut self, ) -> impl Future>> + Send; - + fn insert_with<'a, I>( &mut self, input: I, ) -> impl Future>> + Send where I: DbConnection + Send + 'a; - + fn multi_insert<'a>( instances: &'a mut [&'a mut T], ) -> impl Future>> + Send; - + fn multi_insert_with<'a, I>( instances: &'a mut [&'a mut T], input: I, ) -> impl Future>> + Send where I: DbConnection + Send + 'a; - - // fn update(&self) -> impl Future>> + Send; - // - // fn update_with<'a, I>( - // &self, - // input: I, - // ) -> impl Future>> + Send - // where - // I: DbConnection + Send + 'a; - // - // fn update_query<'a>() -> UpdateQueryBuilder<'a, T, &'a str>; - // - // fn update_query_with<'a, I>(input: I) -> UpdateQueryBuilder<'a, T, I> - // where - // I: DbConnection + Send + 'a; - // + + fn update(&self) -> impl Future>> + Send; + + fn update_with<'a, I>( + &self, + input: I, + ) -> impl Future>> + Send + where + I: DbConnection + Send + 'a; + + fn update_query<'a>() -> UpdateQueryBuilder<'a, T, &'a str>; + + fn update_query_with<'a, I>(input: I) -> UpdateQueryBuilder<'a, T, I> + where + I: DbConnection + Send + 'a; + // fn delete(&self) -> impl Future>> + Send; - // + // fn delete_with<'a, I>( // &self, // input: I, diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 598ea305..aa01b962 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -408,7 +408,7 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_find_by_pk() { let find_by_pk_builder = create_find_by_pk_macro( &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - FIND_BY_PK_STMT + FIND_BY_PK_STMT, ); let find_by_pk = find_by_pk_builder.generate_tokens().to_string(); @@ -421,7 +421,7 @@ mod macro_builder_read_ops_tests { fn test_macro_builder_find_by_pk_with() { let find_by_pk_with_builder = create_find_by_pk_with( &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - FIND_BY_PK_STMT + FIND_BY_PK_STMT, ); let find_by_pk_with = find_by_pk_with_builder.generate_tokens().to_string(); diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index 34bdfa9f..05dedc4c 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -36,39 +36,30 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri /// Updates a database record that matches /// the current instance of a T type, returning a result /// indicating a possible failure querying the database. - async fn update(&self) -> Result<(), Box> { + async fn update(&self) -> Result> { let stmt = format!( "UPDATE {} SET {} WHERE {} = ${:?}", #table_schema_data, #str_columns_values, #primary_key, &self.#pk_ident ); let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = &[#(#update_values),*]; - <#ty as canyon_sql::core::Transaction<#ty>>::query( - stmt, update_values, "" - ).await?; - - Ok(()) + <#ty as canyon_sql::core::Transaction<#ty>>::execute(stmt, update_values, "").await } - /// Updates a database record that matches /// the current instance of a T type, returning a result /// indicating a possible failure querying the database with the /// specified datasource async fn update_with<'a, I>(&self, input: I) - -> Result<(), Box> - where I: canyon_sql::core::DbConnection + Send + 'a + -> Result> + where I: canyon_sql::core::DbConnection + Send + 'a { let stmt = format!( "UPDATE {} SET {} WHERE {} = ${:?}", - #table_schema_data, #str_columns_values, #primary_key, #pk_index + 1 + #table_schema_data, #str_columns_values, #primary_key, &self.#pk_ident ); let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = &[#(#update_values_cloned),*]; - <#ty as canyon_sql::core::Transaction<#ty>>::query( - stmt, update_values, input - ).await?; - - Ok(()) + <#ty as canyon_sql::core::Transaction<#ty>>::execute(stmt, update_values, input).await } }); } else { @@ -86,9 +77,7 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let querybuilder_update_tokens = generate_update_query_tokens(ty, table_schema_data); update_ops_tokens.extend(querybuilder_update_tokens); - update_ops_tokens; - - quote! {} + update_ops_tokens } /// Generates the TokenStream for the __update() CRUD operation @@ -125,17 +114,16 @@ fn generate_update_query_tokens(ty: &Ident, table_schema_data: &String) -> Token } mod __details { - - use crate::query_operations::consts::VOID_RET_TY; use crate::query_operations::doc_comments; use crate::query_operations::macro_template::MacroOperationBuilder; + use proc_macro2::{Ident, Span}; pub fn create_update_err_macro(ty: &syn::Ident) -> MacroOperationBuilder { MacroOperationBuilder::new() .fn_name("update") .with_self_as_ref() .user_type(ty) - .return_type_ts(&VOID_RET_TY.with(|v| v.borrow().clone())) + .return_type(&Ident::new("u64", Span::call_site())) .raw_return() .add_doc_comment(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) .with_direct_error_return(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) @@ -147,7 +135,7 @@ mod __details { .with_self_as_ref() .with_input_param() .user_type(ty) - .return_type_ts(&VOID_RET_TY.with(|v| v.borrow().clone())) + .return_type(&Ident::new("u64", Span::call_site())) .raw_return() .add_doc_comment(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) .with_direct_error_return(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) diff --git a/tests/crud/update_operations.rs b/tests/crud/update_operations.rs index 8eac3031..592468d5 100644 --- a/tests/crud/update_operations.rs +++ b/tests/crud/update_operations.rs @@ -1,142 +1,142 @@ -// use crate::tests_models::league::*; -// // Integration tests for the CRUD operations available in `Canyon` that -// /// generates and executes *UPDATE* statements -// use canyon_sql::crud::CrudOperations; -// -// #[cfg(feature = "mysql")] -// use crate::constants::MYSQL_DS; -// #[cfg(feature = "mssql")] -// use crate::constants::SQL_SERVER_DS; -// -// /// Update operation is a *CRUD* method defined for some entity `T`, that works by appliying -// /// some change to a Rust's entity instance, and persisting them into the database. -// /// -// /// The `t.update(&self)` operation is only enabled for types that -// /// has, at least, one of it's fields annotated with a `#[primary_key]` -// /// operation, because we use that concrete field to construct the clause that targets -// /// that entity. -// /// -// /// Attempt of usage the `t.update(&self)` method on an entity without `#[primary_key]` -// /// will raise a runtime error. -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_method_operation() { -// // We first retrieve some entity from the database. Note that we must make -// // the retrieved instance mutable of clone it to a new mutable resource -// let mut updt_candidate: League = League::find_by_pk(&1) -// .await -// .expect("[1] - Failed the query to the database") -// .expect("[1] - No entity found for the primary key value passed in"); -// -// // The ext_id field value is extracted from the sql scripts under the -// // docker/sql folder. We are retrieving the first entity inserted at the -// // wake-up time of the database, and now checking some of its properties. -// assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); -// -// // Modify the value, and perform the update -// let updt_value: i64 = 593064_i64; -// updt_candidate.ext_id = updt_value; -// updt_candidate -// .update() -// .await -// .expect("Failed the update operation"); -// -// // Retrieve it again, and check if the value was really updated -// let updt_entity: League = League::find_by_pk(&1) -// .await -// .expect("[2] - Failed the query to the database") -// .expect("[2] - No entity found for the primary key value passed in"); -// -// assert_eq!(updt_entity.ext_id, updt_value); -// -// // We roll back the changes to the initial value to don't broke other tests -// // the next time that will run -// updt_candidate.ext_id = 100695891328981122_i64; -// updt_candidate -// .update() -// .await -// .expect("Failed to restore the initial value in the psql update operation"); -// } -// -// /// Same as the above test, but with the specified datasource. -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_with_mssql_method_operation() { -// // We first retrieve some entity from the database. Note that we must make -// // the retrieved instance mutable of clone it to a new mutable resource -// let mut updt_candidate: League = League::find_by_pk_with(&1, SQL_SERVER_DS) -// .await -// .expect("[1] - Failed the query to the database") -// .expect("[1] - No entity found for the primary key value passed in"); -// -// // The ext_id field value is extracted from the sql scripts under the -// // docker/sql folder. We are retrieving the first entity inserted at the -// // wake-up time of the database, and now checking some of its properties. -// assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); -// -// // Modify the value, and perform the update -// let updt_value: i64 = 59306442534_i64; -// updt_candidate.ext_id = updt_value; -// updt_candidate -// .update_with(SQL_SERVER_DS) -// .await -// .expect("Failed the update operation"); -// -// // Retrieve it again, and check if the value was really updated -// let updt_entity: League = League::find_by_pk_with(&1, SQL_SERVER_DS) -// .await -// .expect("[2] - Failed the query to the database") -// .expect("[2] - No entity found for the primary key value passed in"); -// -// assert_eq!(updt_entity.ext_id, updt_value); -// -// // We rollback the changes to the initial value to don't broke other tests -// // the next time that will run -// updt_candidate.ext_id = 100695891328981122_i64; -// updt_candidate -// .update_with(SQL_SERVER_DS) -// .await -// .expect("Failed to restablish the initial value update operation"); -// } -// -// /// Same as the above test, but with the specified datasource. -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_with_mysql_method_operation() { -// // We first retrieve some entity from the database. Note that we must make -// // the retrieved instance mutable of clone it to a new mutable resource -// -// let mut updt_candidate: League = League::find_by_pk_with(&1, MYSQL_DS) -// .await -// .expect("[1] - Failed the query to the database") -// .expect("[1] - No entity found for the primary key value passed in"); -// -// // The ext_id field value is extracted from the sql scripts under the -// // docker/sql folder. We are retrieving the first entity inserted at the -// // wake up time of the database, and now checking some of its properties. -// assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); -// -// // Modify the value, and perform the update -// let updt_value: i64 = 59306442534_i64; -// updt_candidate.ext_id = updt_value; -// updt_candidate -// .update_with(MYSQL_DS) -// .await -// .expect("Failed the update operation"); -// -// // Retrieve it again, and check if the value was really updated -// let updt_entity: League = League::find_by_pk_with(&1, MYSQL_DS) -// .await -// .expect("[2] - Failed the query to the database") -// .expect("[2] - No entity found for the primary key value passed in"); -// -// assert_eq!(updt_entity.ext_id, updt_value); -// -// // We rollback the changes to the initial value to don't broke other tests -// // the next time that will run -// updt_candidate.ext_id = 100695891328981122_i64; -// updt_candidate -// .update_with(MYSQL_DS) -// .await -// .expect("Failed to restablish the initial value update operation"); -// } +use crate::tests_models::league::*; +// Integration tests for the CRUD operations available in `Canyon` that +/// generates and executes *UPDATE* statements +use canyon_sql::crud::CrudOperations; + +#[cfg(feature = "mysql")] +use crate::constants::MYSQL_DS; +#[cfg(feature = "mssql")] +use crate::constants::SQL_SERVER_DS; + +/// Update operation is a *CRUD* method defined for some entity `T`, that works by appliying +/// some change to a Rust's entity instance, and persisting them into the database. +/// +/// The `t.update(&self)` operation is only enabled for types that +/// has, at least, one of it's fields annotated with a `#[primary_key]` +/// operation, because we use that concrete field to construct the clause that targets +/// that entity. +/// +/// Attempt of usage the `t.update(&self)` method on an entity without `#[primary_key]` +/// will raise a runtime error. +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_update_method_operation() { + // We first retrieve some entity from the database. Note that we must make + // the retrieved instance mutable of clone it to a new mutable resource + let mut updt_candidate: League = League::find_by_pk(&1) + .await + .expect("[1] - Failed the query to the database") + .expect("[1] - No entity found for the primary key value passed in"); + + // The ext_id field value is extracted from the sql scripts under the + // docker/sql folder. We are retrieving the first entity inserted at the + // wake-up time of the database, and now checking some of its properties. + assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); + + // Modify the value, and perform the update + let updt_value: i64 = 593064_i64; + updt_candidate.ext_id = updt_value; + updt_candidate + .update() + .await + .expect("Failed the update operation"); + + // Retrieve it again, and check if the value was really updated + let updt_entity: League = League::find_by_pk(&1) + .await + .expect("[2] - Failed the query to the database") + .expect("[2] - No entity found for the primary key value passed in"); + + assert_eq!(updt_entity.ext_id, updt_value); + + // We roll back the changes to the initial value to don't broke other tests + // the next time that will run + updt_candidate.ext_id = 100695891328981122_i64; + updt_candidate + .update() + .await + .expect("Failed to restore the initial value in the psql update operation"); +} + +/// Same as the above test, but with the specified datasource. +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_update_with_mssql_method_operation() { + // We first retrieve some entity from the database. Note that we must make + // the retrieved instance mutable of clone it to a new mutable resource + let mut updt_candidate: League = League::find_by_pk_with(&1, SQL_SERVER_DS) + .await + .expect("[1] - Failed the query to the database") + .expect("[1] - No entity found for the primary key value passed in"); + + // The ext_id field value is extracted from the sql scripts under the + // docker/sql folder. We are retrieving the first entity inserted at the + // wake-up time of the database, and now checking some of its properties. + assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); + + // Modify the value, and perform the update + let updt_value: i64 = 59306442534_i64; + updt_candidate.ext_id = updt_value; + updt_candidate + .update_with(SQL_SERVER_DS) + .await + .expect("Failed the update operation"); + + // Retrieve it again, and check if the value was really updated + let updt_entity: League = League::find_by_pk_with(&1, SQL_SERVER_DS) + .await + .expect("[2] - Failed the query to the database") + .expect("[2] - No entity found for the primary key value passed in"); + + assert_eq!(updt_entity.ext_id, updt_value); + + // We roll back the changes to the initial value to don't broke other tests + // the next time that will run + updt_candidate.ext_id = 100695891328981122_i64; + updt_candidate + .update_with(SQL_SERVER_DS) + .await + .expect("Failed to restablish the initial value update operation"); +} + +/// Same as the above test, but with the specified datasource. +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_update_with_mysql_method_operation() { + // We first retrieve some entity from the database. Note that we must make + // the retrieved instance mutable of clone it to a new mutable resource + + let mut updt_candidate: League = League::find_by_pk_with(&1, MYSQL_DS) + .await + .expect("[1] - Failed the query to the database") + .expect("[1] - No entity found for the primary key value passed in"); + + // The ext_id field value is extracted from the sql scripts under the + // docker/sql folder. We are retrieving the first entity inserted at the + // wake up time of the database, and now checking some of its properties. + assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); + + // Modify the value, and perform the update + let updt_value: i64 = 59306442534_i64; + updt_candidate.ext_id = updt_value; + updt_candidate + .update_with(MYSQL_DS) + .await + .expect("Failed the update operation"); + + // Retrieve it again, and check if the value was really updated + let updt_entity: League = League::find_by_pk_with(&1, MYSQL_DS) + .await + .expect("[2] - Failed the query to the database") + .expect("[2] - No entity found for the primary key value passed in"); + + assert_eq!(updt_entity.ext_id, updt_value); + + // We rollback the changes to the initial value to don't broke other tests + // the next time that will run + updt_candidate.ext_id = 100695891328981122_i64; + updt_candidate + .update_with(MYSQL_DS) + .await + .expect("Failed to restablish the initial value update operation"); +} From 1059df65fc7d5a75caf8c162026e4e181af0232e Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 11 Feb 2025 18:23:04 +0100 Subject: [PATCH 074/155] feat: re-enabled all the crud operations again --- canyon_crud/src/crud.rs | 28 +- canyon_macros/src/query_operations/delete.rs | 16 +- tests/crud/delete_operations.rs | 318 +++++++++---------- 3 files changed, 182 insertions(+), 180 deletions(-) diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 2b6c116b..fb0a14bf 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -102,18 +102,18 @@ where where I: DbConnection + Send + 'a; - // fn delete(&self) -> impl Future>> + Send; - - // fn delete_with<'a, I>( - // &self, - // input: I, - // ) -> impl Future>> + Send - // where - // I: DbConnection + Send + 'a; - // - // fn delete_query<'a>() -> DeleteQueryBuilder<'a, T, &'a str>; - // - // fn delete_query_with<'a, I>(input: I) -> DeleteQueryBuilder<'a, T, I> - // where - // I: DbConnection + Send + 'a; + fn delete(&self) -> impl Future>> + Send; + + fn delete_with<'a, I>( + &self, + input: I, + ) -> impl Future>> + Send + where + I: DbConnection + Send + 'a; + + fn delete_query<'a>() -> DeleteQueryBuilder<'a, T, &'a str>; + + fn delete_query_with<'a, I>(input: I) -> DeleteQueryBuilder<'a, T, I> + where + I: DbConnection + Send + 'a; } diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index c1a50a0d..ffdf1e5e 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -51,12 +51,10 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri }); } - // let delete_with_querybuilder = generate_delete_query_tokens(&ty, table_schema_data); - // delete_ops_tokens.extend(delete_with_querybuilder); - // - // delete_ops_tokens - - quote! {} + let delete_with_querybuilder = generate_delete_query_tokens(ty, table_schema_data); + delete_ops_tokens.extend(delete_with_querybuilder); + + delete_ops_tokens } /// Generates the TokenStream for the __delete() CRUD operation as a @@ -92,11 +90,13 @@ fn generate_delete_query_tokens(ty: &Ident, table_schema_data: &str) -> TokenStr } } +// NOTE: The delete operations shouldn't be using TransactionMethod::QueryRows +// This should be refactored on the future mod __details { use super::*; use crate::query_operations::doc_comments; - use crate::query_operations::macro_template::MacroOperationBuilder; + use crate::query_operations::macro_template::{MacroOperationBuilder, TransactionMethod}; pub fn create_delete_macro( ty: &Ident, @@ -114,6 +114,7 @@ mod __details { .query_string(stmt) .forwarded_parameters(quote! {&[#pk_field_value]}) .propagate_transaction_result() + .with_transaction_method(TransactionMethod::QueryRows) .raw_return() .with_no_result_value() } @@ -136,6 +137,7 @@ mod __details { .query_string(stmt) .forwarded_parameters(quote! {&[#pk_field_value]}) .propagate_transaction_result() + .with_transaction_method(TransactionMethod::QueryRows) .raw_return() .with_no_result_value() } diff --git a/tests/crud/delete_operations.rs b/tests/crud/delete_operations.rs index 22ffacca..e9bb61ef 100644 --- a/tests/crud/delete_operations.rs +++ b/tests/crud/delete_operations.rs @@ -1,159 +1,159 @@ -// //! Integration tests for the CRUD operations available in `Canyon` that -// //! generates and executes *INSERT* statements -// use canyon_sql::crud::CrudOperations; -// -// #[cfg(feature = "mysql")] -// use crate::constants::MYSQL_DS; -// #[cfg(feature = "postgres")] -// use crate::constants::PSQL_DS; -// #[cfg(feature = "mssql")] -// use crate::constants::SQL_SERVER_DS; -// -// use crate::tests_models::league::*; -// -// /// Deletes a row from the database that is mapped into some instance of a `T` entity. -// /// -// /// The `t.delete(&self)` operation is only enabled for types that -// /// has, at least, one of it's fields annotated with a `#[primary_key]` -// /// operation, because we use that concrete field to construct the clause that targets -// /// that entity. -// /// -// /// Attempt of usage the `t.delete(&self)` method on an entity without `#[primary_key]` -// /// will raise a runtime error. -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_method_operation() { -// // For test the delete operation, we will insert a new instance of the database, and then, -// // after inspect it, we will proceed to delete it -// let mut new_league: League = League { -// id: Default::default(), -// ext_id: 7892635306594_i64, -// slug: "some-new-league".to_string(), -// name: "Some New League".to_string(), -// region: "Bahía de cochinos".to_string(), -// image_url: "https://nobodyspectsandimage.io".to_string(), -// }; -// -// // We insert the instance on the database, on the `League` entity -// new_league.insert().await.expect("Failed insert operation"); -// -// assert_eq!( -// new_league.id, -// League::find_by_pk_with(&new_league.id, PSQL_DS) -// .await -// .expect("Request error") -// .expect("None value") -// .id -// ); -// -// // Now that we have an instance mapped to some entity by a primary key, we can now -// // remove that entry from the database with the delete operation -// new_league -// .delete() -// .await -// .expect("Failed to delete the operation"); -// -// // To check the success, we can query by the primary key value and check if, after unwrap() -// // the result of the operation, the find by primary key contains Some(v) or None -// // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> -// assert_eq!( -// League::find_by_pk(&new_league.id) -// .await -// .expect("Unwrapping the result, letting the Option"), -// None -// ); -// } -// -// /// Same as the delete test, but performing the operations with the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_with_mssql_method_operation() { -// // For test the delete, we will insert a new instance of the database, and then, -// // after inspect it, we will proceed to delete it -// let mut new_league: League = League { -// id: Default::default(), -// ext_id: 7892635306594_i64, -// slug: "some-new-league".to_string(), -// name: "Some New League".to_string(), -// region: "Bahía de cochinos".to_string(), -// image_url: "https://nobodyspectsandimage.io".to_string(), -// }; -// -// // We insert the instance on the database, on the `League` entity -// new_league -// .insert_with(SQL_SERVER_DS) -// .await -// .expect("Failed insert operation"); -// assert_eq!( -// new_league.id, -// League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) -// .await -// .expect("Request error") -// .expect("None value") -// .id -// ); -// -// // Now that we have an instance mapped to some entity by a primary key, we can now -// // remove that entry from the database with the delete operation -// new_league -// .delete_with(SQL_SERVER_DS) -// .await -// .expect("Failed to delete the operation"); -// -// // To check the success, we can query by the primary key value and check if, after unwrap() -// // the result of the operation, the find by primary key contains Some(v) or None -// // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> -// assert_eq!( -// League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) -// .await -// .expect("Unwrapping the result, letting the Option"), -// None -// ); -// } -// -// /// Same as the delete test, but performing the operations with the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_with_mysql_method_operation() { -// // For test the delete, we will insert a new instance of the database, and then, -// // after inspect it, we will proceed to delete it -// let mut new_league: League = League { -// id: Default::default(), -// ext_id: 7892635306594_i64, -// slug: "some-new-league".to_string(), -// name: "Some New League".to_string(), -// region: "Bahía de cochinos".to_string(), -// image_url: "https://nobodyspectsandimage.io".to_string(), -// }; -// -// // We insert the instance on the database, on the `League` entity -// new_league -// .insert_with(MYSQL_DS) -// .await -// .expect("Failed insert operation"); -// assert_eq!( -// new_league.id, -// League::find_by_pk_with(&new_league.id, MYSQL_DS) -// .await -// .expect("Request error") -// .expect("None value") -// .id -// ); -// -// // Now that we have an instance mapped to some entity by a primary key, we can now -// // remove that entry from the database with the delete operation -// new_league -// .delete_with(MYSQL_DS) -// .await -// .expect("Failed to delete the operation"); -// -// // To check the success, we can query by the primary key value and check if, after unwrap() -// // the result of the operation, the find by primary key contains Some(v) or None -// // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> -// assert_eq!( -// League::find_by_pk_with(&new_league.id, MYSQL_DS) -// .await -// .expect("Unwrapping the result, letting the Option"), -// None -// ); -// } +//! Integration tests for the CRUD operations available in `Canyon` that +//! generates and executes *INSERT* statements +use canyon_sql::crud::CrudOperations; + +#[cfg(feature = "mysql")] +use crate::constants::MYSQL_DS; +#[cfg(feature = "postgres")] +use crate::constants::PSQL_DS; +#[cfg(feature = "mssql")] +use crate::constants::SQL_SERVER_DS; + +use crate::tests_models::league::*; + +/// Deletes a row from the database that is mapped into some instance of a `T` entity. +/// +/// The `t.delete(&self)` operation is only enabled for types that +/// has, at least, one of it's fields annotated with a `#[primary_key]` +/// operation, because we use that concrete field to construct the clause that targets +/// that entity. +/// +/// Attempt of usage the `t.delete(&self)` method on an entity without `#[primary_key]` +/// will raise a runtime error. +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_delete_method_operation() { + // For test the delete operation, we will insert a new instance of the database, and then, + // after inspect it, we will proceed to delete it + let mut new_league: League = League { + id: Default::default(), + ext_id: 7892635306594_i64, + slug: "some-new-league".to_string(), + name: "Some New League".to_string(), + region: "Bahía de cochinos".to_string(), + image_url: "https://nobodyspectsandimage.io".to_string(), + }; + + // We insert the instance on the database, on the `League` entity + new_league.insert().await.expect("Failed insert operation"); + + assert_eq!( + new_league.id, + League::find_by_pk_with(&new_league.id, PSQL_DS) + .await + .expect("Request error") + .expect("None value") + .id + ); + + // Now that we have an instance mapped to some entity by a primary key, we can now + // remove that entry from the database with the delete operation + new_league + .delete() + .await + .expect("Failed to delete the operation"); + + // To check the success, we can query by the primary key value and check if, after unwrap() + // the result of the operation, the find by primary key contains Some(v) or None + // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> + assert_eq!( + League::find_by_pk(&new_league.id) + .await + .expect("Unwrapping the result, letting the Option"), + None + ); +} + +/// Same as the delete test, but performing the operations with the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_delete_with_mssql_method_operation() { + // For test the delete, we will insert a new instance of the database, and then, + // after inspect it, we will proceed to delete it + let mut new_league: League = League { + id: Default::default(), + ext_id: 7892635306594_i64, + slug: "some-new-league".to_string(), + name: "Some New League".to_string(), + region: "Bahía de cochinos".to_string(), + image_url: "https://nobodyspectsandimage.io".to_string(), + }; + + // We insert the instance on the database, on the `League` entity + new_league + .insert_with(SQL_SERVER_DS) + .await + .expect("Failed insert operation"); + assert_eq!( + new_league.id, + League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) + .await + .expect("Request error") + .expect("None value") + .id + ); + + // Now that we have an instance mapped to some entity by a primary key, we can now + // remove that entry from the database with the delete operation + new_league + .delete_with(SQL_SERVER_DS) + .await + .expect("Failed to delete the operation"); + + // To check the success, we can query by the primary key value and check if, after unwrap() + // the result of the operation, the find by primary key contains Some(v) or None + // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> + assert_eq!( + League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) + .await + .expect("Unwrapping the result, letting the Option"), + None + ); +} + +/// Same as the delete test, but performing the operations with the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_delete_with_mysql_method_operation() { + // For test the delete, we will insert a new instance of the database, and then, + // after inspect it, we will proceed to delete it + let mut new_league: League = League { + id: Default::default(), + ext_id: 7892635306594_i64, + slug: "some-new-league".to_string(), + name: "Some New League".to_string(), + region: "Bahía de cochinos".to_string(), + image_url: "https://nobodyspectsandimage.io".to_string(), + }; + + // We insert the instance on the database, on the `League` entity + new_league + .insert_with(MYSQL_DS) + .await + .expect("Failed insert operation"); + assert_eq!( + new_league.id, + League::find_by_pk_with(&new_league.id, MYSQL_DS) + .await + .expect("Request error") + .expect("None value") + .id + ); + + // Now that we have an instance mapped to some entity by a primary key, we can now + // remove that entry from the database with the delete operation + new_league + .delete_with(MYSQL_DS) + .await + .expect("Failed to delete the operation"); + + // To check the success, we can query by the primary key value and check if, after unwrap() + // the result of the operation, the find by primary key contains Some(v) or None + // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> + assert_eq!( + League::find_by_pk_with(&new_league.id, MYSQL_DS) + .await + .expect("Unwrapping the result, letting the Option"), + None + ); +} From 61621b87ae886bd5a1afe72c53dd4217ba9f1de2 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 12 Feb 2025 11:30:55 +0100 Subject: [PATCH 075/155] fix: mssql parameter binding on query methods --- .../src/connection/db_clients/mssql.rs | 55 +++---------------- .../src/connection/db_clients/mysql.rs | 4 +- .../src/connection/db_clients/postgresql.rs | 18 ++++-- canyon_core/src/connection/db_connector.rs | 8 +-- canyon_macros/src/query_operations/delete.rs | 4 +- .../src/query_operations/macro_template.rs | 3 + 6 files changed, 34 insertions(+), 58 deletions(-) diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs index 33055325..97dc79d8 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -1,7 +1,7 @@ use crate::connection::database_type::DatabaseType; use crate::connection::db_connector::DbConnection; use crate::mapper::RowMapper; -use crate::rows::{FromSql, FromSqlOwnedValue}; +use crate::rows::FromSqlOwnedValue; use crate::{query_parameters::QueryParameter, rows::CanyonRows}; #[cfg(feature = "mssql")] use async_std::net::TcpStream; @@ -28,7 +28,7 @@ impl DbConnection for SqlServerConnection { fn query<'a, S, R: RowMapper>( &self, stmt: S, - params: &[&'a (dyn QueryParameter<'_>)], + params: &[&'a (dyn QueryParameter<'a>)], ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where S: AsRef + Display + Send, @@ -73,12 +73,12 @@ pub(crate) mod sqlserver_query_launcher { use super::*; use crate::mapper::RowMapper; use crate::rows::FromSqlOwnedValue; - use tiberius::{ColumnData, QueryStream}; + use tiberius::{ColumnData, IntoSql, QueryStream}; #[inline(always)] pub(crate) async fn query<'a, S, R: RowMapper>( stmt: S, - params: &[&'a dyn QueryParameter<'_>], + params: &[&'a dyn QueryParameter<'a>], conn: &SqlServerConnection, ) -> Result, Box<(dyn Error + Sync + Send)>> where @@ -167,7 +167,7 @@ pub(crate) mod sqlserver_query_launcher { async fn execute_query<'a>( stmt: &str, - params: &[&'a (dyn QueryParameter<'_>)], + params: &[&'a (dyn QueryParameter<'a>)], conn: &SqlServerConnection, ) -> Result, Box<(dyn Error + Send + Sync)>> { let mssql_query = generate_mssql_stmt(stmt, params).await; @@ -180,7 +180,7 @@ pub(crate) mod sqlserver_query_launcher { async fn generate_mssql_stmt<'a>( stmt: &str, - params: &[&'a dyn QueryParameter<'_>], + params: &[&'a (dyn QueryParameter<'a>)], ) -> Query<'a> { let mut stmt = String::from(stmt); if stmt.contains("RETURNING") { @@ -196,47 +196,10 @@ pub(crate) mod sqlserver_query_launcher { ); } - // TODO: We must address the query generation. Look at the returning example, or the - // replace below. We may use our own type Query to address this concerns when the query - // is generated + // TODO: We must address the query generation let mut mssql_query = Query::new(stmt.to_owned().replace('$', "@P")); - params.iter().for_each(|param| { - let column_data = param.as_sqlserver_param(); - match column_data { - ColumnData::U8(v) => mssql_query.bind(v), - ColumnData::I16(v) => mssql_query.bind(v), - ColumnData::I32(v) => mssql_query.bind(v), - ColumnData::I64(v) => mssql_query.bind(v), - ColumnData::F32(v) => mssql_query.bind(v), - ColumnData::F64(v) => mssql_query.bind(v), - ColumnData::Bit(v) => mssql_query.bind(v), - ColumnData::String(v) => mssql_query.bind(v), - ColumnData::Guid(v) => mssql_query.bind(v), - ColumnData::Binary(v) => mssql_query.bind(v), - ColumnData::Numeric(v) => mssql_query.bind(v), - ColumnData::Xml(v) => mssql_query.bind(v.as_deref().map(ToString::to_string)), - ColumnData::DateTime(v) => { - todo!() - } - ColumnData::SmallDateTime(v) => { - todo!() - } - ColumnData::Time(v) => { - todo!() - } - ColumnData::Date(v) => { - todo!() - } - ColumnData::DateTime2(v) => { - todo!() - } - ColumnData::DateTimeOffset(v) => { - todo!() - } - } - // mssql_query.bind() - }); - + params.iter().for_each(|param| { mssql_query.bind(*param); }); + mssql_query } } diff --git a/canyon_core/src/connection/db_clients/mysql.rs b/canyon_core/src/connection/db_clients/mysql.rs index 8c56c3af..3fa9b0e3 100644 --- a/canyon_core/src/connection/db_clients/mysql.rs +++ b/canyon_core/src/connection/db_clients/mysql.rs @@ -1,7 +1,7 @@ use crate::connection::database_type::DatabaseType; use crate::connection::db_connector::DbConnection; use crate::mapper::RowMapper; -use crate::rows::{FromSql, FromSqlOwnedValue}; +use crate::rows::FromSqlOwnedValue; use crate::{query_parameters::QueryParameter, rows::CanyonRows}; #[cfg(feature = "mysql")] use mysql_async::Pool; @@ -30,7 +30,7 @@ impl DbConnection for MysqlConnection { fn query<'a, S, R: RowMapper>( &self, stmt: S, - params: &[&'a (dyn QueryParameter<'_>)], + params: &[&'a (dyn QueryParameter<'a>)], ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where S: AsRef + Display + Send, diff --git a/canyon_core/src/connection/db_clients/postgresql.rs b/canyon_core/src/connection/db_clients/postgresql.rs index ceaff086..76b7359a 100644 --- a/canyon_core/src/connection/db_clients/postgresql.rs +++ b/canyon_core/src/connection/db_clients/postgresql.rs @@ -1,7 +1,7 @@ use crate::connection::database_type::DatabaseType; use crate::connection::db_connector::DbConnection; use crate::mapper::RowMapper; -use crate::rows::{FromSql, FromSqlOwnedValue}; +use crate::rows::FromSqlOwnedValue; use crate::{query_parameters::QueryParameter, rows::CanyonRows}; use std::error::Error; use std::fmt::Display; @@ -28,7 +28,7 @@ impl DbConnection for PostgreSqlConnection { fn query<'a, S, R: RowMapper>( &self, stmt: S, - params: &[&'a (dyn QueryParameter<'_>)], + params: &[&'a (dyn QueryParameter<'a>)], ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where S: AsRef + Display + Send, @@ -70,6 +70,7 @@ impl DbConnection for PostgreSqlConnection { #[cfg(feature = "postgres")] pub(crate) mod postgres_query_launcher { + use super::*; use crate::rows::FromSqlOwnedValue; use tokio_postgres::types::ToSql; @@ -106,6 +107,8 @@ pub(crate) mod postgres_query_launcher { Ok(CanyonRows::Postgres(r)) } + /// *NOTE*: implementation details of `query_one` when handling errors are + /// discussed [here](https://github.com/sfackler/rust-postgres/issues/790#issuecomment-2095729043) #[inline(always)] pub(crate) async fn query_one<'a, T: RowMapper>( stmt: &str, @@ -116,8 +119,15 @@ pub(crate) mod postgres_query_launcher { .iter() .map(|param| param.as_postgres_param()) .collect(); - let r = conn.client.query_one(stmt, m_params.as_slice()).await?; - Ok(Some(T::deserialize_postgresql(&r))) + let result = conn.client.query_one(stmt, m_params.as_slice()).await; + + match result { + Ok(row) => { Ok(Some(T::deserialize_postgresql(&row))) }, + Err(e) => match e.to_string().contains("unexpected number of rows") { + true => { Ok(None) }, + _ => Err(e)?, + } + } } #[inline(always)] diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index 191cadea..5a38c67c 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -27,7 +27,7 @@ pub trait DbConnection { fn query<'a, S, R: RowMapper>( &self, stmt: S, - params: &[&'a (dyn QueryParameter<'_>)], + params: &[&'a (dyn QueryParameter<'a>)], ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where S: AsRef + Display + Send; @@ -78,7 +78,7 @@ impl DbConnection for &str { async fn query<'a, S, R: RowMapper>( &self, stmt: S, - params: &[&'a (dyn QueryParameter<'_>)], + params: &[&'a (dyn QueryParameter<'a>)], ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, @@ -148,7 +148,7 @@ impl DbConnection for DatabaseConnection { async fn query<'a, S, R: RowMapper>( &self, stmt: S, - params: &[&'a (dyn QueryParameter<'_>)], + params: &[&'a (dyn QueryParameter<'a>)], ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, @@ -222,7 +222,7 @@ impl DbConnection for &mut DatabaseConnection { async fn query<'a, S, R: RowMapper>( &self, stmt: S, - params: &[&'a (dyn QueryParameter<'_>)], + params: &[&'a (dyn QueryParameter<'a>)], ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index ffdf1e5e..25a4c5f8 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -114,7 +114,7 @@ mod __details { .query_string(stmt) .forwarded_parameters(quote! {&[#pk_field_value]}) .propagate_transaction_result() - .with_transaction_method(TransactionMethod::QueryRows) + .with_transaction_method(TransactionMethod::Execute) .raw_return() .with_no_result_value() } @@ -137,7 +137,7 @@ mod __details { .query_string(stmt) .forwarded_parameters(quote! {&[#pk_field_value]}) .propagate_transaction_result() - .with_transaction_method(TransactionMethod::QueryRows) + .with_transaction_method(TransactionMethod::Execute) .raw_return() .with_no_result_value() } diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index a070359a..6a1fcaf8 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -2,11 +2,13 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; #[derive(Debug, Copy, Clone)] +#[allow(dead_code)] pub enum TransactionMethod { Query, QueryOne, QueryOneFor, QueryRows, + Execute } impl ToTokens for TransactionMethod { @@ -16,6 +18,7 @@ impl ToTokens for TransactionMethod { TransactionMethod::QueryOne => tokens.extend(quote! {query_one}), TransactionMethod::QueryOneFor => tokens.extend(quote! {query_one_for}), TransactionMethod::QueryRows => tokens.extend(quote! {query_rows}), + TransactionMethod::Execute => tokens.extend(quote! {execute}), } } } From 7eb21a135742047ebcef776188b9ac6fdf0e4cef Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 12 Feb 2025 11:34:37 +0100 Subject: [PATCH 076/155] chore: the macro template uses async fn syntax --- .../src/query_operations/macro_template.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index 6a1fcaf8..322153d0 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -203,7 +203,7 @@ impl MacroOperationBuilder { quote! { #container_ret_type<#organic_ret_type> } }; - let expected_data = match &self.with_unwrap { + match &self.with_unwrap { // TODO: distinguish collection from rows with only results true => quote! { #ret_type }, false => { @@ -215,9 +215,7 @@ impl MacroOperationBuilder { quote! { Result<#ret_type, #err_variant> } } - }; - - quote! { impl std::future::Future + Send } + } } fn get_where_clause_bounds(&self) -> TokenStream { @@ -404,7 +402,7 @@ impl MacroOperationBuilder { quote! { #(#doc_comments)* - fn #fn_name #generics( + async fn #fn_name #generics( #as_method #separate_self_params #fn_parameters @@ -413,10 +411,8 @@ impl MacroOperationBuilder { ) -> #return_type #where_clause { - async move { - #body_tokens - #unwrap - } + #body_tokens + #unwrap } } } From 867f4a705375c4381669fd0d98c2655e97f0f98e Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 12 Feb 2025 12:07:00 +0100 Subject: [PATCH 077/155] chore: cleaning clippy warnings --- canyon_core/src/column.rs | 2 +- .../src/connection/db_clients/mssql.rs | 2 +- .../src/connection/db_clients/mysql.rs | 4 +- .../src/connection/db_clients/postgresql.rs | 8 +- canyon_core/src/connection/db_connector.rs | 18 +-- canyon_core/src/connection/mod.rs | 6 +- canyon_macros/src/canyon_entity_macro.rs | 6 +- canyon_macros/src/canyon_mapper_macro.rs | 21 ++-- canyon_macros/src/foreignkeyable_macro.rs | 1 - canyon_macros/src/query_operations/delete.rs | 6 +- .../src/query_operations/foreign_key.rs | 117 ++++++++---------- .../src/query_operations/macro_template.rs | 7 -- canyon_macros/src/query_operations/read.rs | 4 +- canyon_macros/src/query_operations/update.rs | 8 +- canyon_migrations/src/migrations/memory.rs | 4 +- 15 files changed, 91 insertions(+), 123 deletions(-) diff --git a/canyon_core/src/column.rs b/canyon_core/src/column.rs index da01128a..502b9a35 100644 --- a/canyon_core/src/column.rs +++ b/canyon_core/src/column.rs @@ -14,7 +14,7 @@ pub struct Column<'a> { pub(crate) name: Cow<'a, str>, pub(crate) type_: ColumnType, } -impl<'a> Column<'a> { +impl Column<'_> { pub fn name(&self) -> &str { &self.name } diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs index 97dc79d8..d01ced7d 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -73,7 +73,7 @@ pub(crate) mod sqlserver_query_launcher { use super::*; use crate::mapper::RowMapper; use crate::rows::FromSqlOwnedValue; - use tiberius::{ColumnData, IntoSql, QueryStream}; + use tiberius::QueryStream; #[inline(always)] pub(crate) async fn query<'a, S, R: RowMapper>( diff --git a/canyon_core/src/connection/db_clients/mysql.rs b/canyon_core/src/connection/db_clients/mysql.rs index 3fa9b0e3..a4804398 100644 --- a/canyon_core/src/connection/db_clients/mysql.rs +++ b/canyon_core/src/connection/db_clients/mysql.rs @@ -83,9 +83,9 @@ pub(crate) mod mysql_query_launcher { use std::sync::Arc; #[inline(always)] - pub async fn query<'a, S, R: RowMapper>( + pub async fn query>( stmt: S, - params: &[&'a dyn QueryParameter<'_>], + params: &[&'_ dyn QueryParameter<'_>], conn: &MysqlConnection, ) -> Result, Box<(dyn Error + Sync + Send)>> where diff --git a/canyon_core/src/connection/db_clients/postgresql.rs b/canyon_core/src/connection/db_clients/postgresql.rs index 76b7359a..23f7807d 100644 --- a/canyon_core/src/connection/db_clients/postgresql.rs +++ b/canyon_core/src/connection/db_clients/postgresql.rs @@ -76,9 +76,9 @@ pub(crate) mod postgres_query_launcher { use tokio_postgres::types::ToSql; #[inline(always)] - pub(crate) async fn query<'a, S, R: RowMapper>( + pub(crate) async fn query>( stmt: S, - params: &[&'a (dyn QueryParameter<'_>)], + params: &[&'_ (dyn QueryParameter<'_>)], conn: &PostgreSqlConnection, ) -> Result, Box<(dyn Error + Sync + Send)>> where @@ -145,9 +145,9 @@ pub(crate) mod postgres_query_launcher { } #[inline(always)] - pub(crate) async fn execute<'a, S>( + pub(crate) async fn execute( stmt: S, - params: &[&'a (dyn QueryParameter<'_>)], + params: &[&'_ (dyn QueryParameter<'_>)], conn: &PostgreSqlConnection, ) -> Result> where diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index 5a38c67c..db18dba0 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -12,7 +12,7 @@ use crate::connection::db_connector::connection_helpers::{ use crate::connection::{find_datasource_by_name_or_try_default, get_database_connection_by_ds}; use crate::mapper::RowMapper; use crate::query_parameters::QueryParameter; -use crate::rows::{CanyonRows, FromSql, FromSqlOwnedValue}; +use crate::rows::{CanyonRows, FromSqlOwnedValue}; use std::error::Error; use std::fmt::Display; use std::future::Future; @@ -479,9 +479,9 @@ mod auth { use crate::connection::datasources::SqlServerAuth; #[cfg(feature = "postgres")] - pub fn extract_postgres_auth<'a>( - auth: &'a Auth, - ) -> Result<(&'a str, &'a str), Box<(dyn std::error::Error + Send + Sync)>> { + pub fn extract_postgres_auth( + auth: &Auth, + ) -> Result<(&str, &str), Box<(dyn std::error::Error + Send + Sync)>> { match auth { Auth::Postgres(pg_auth) => match pg_auth { PostgresAuth::Basic { username, password } => Ok((username, password)), @@ -492,8 +492,8 @@ mod auth { } #[cfg(feature = "mssql")] - pub fn extract_mssql_auth<'a>( - auth: &'a Auth, + pub fn extract_mssql_auth( + auth: &Auth, ) -> Result> { match auth { Auth::SqlServer(sql_server_auth) => match sql_server_auth { @@ -508,9 +508,9 @@ mod auth { } #[cfg(feature = "mysql")] - pub fn extract_mysql_auth<'a>( - auth: &'a Auth, - ) -> Result<(&'a str, &'a str), Box<(dyn std::error::Error + Send + Sync)>> { + pub fn extract_mysql_auth( + auth: &Auth, + ) -> Result<(&str, &str), Box<(dyn std::error::Error + Send + Sync)>> { match auth { Auth::MySQL(mysql_auth) => match mysql_auth { MySQLAuth::Basic { username, password } => Ok((username, password)), diff --git a/canyon_core/src/connection/mod.rs b/canyon_core/src/connection/mod.rs index ac3b66d3..e25519d6 100644 --- a/canyon_core/src/connection/mod.rs +++ b/canyon_core/src/connection/mod.rs @@ -98,14 +98,14 @@ pub async fn init_connections_cache() { // user code determine whenever you can find a valid datasource via a concrete type instead of an string? // TODO: doc (main way for the user to obtain a db connection given a datasource identifier) -pub async fn get_database_connection_by_ds<'a>( - datasource_name: Option<&'a str>, +pub async fn get_database_connection_by_ds( + datasource_name: Option<&str>, ) -> Result> { let ds = find_datasource_by_name_or_try_default(datasource_name)?; DatabaseConnection::new(ds).await } -pub fn find_datasource_by_name_or_try_default<'a>( +pub fn find_datasource_by_name_or_try_default( datasource_name: Option<&str>, // TODO: with the new inputs, we don't want anymore this as Option ) -> Result<&DatasourceConfig, DatasourceNotFound> { let datasource_name = datasource_name.filter(|&ds_name| !ds_name.is_empty()); diff --git a/canyon_macros/src/canyon_entity_macro.rs b/canyon_macros/src/canyon_entity_macro.rs index 0d428900..4357e3ad 100644 --- a/canyon_macros/src/canyon_entity_macro.rs +++ b/canyon_macros/src/canyon_entity_macro.rs @@ -20,8 +20,7 @@ pub fn generate_canyon_entity_tokens( if entity_res.is_err() { return entity_res .expect_err("Unexpected error parsing the struct") - .into_compile_error() - .into(); + .into_compile_error(); } // No errors detected on the parsing, so we can safely unwrap the parse result @@ -70,9 +69,8 @@ pub fn generate_canyon_entity_tokens( #macro_error #generated_user_struct } - .into() } else { - tokens.into() + tokens } } diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index 85de4c09..cc2a08df 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -19,7 +19,6 @@ pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { _ => { return syn::Error::new(ast.ident.span(), "CanyonMapper only works with Structs") .to_compile_error() - .into() } }); @@ -64,8 +63,9 @@ pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { } #[cfg(feature = "postgres")] +#[allow(clippy::type_complexity)] fn create_postgres_fields_mapping( - fields: &Vec<(Visibility, Ident, Type)>, + fields: &[(Visibility, Ident, Type)], ) -> Map, fn(&'_ (Visibility, Ident, Type)) -> TokenStream> { fields.iter().map(|(_vis, ident, _ty)| { let ident_name = ident.to_string(); @@ -77,8 +77,8 @@ fn create_postgres_fields_mapping( } #[cfg(feature = "mysql")] -fn create_mysql_fields_mapping( - fields: &Vec<(Visibility, Ident, Type)>, +#[allow(clippy::type_complexity)]fn create_mysql_fields_mapping( + fields: &[(Visibility, Ident, Type)], ) -> Map, fn(&'_ (Visibility, Ident, Type)) -> TokenStream> { fields.iter().map(|(_vis, ident, _ty)| { let ident_name = ident.to_string(); @@ -90,10 +90,11 @@ fn create_mysql_fields_mapping( } #[cfg(feature = "mssql")] +#[allow(clippy::type_complexity)] fn create_sqlserver_fields_mapping( - fields: &Vec<(Visibility, Ident, Type)>, + fields: &[(Visibility, Ident, Type)], ) -> Map, fn(&'_ (Visibility, Ident, Type)) -> TokenStream> { - fields.into_iter().map(|(_vis, ident, ty)| { + fields.iter().map(|(_vis, ident, ty)| { let ident_name = ident.to_string(); let target_field_type_str = get_field_type_as_string(ty); @@ -140,7 +141,7 @@ fn handle_stupid_tiberius_sql_conversions(target_type: &str, ident_name: &str) - #[cfg(feature = "mssql")] fn get_deserializing_type(target_type: &str) -> TokenStream { let re = Regex::new(r"(?:Option\s*<\s*)?(?P&?\w+)(?:\s*>)?").unwrap(); - re.captures(&*target_type) + re.captures(target_type) .map(|inner| String::from(&inner["type"])) .map(|tt| { if BY_VALUE_CONVERSION_TARGETS.contains(&tt.as_str()) { @@ -154,10 +155,8 @@ fn get_deserializing_type(target_type: &str) -> TokenStream { quote! { #tt } } }) - .expect(&format!( - "Unable to process type: {} on the given struct for SqlServer", - target_type - )) + .unwrap_or_else(|| panic!("Unable to process type: {} on the given struct for SqlServer", + target_type)) } #[cfg(feature = "mssql")] diff --git a/canyon_macros/src/foreignkeyable_macro.rs b/canyon_macros/src/foreignkeyable_macro.rs index 3e838b71..a3415ec5 100644 --- a/canyon_macros/src/foreignkeyable_macro.rs +++ b/canyon_macros/src/foreignkeyable_macro.rs @@ -12,7 +12,6 @@ pub fn foreignkeyable_impl_tokens(ast: DeriveInput) -> TokenStream { _ => { return syn::Error::new(ty.span(), "ForeignKeyable only works with Structs") .to_compile_error() - .into() } }); diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index 25a4c5f8..7939675d 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -170,9 +170,9 @@ mod __details { mod delete_tests { use super::__details::*; use crate::query_operations::consts::*; - use proc_macro2::Span; - use quote::quote; - use syn::Ident; + + + const DELETE_MOCK_STMT: &str = "DELETE FROM public.user WHERE user.id = 1"; diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index 9f78f3c3..9499c67b 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -81,12 +81,12 @@ fn generate_find_by_foreign_key_tokens( proc_macro2::Span::call_site(), ); let quoted_method_signature: TokenStream = quote! { - fn #method_name_ident<'a>(&self) -> - impl std::future::Future, Box<(dyn std::error::Error + Sync + Send + 'a)>>> + Send + async fn #method_name_ident<'a>(&self) -> + Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> }; let quoted_with_method_signature: TokenStream = quote! { - fn #method_name_ident_with<'a, I>(&self, input: I) -> - impl std::future::Future, Box<(dyn std::error::Error + Sync + Send + 'a)>>> + Send + async fn #method_name_ident_with<'a, I>(&self, input: I) -> + Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> where I: canyon_sql::core::DbConnection + Send + 'a }; @@ -95,27 +95,16 @@ fn generate_find_by_foreign_key_tokens( table, format!("\"{column}\"").as_str(), ); - let result_handler = quote! { - match result { - n if n.len() == 0 => Ok(None), - _ => Ok(Some( - result.into_results::<#fk_ty>().remove(0) - )) - } - }; - fk_quotes.push(( quote! { #quoted_method_signature; }, quote! { /// Searches the parent entity (if exists) for this type #quoted_method_signature { - async move { - <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query_one( - #stmt, - &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], - "" - ).await - } + <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query_one( + #stmt, + &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], + "" + ).await } }, )); @@ -125,13 +114,11 @@ fn generate_find_by_foreign_key_tokens( quote! { /// Searches the parent entity (if exists) for this type with the specified datasource #quoted_with_method_signature { - async move { - <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query_one( - #stmt, - &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], - input - ).await - } + <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query_one( + #stmt, + &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], + input + ).await } }, )); @@ -164,12 +151,12 @@ fn generate_find_by_reverse_foreign_key_tokens( proc_macro2::Span::call_site(), ); let quoted_method_signature: TokenStream = quote! { - fn #method_name_ident<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send>(value: &F) -> - impl std::future::Future, Box<(dyn std::error::Error + Sync + Send + 'a)>>> + Send + async fn #method_name_ident<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send>(value: &F) -> + Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> }; let quoted_with_method_signature: TokenStream = quote! { - fn #method_name_ident_with<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send, I> (value: &F, input: I) - -> impl std::future::Future, Box<(dyn std::error::Error + Sync + Send + 'a)>>> + Send + async fn #method_name_ident_with<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send, I> (value: &F, input: I) + -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> where I: canyon_sql::core::DbConnection + Send + 'a }; @@ -182,24 +169,22 @@ fn generate_find_by_reverse_foreign_key_tokens( /// performs a search to find the children that belong to that concrete parent. #quoted_method_signature { - async move { - let lookage_value = value.get_fk_column(#column) - .expect(format!( - "Column: {:?} not found in type: {:?}", #column, #table - ).as_str()); - - let stmt = format!( - "SELECT * FROM {} WHERE {} = $1", - #table_schema_data, - format!("\"{}\"", #f_ident).as_str() - ); - - <#ty as canyon_sql::core::Transaction<#ty>>::query( - stmt, - &[lookage_value], - "" - ).await - } + let lookage_value = value.get_fk_column(#column) + .expect(format!( + "Column: {:?} not found in type: {:?}", #column, #table + ).as_str()); + + let stmt = format!( + "SELECT * FROM {} WHERE {} = $1", + #table_schema_data, + format!("\"{}\"", #f_ident).as_str() + ); + + <#ty as canyon_sql::core::Transaction<#ty>>::query( + stmt, + &[lookage_value], + "" + ).await } }, )); @@ -212,24 +197,22 @@ fn generate_find_by_reverse_foreign_key_tokens( /// with the specified datasource. #quoted_with_method_signature { - async move { - let lookage_value = value.get_fk_column(#column) - .expect(format!( - "Column: {:?} not found in type: {:?}", #column, #table - ).as_str()); - - let stmt = format!( - "SELECT * FROM {} WHERE {} = $1", - #table_schema_data, - format!("\"{}\"", #f_ident).as_str() - ); - - <#ty as canyon_sql::core::Transaction<#ty>>::query( - stmt, - &[lookage_value], - input - ).await - } + let lookage_value = value.get_fk_column(#column) + .expect(format!( + "Column: {:?} not found in type: {:?}", #column, #table + ).as_str()); + + let stmt = format!( + "SELECT * FROM {} WHERE {} = $1", + #table_schema_data, + format!("\"{}\"", #f_ident).as_str() + ); + + <#ty as canyon_sql::core::Transaction<#ty>>::query( + stmt, + &[lookage_value], + input + ).await } }, )); diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index 322153d0..cca36bd9 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -34,7 +34,6 @@ pub struct MacroOperationBuilder { return_type_ts: Option, where_clause_bounds: Vec, doc_comments: Vec, - body_tokens: Option, query_string: Option, input_parameters: Option, forwarded_parameters: Option, @@ -69,7 +68,6 @@ impl MacroOperationBuilder { return_type_ts: None, where_clause_bounds: Vec::new(), doc_comments: Vec::new(), - body_tokens: None, query_string: None, input_parameters: None, forwarded_parameters: None, @@ -318,11 +316,6 @@ impl MacroOperationBuilder { self } - pub fn disable_mapping(mut self) -> Self { - self.enable_mapping = true; - self - } - pub fn raw_return(mut self) -> Self { self.raw_return = true; self diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index aa01b962..2815faf1 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -284,7 +284,7 @@ mod __details { pub mod pk_generators { use super::*; use crate::query_operations::macro_template::TransactionMethod; - use proc_macro2::TokenStream; + pub fn create_find_by_pk_macro(ty: &Ident, stmt: &str) -> MacroOperationBuilder { MacroOperationBuilder::new() @@ -320,7 +320,7 @@ mod __details { mod macro_builder_read_ops_tests { use super::__details::{count_generators::*, find_all_generators::*, pk_generators::*}; use crate::query_operations::consts::*; - use quote::quote; + const SELECT_ALL_STMT: &str = "SELECT * FROM public.user"; // TODO: introduce the const_format crate const COUNT_STMT: &str = "SELECT COUNT(*) FROM public.user"; diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index 05dedc4c..41de2f5a 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -26,11 +26,7 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let update_values_cloned = update_values.clone(); if let Some(primary_key) = macro_data.get_primary_key_annotation() { - let pk_ident = Ident::new(&primary_key, Span::call_site()); // TODO get it - // from macro_data in the future, saving operations - let pk_index = macro_data - .get_pk_index() - .expect("Update method failed to retrieve the index of the primary key"); + let pk_ident = Ident::new(&primary_key, Span::call_site()); update_ops_tokens.extend(quote! { /// Updates a database record that matches @@ -145,7 +141,7 @@ mod __details { #[cfg(test)] mod update_tokens_tests { use crate::query_operations::consts::{ - INPUT_PARAM, LT_CONSTRAINT, RES_VOID_RET_TY, RES_VOID_RET_TY_LT, USER_MOCK_TY, VOID_RET_TY, + INPUT_PARAM, LT_CONSTRAINT, RES_VOID_RET_TY, RES_VOID_RET_TY_LT, USER_MOCK_TY, }; use crate::query_operations::update::__details::{ create_update_err_macro, create_update_err_with_macro, diff --git a/canyon_migrations/src/migrations/memory.rs b/canyon_migrations/src/migrations/memory.rs index eea809fa..29ad342c 100644 --- a/canyon_migrations/src/migrations/memory.rs +++ b/canyon_migrations/src/migrations/memory.rs @@ -195,6 +195,7 @@ impl CanyonMemory { &mut self, canyon_entities: &[CanyonRegisterEntity<'_>], ) { + let re = Regex::new(r#"\bstruct\s+(\w+)"#).unwrap(); for file in WalkDir::new("./src") .into_iter() .filter_map(|file| file.ok()) @@ -215,8 +216,7 @@ impl CanyonMemory { { canyon_entity_macro_counter += 1; } - - let re = Regex::new(r#"\bstruct\s+(\w+)"#).unwrap(); + if let Some(captures) = re.captures(line) { struct_name.push_str(captures.get(1).unwrap().as_str()); } From 253e32e855c4e597e8d9327150d21c877f9e6a62 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 12 Feb 2025 13:32:28 +0100 Subject: [PATCH 078/155] chore: starting to clean the code around canyon connection and its exposed APIs --- canyon_core/src/connection/database_type.rs | 9 +- canyon_core/src/connection/datasources.rs | 36 +++- canyon_core/src/connection/lib.rs | 162 ------------------ canyon_core/src/connection/mod.rs | 19 +- canyon_migrations/src/migrations/handler.rs | 23 +-- canyon_migrations/src/migrations/processor.rs | 9 +- src/lib.rs | 1 - 7 files changed, 38 insertions(+), 221 deletions(-) delete mode 100644 canyon_core/src/connection/lib.rs diff --git a/canyon_core/src/connection/database_type.rs b/canyon_core/src/connection/database_type.rs index 99478664..a7730129 100644 --- a/canyon_core/src/connection/database_type.rs +++ b/canyon_core/src/connection/database_type.rs @@ -18,13 +18,6 @@ pub enum DatabaseType { impl From<&Auth> for DatabaseType { fn from(value: &Auth) -> Self { - match value { - #[cfg(feature = "postgres")] - Auth::Postgres(_) => DatabaseType::PostgreSql, - #[cfg(feature = "mssql")] - Auth::SqlServer(_) => DatabaseType::SqlServer, - #[cfg(feature = "mysql")] - Auth::MySQL(_) => DatabaseType::MySQL, - } + value.get_db_type() } } diff --git a/canyon_core/src/connection/datasources.rs b/canyon_core/src/connection/datasources.rs index 63366636..560789a0 100644 --- a/canyon_core/src/connection/datasources.rs +++ b/canyon_core/src/connection/datasources.rs @@ -2,7 +2,7 @@ use serde::Deserialize; use super::database_type::DatabaseType; -/// ``` +/// ```rust #[test] fn load_ds_config_from_array() { #[cfg(feature = "postgres")] @@ -112,14 +112,13 @@ pub struct DatasourceConfig { impl DatasourceConfig { pub fn get_db_type(&self) -> DatabaseType { - match self.auth { - #[cfg(feature = "postgres")] - Auth::Postgres(_) => DatabaseType::PostgreSql, - #[cfg(feature = "mssql")] - Auth::SqlServer(_) => DatabaseType::SqlServer, - #[cfg(feature = "mysql")] - Auth::MySQL(_) => DatabaseType::MySQL, - } + self.auth.get_db_type() + } + + pub fn has_migrations_enabled(&self) -> bool { + if let Some(migrations) = self.properties.migrations { + migrations.has_migrations_enabled() + } else { false } } } @@ -136,6 +135,19 @@ pub enum Auth { MySQL(MySQLAuth), } +impl Auth { + pub fn get_db_type(&self) -> DatabaseType { + match self { + #[cfg(feature = "postgres")] + Auth::Postgres(_) => DatabaseType::PostgreSql, + #[cfg(feature = "mssql")] + Auth::SqlServer(_) => DatabaseType::SqlServer, + #[cfg(feature = "mysql")] + Auth::MySQL(_) => DatabaseType::MySQL, + } + } +} + #[derive(Deserialize, Debug, Clone, PartialEq)] #[cfg(feature = "postgres")] pub enum PostgresAuth { @@ -176,6 +188,12 @@ pub enum Migrations { Disabled, } +impl Migrations { + pub fn has_migrations_enabled(&self) -> bool { + matches!(self, Migrations::Enabled) + } +} + #[cfg(test)] mod datasources_tests { use super::*; diff --git a/canyon_core/src/connection/lib.rs b/canyon_core/src/connection/lib.rs deleted file mode 100644 index b6298c8c..00000000 --- a/canyon_core/src/connection/lib.rs +++ /dev/null @@ -1,162 +0,0 @@ -#[cfg(feature = "postgres")] -pub extern crate tokio_postgres; - -#[cfg(feature = "mssql")] -pub extern crate async_std; -#[cfg(feature = "mssql")] -pub extern crate tiberius; - -#[cfg(feature = "mysql")] -pub extern crate mysql_async; - -pub extern crate futures; -pub extern crate lazy_static; -pub extern crate tokio; -pub extern crate tokio_util; - -pub mod conn_errors; -pub mod database_type; -pub mod datasources; -pub mod db_clients; -pub mod db_connector; - -use std::fmt::Debug; -use std::path::PathBuf; -use std::{error::Error, fs}; - -use crate::datasources::{CanyonSqlConfig, DatasourceConfig}; -use conn_errors::DatasourceNotFound; -use db_connector::DatabaseConnection; -use indexmap::IndexMap; -use lazy_static::lazy_static; -use tokio::sync::{Mutex, MutexGuard}; -use walkdir::WalkDir; - -lazy_static! { - pub static ref CANYON_TOKIO_RUNTIME: tokio::runtime::Runtime = - tokio::runtime::Runtime::new() // TODO Make the config with the builder - .expect("Failed initializing the Canyon-SQL Tokio Runtime"); - - static ref RAW_CONFIG_FILE: String = fs::read_to_string(find_canyon_config_file()) - .expect("Error opening or reading the Canyon configuration file"); - static ref CONFIG_FILE: CanyonSqlConfig = toml::from_str(RAW_CONFIG_FILE.as_str()) - .expect("Error generating the configuration for Canyon-SQL"); - - pub static ref DATASOURCES: Vec = - CONFIG_FILE.canyon_sql.datasources.clone(); - - pub static ref CACHED_DATABASE_CONN: Mutex> = - Mutex::new(IndexMap::new()); -} - -fn find_canyon_config_file() -> PathBuf { - for e in WalkDir::new(".") - .max_depth(2) - .into_iter() - .filter_map(|e| e.ok()) - { - let filename = e.file_name().to_str().unwrap(); // TODO: remove the .unwrap(). Use - // lowercase to allow Canyon.toml - if e.metadata().unwrap().is_file() - && filename.starts_with("canyon") - && filename.ends_with(".toml") - { - return e.path().to_path_buf(); - } - } - - panic!() // TODO: get rid out of this panic and return Err instead -} - -/// Convenient free function to initialize a kind of connection pool based on the datasources present defined -/// in the configuration file. -/// -/// This avoids Canyon to create a new connection to the database on every query, potentially avoiding bottlenecks -/// coming from the instantiation of that new conn every time. -/// -/// Note: We noticed with the integration tests that the [`tokio_postgres`] crate (PostgreSQL) is able to work in an async environment -/// with a new connection per query without no problem, but the [`tiberius`] crate (MSSQL) suffers a lot when it has continuous -/// statements with multiple queries, like and insert followed by a find by id to check if the insert query has done its -/// job done. -pub async fn init_connections_cache() { - for datasource in DATASOURCES.iter() { - CACHED_DATABASE_CONN.lock().await.insert( - &datasource.name, - DatabaseConnection::new(datasource) - .await - .unwrap_or_else(|_| { - panic!( - "Error pooling a new connection for the datasource: {:?}", - datasource.name - ) - }), - ); - } -} - -// TODO: idea. Should we leak the datasources config pull to the user, so we can be more flexible and let the -// user code determine whenever you can find a valid datasource via a concrete type instead of an string? - -// TODO: doc (main way for the user to obtain a db connection given a datasource identifier) -pub async fn get_database_connection_by_ds< - 'a, - T: AsRef + Copy + Debug + Default + Send + Sync + 'a, ->( - datasource_name: Option, -) -> Result> { - let ds = find_datasource_by_name_or_try_default(datasource_name)?; - DatabaseConnection::new(ds).await -} - -fn find_datasource_by_name_or_try_default<'a, T: AsRef + Copy + Debug + Default>( - datasource_name: Option, -) -> Result<&'a DatasourceConfig, DatasourceNotFound> { - datasource_name - .map_or_else( - || DATASOURCES.first(), - |ds_name| DATASOURCES.iter().find(|ds| ds.name.eq(ds_name.as_ref())), - ) - .ok_or_else(|| DatasourceNotFound::from(datasource_name)) -} - -// TODO: create a new one that just receives a str and tries to find the ds config on the vec, so we can make a facade over the one above - -// TODO: get_cached_database_connection -// the idea behind this is that we can have a #cfg feature that offers the end user to let Canyon to automagically manage the connections -// to the db servers, instead of being the default behaviour -pub fn get_database_connection<'a>( - datasource_name: &str, - guarded_cache: &'a mut MutexGuard>, -) -> &'a mut DatabaseConnection { - if datasource_name.is_empty() { - guarded_cache - .get_mut( - DATASOURCES - .first() - .expect("We didn't found any valid datasource configuration. Check your `canyon.toml` file") - .name - .as_str() - ).unwrap_or_else(|| panic!("No default datasource found. Check your `canyon.toml` file")) - } else { - guarded_cache.get_mut(datasource_name) - .unwrap_or_else(|| - panic!("Canyon couldn't find a datasource in the pool with the argument provided: {datasource_name}") - ) - } -} - -pub fn get_database_config<'a>( - datasource_name: &str, - datasources_config: &'a [DatasourceConfig], -) -> &'a DatasourceConfig { - if datasource_name.is_empty() { - datasources_config - .first() - .unwrap_or_else(|| panic!("Not exist datasource")) - } else { - datasources_config - .iter() - .find(|dc| dc.name == datasource_name) - .unwrap_or_else(|| panic!("Not found datasource expected {datasource_name}")) - } -} diff --git a/canyon_core/src/connection/mod.rs b/canyon_core/src/connection/mod.rs index e25519d6..a76e5d08 100644 --- a/canyon_core/src/connection/mod.rs +++ b/canyon_core/src/connection/mod.rs @@ -41,8 +41,7 @@ lazy_static! { static ref CONFIG_FILE: CanyonSqlConfig = toml::from_str(RAW_CONFIG_FILE.as_str()) .expect("Error generating the configuration for Canyon-SQL"); - pub static ref DATASOURCES: Vec< - DatasourceConfig> = + pub static ref DATASOURCES: Vec = CONFIG_FILE.canyon_sql.datasources.clone(); pub static ref CACHED_DATABASE_CONN: Mutex> = @@ -143,19 +142,3 @@ pub fn get_database_connection<'a>( ) } } - -pub fn get_database_config<'a>( - datasource_name: &str, - datasources_config: &'a [DatasourceConfig], -) -> &'a DatasourceConfig { - if datasource_name.is_empty() { - datasources_config - .first() - .unwrap_or_else(|| panic!("Not exist datasource")) - } else { - datasources_config - .iter() - .find(|dc| dc.name == datasource_name) - .unwrap_or_else(|| panic!("Not found datasource expected {datasource_name}")) - } -} diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index bb71b22d..ec5ff3e2 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -1,8 +1,6 @@ use canyon_core::{ column::Column, - connection::{ - datasources::Migrations as MigrationsStatus, db_connector::DatabaseConnection, DATASOURCES, - }, + connection::{db_connector::DatabaseConnection, DATASOURCES}, row::{Row, RowOperations}, rows::CanyonRows, transaction::Transaction, @@ -31,16 +29,7 @@ impl Migrations { /// migrations over the targeted database pub async fn migrate() { for datasource in DATASOURCES.iter() { - if datasource - .properties - .migrations - .filter(|status| !status.eq(&MigrationsStatus::Disabled)) - .is_none() - { - println!( - "Skipped datasource: {:?} for being disabled (or not configured)", - datasource.name - ); + if !datasource.has_migrations_enabled() { continue; } println!( @@ -49,16 +38,16 @@ impl Migrations { ); let mut migrations_processor = MigrationsProcessor::default(); - let mut conn_cache = canyon_core::connection::CACHED_DATABASE_CONN.lock().await; - let db_conn = // TODO: use the appropiated new way - canyon_core::connection::get_database_connection(&datasource.name, &mut conn_cache); + let mut db_conn = canyon_core::connection::get_database_connection_by_ds(Some(&datasource.name)) + .await + .unwrap_or_else(|_| panic!("Unable to get a database connection on the migrations processor for: {:?}", datasource.name)); let canyon_entities = CANYON_REGISTER_ENTITIES.lock().unwrap().to_vec(); let canyon_memory = CanyonMemory::remember(datasource, &canyon_entities).await; // Tracked entities that must be migrated whenever Canyon starts let schema_status = - Self::fetch_database(&datasource.name, db_conn, datasource.get_db_type()).await; + Self::fetch_database(&datasource.name, &mut db_conn, datasource.get_db_type()).await; let database_tables_schema_info = Self::map_rows(schema_status, datasource.get_db_type()); diff --git a/canyon_migrations/src/migrations/processor.rs b/canyon_migrations/src/migrations/processor.rs index 334d9534..3bb98fc6 100644 --- a/canyon_migrations/src/migrations/processor.rs +++ b/canyon_migrations/src/migrations/processor.rs @@ -575,17 +575,14 @@ impl MigrationsProcessor { } /// Make the detected migrations for the next Canyon-SQL run - #[allow(clippy::await_holding_lock)] pub async fn from_query_register(queries_to_execute: &HashMap<&str, Vec<&str>>) { for datasource in queries_to_execute.iter() { for query_to_execute in datasource.1 { let datasource_name = datasource.0; - let mut conn_cache = canyon_core::connection::CACHED_DATABASE_CONN.lock().await; - let db_conn = canyon_core::connection::get_database_connection( - datasource_name, - &mut conn_cache, - ); + let db_conn = canyon_core::connection::get_database_connection_by_ds(Some(datasource_name)) + .await + .unwrap_or_else(|_| panic!("Unable to get a database connection on the migrations processor for: {:?}", datasource_name)); let res = Self::query_rows(query_to_execute, [], db_conn).await; diff --git a/src/lib.rs b/src/lib.rs index ef7aa9fa..05a39afd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,6 @@ pub mod macros { pub mod connection { pub use canyon_core::connection::database_type::DatabaseType; pub use canyon_core::connection::db_connector::DatabaseConnection; - pub use canyon_core::connection::get_database_config; pub use canyon_core::connection::get_database_connection; pub use canyon_core::connection::get_database_connection_by_ds; } From 7727ce603e5814281927a84d98eb2b6715318420 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 12 Feb 2025 13:47:25 +0100 Subject: [PATCH 079/155] chore: removing the legacy way of retrieving a database connection --- canyon_core/src/connection/mod.rs | 35 +++------------------- canyon_migrations/src/migrations/memory.rs | 16 +++++----- src/lib.rs | 1 - 3 files changed, 12 insertions(+), 40 deletions(-) diff --git a/canyon_core/src/connection/mod.rs b/canyon_core/src/connection/mod.rs index a76e5d08..fca7de8d 100644 --- a/canyon_core/src/connection/mod.rs +++ b/canyon_core/src/connection/mod.rs @@ -28,7 +28,7 @@ use datasources::{CanyonSqlConfig, DatasourceConfig}; use db_connector::DatabaseConnection; use indexmap::IndexMap; use lazy_static::lazy_static; -use tokio::sync::{Mutex, MutexGuard}; +use tokio::sync::Mutex; use walkdir::WalkDir; lazy_static! { @@ -36,9 +36,8 @@ lazy_static! { tokio::runtime::Runtime::new() // TODO Make the config with the builder .expect("Failed initializing the Canyon-SQL Tokio Runtime"); - static ref RAW_CONFIG_FILE: String = fs::read_to_string(find_canyon_config_file()) - .expect("Error opening or reading the Canyon configuration file"); - static ref CONFIG_FILE: CanyonSqlConfig = toml::from_str(RAW_CONFIG_FILE.as_str()) + static ref CONFIG_FILE: CanyonSqlConfig = toml::from_str(&fs::read_to_string(find_canyon_config_file()) + .expect("Error opening or reading the Canyon configuration file")) .expect("Error generating the configuration for Canyon-SQL"); pub static ref DATASOURCES: Vec = @@ -115,30 +114,4 @@ pub fn find_datasource_by_name_or_try_default( |ds_name| DATASOURCES.iter().find(|ds| ds.name.eq(ds_name)), ) .ok_or_else(|| DatasourceNotFound::from(datasource_name)) -} - -// TODO: create a new one that just receives a str and tries to find the ds config on the vec, so we can make a facade over the one above - -// TODO: get_cached_database_connection -// the idea behind this is that we can have a #cfg feature that offers the end user to let Canyon to automagically manage the connections -// to the db servers, instead of being the default behaviour -pub fn get_database_connection<'a>( - datasource_name: &str, - guarded_cache: &'a mut MutexGuard>, -) -> &'a mut DatabaseConnection { - if datasource_name.is_empty() { - guarded_cache - .get_mut( - DATASOURCES - .first() - .expect("We didn't found any valid datasource configuration. Check your `canyon.toml` file") - .name - .as_str() - ).unwrap_or_else(|| panic!("No default datasource found. Check your `canyon.toml` file")) - } else { - guarded_cache.get_mut(datasource_name) - .unwrap_or_else(|| - panic!("Canyon couldn't find a datasource in the pool with the argument provided: {datasource_name}") - ) - } -} +} \ No newline at end of file diff --git a/canyon_migrations/src/migrations/memory.rs b/canyon_migrations/src/migrations/memory.rs index 29ad342c..508ac02c 100644 --- a/canyon_migrations/src/migrations/memory.rs +++ b/canyon_migrations/src/migrations/memory.rs @@ -64,17 +64,17 @@ impl CanyonMemory { datasource: &DatasourceConfig, canyon_entities: &[CanyonRegisterEntity<'_>], ) -> Self { - // TODO: can't we get the target DS while in the migrations at call site and avoid to - // duplicate calls to the pool? - let mut conn_cache = canyon_core::connection::CACHED_DATABASE_CONN.lock().await; - let db_conn = - canyon_core::connection::get_database_connection(&datasource.name, &mut conn_cache); + let datasource_name = &datasource.name; + let mut db_conn = + canyon_core::connection::get_database_connection_by_ds(Some(datasource_name)).await + .unwrap_or_else(|_| panic!("Unable to get a database connection on the migrations processor for: {:?}", datasource_name)); + // Creates the memory table if not exists - Self::create_memory(&datasource.name, db_conn, &datasource.get_db_type()).await; + Self::create_memory(&datasource.name, &mut db_conn, &datasource.get_db_type()).await; // Retrieve the last status data from the `canyon_memory` table - let res = Self::query_rows("SELECT * FROM canyon_memory", [], db_conn) + let res = Self::query_rows("SELECT * FROM canyon_memory", [], &mut db_conn) .await .expect("Error querying Canyon Memory"); @@ -216,7 +216,7 @@ impl CanyonMemory { { canyon_entity_macro_counter += 1; } - + if let Some(captures) = re.captures(line) { struct_name.push_str(captures.get(1).unwrap().as_str()); } diff --git a/src/lib.rs b/src/lib.rs index 05a39afd..783eb71e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,6 @@ pub mod macros { pub mod connection { pub use canyon_core::connection::database_type::DatabaseType; pub use canyon_core::connection::db_connector::DatabaseConnection; - pub use canyon_core::connection::get_database_connection; pub use canyon_core::connection::get_database_connection_by_ds; } From e34baf84f4c233eb8b8c0747f0446c0e0e0c4618 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 13 Feb 2025 15:25:11 +0100 Subject: [PATCH 080/155] feat: Crud operations and transaction decoupling, reworking the bounds of all the associated types --- canyon_core/src/connection/database_type.rs | 10 +- .../src/connection/db_clients/mssql.rs | 10 +- .../src/connection/db_clients/mysql.rs | 18 +- .../src/connection/db_clients/postgresql.rs | 17 +- canyon_core/src/connection/db_connector.rs | 45 ++- canyon_core/src/connection/mod.rs | 5 +- canyon_core/src/mapper.rs | 15 +- canyon_core/src/rows.rs | 26 +- canyon_core/src/transaction.rs | 8 +- canyon_crud/src/bounds.rs | 12 +- canyon_crud/src/crud.rs | 54 ++- .../src/query_elements/query_builder.rs | 300 ++++++--------- canyon_entities/src/manager_builder.rs | 4 +- canyon_macros/src/canyon_mapper_macro.rs | 9 +- canyon_macros/src/lib.rs | 1 - canyon_macros/src/query_operations/delete.rs | 4 +- .../src/query_operations/foreign_key.rs | 10 +- canyon_macros/src/query_operations/insert.rs | 8 +- .../src/query_operations/macro_template.rs | 8 +- canyon_macros/src/query_operations/mod.rs | 6 +- canyon_macros/src/query_operations/read.rs | 18 +- canyon_macros/src/query_operations/update.rs | 8 +- canyon_migrations/src/migrations/handler.rs | 2 +- canyon_migrations/src/migrations/memory.rs | 2 +- canyon_migrations/src/migrations/processor.rs | 8 +- tests/crud/querybuilder_operations.rs | 362 +++++++++--------- 26 files changed, 467 insertions(+), 503 deletions(-) diff --git a/canyon_core/src/connection/database_type.rs b/canyon_core/src/connection/database_type.rs index a7730129..cabc5f31 100644 --- a/canyon_core/src/connection/database_type.rs +++ b/canyon_core/src/connection/database_type.rs @@ -1,5 +1,5 @@ use serde::Deserialize; - +use crate::connection::DEFAULT_DATASOURCE; use super::datasources::Auth; /// Holds the current supported databases by Canyon-SQL @@ -21,3 +21,11 @@ impl From<&Auth> for DatabaseType { value.get_db_type() } } + +/// The default implementation for [`DatabaseType`] returns the database type for the first +/// datasource configured +impl Default for DatabaseType { + fn default() -> Self { + DEFAULT_DATASOURCE.get_db_type() + } +} diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs index d01ced7d..f3c6a515 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -25,13 +25,14 @@ impl DbConnection for SqlServerConnection { sqlserver_query_launcher::query_rows(stmt, params, self) } - fn query<'a, S, R: RowMapper>( + fn query<'a, S, R>( &self, stmt: S, params: &[&'a (dyn QueryParameter<'a>)], ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where S: AsRef + Display + Send, + R: RowMapper { sqlserver_query_launcher::query(stmt, params, self) } @@ -42,7 +43,7 @@ impl DbConnection for SqlServerConnection { params: &[&'a (dyn QueryParameter<'a>)], ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where - R: RowMapper, + R: RowMapper { sqlserver_query_launcher::query_one(stmt, params, self) } @@ -76,13 +77,14 @@ pub(crate) mod sqlserver_query_launcher { use tiberius::QueryStream; #[inline(always)] - pub(crate) async fn query<'a, S, R: RowMapper>( + pub(crate) async fn query<'a, S, R>( stmt: S, params: &[&'a dyn QueryParameter<'a>], conn: &SqlServerConnection, ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, + R: RowMapper { Ok(execute_query(stmt.as_ref(), params, conn) .await? @@ -117,7 +119,7 @@ pub(crate) mod sqlserver_query_launcher { conn: &SqlServerConnection, ) -> Result, Box<(dyn Error + Send + Sync)>> where - R: RowMapper, + R: RowMapper, { let result = execute_query(stmt, params, conn).await?.into_row().await?; diff --git a/canyon_core/src/connection/db_clients/mysql.rs b/canyon_core/src/connection/db_clients/mysql.rs index a4804398..5de270b7 100644 --- a/canyon_core/src/connection/db_clients/mysql.rs +++ b/canyon_core/src/connection/db_clients/mysql.rs @@ -27,22 +27,25 @@ impl DbConnection for MysqlConnection { mysql_query_launcher::query_rows(stmt, params, self) } - fn query<'a, S, R: RowMapper>( + fn query<'a, S, R>( &self, stmt: S, params: &[&'a (dyn QueryParameter<'a>)], ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where S: AsRef + Display + Send, + R: RowMapper { mysql_query_launcher::query(stmt, params, self) } - fn query_one<'a, R: RowMapper>( + fn query_one<'a, R>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send { + ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send + where R: RowMapper + { mysql_query_launcher::query_one(stmt, params, self) } @@ -83,13 +86,14 @@ pub(crate) mod mysql_query_launcher { use std::sync::Arc; #[inline(always)] - pub async fn query>( + pub async fn query( stmt: S, params: &[&'_ dyn QueryParameter<'_>], conn: &MysqlConnection, ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, + R: RowMapper { Ok(execute_query(stmt, params, conn) .await? @@ -108,11 +112,13 @@ pub(crate) mod mysql_query_launcher { } #[inline(always)] - pub(crate) async fn query_one<'a, R: RowMapper>( + pub(crate) async fn query_one<'a, R>( stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &MysqlConnection, - ) -> Result, Box<(dyn Error + Sync + Send)>> { + ) -> Result, Box<(dyn Error + Sync + Send)>> + where R: RowMapper + { let result = execute_query(stmt, params, conn).await?; match result.first() { diff --git a/canyon_core/src/connection/db_clients/postgresql.rs b/canyon_core/src/connection/db_clients/postgresql.rs index 23f7807d..3273e100 100644 --- a/canyon_core/src/connection/db_clients/postgresql.rs +++ b/canyon_core/src/connection/db_clients/postgresql.rs @@ -25,13 +25,14 @@ impl DbConnection for PostgreSqlConnection { postgres_query_launcher::query_rows(stmt, params, self) } - fn query<'a, S, R: RowMapper>( + fn query<'a, S, R>( &self, stmt: S, params: &[&'a (dyn QueryParameter<'a>)], ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where S: AsRef + Display + Send, + R: RowMapper { postgres_query_launcher::query(stmt, params, self) } @@ -41,8 +42,7 @@ impl DbConnection for PostgreSqlConnection { stmt: &str, params: &[&'a (dyn QueryParameter<'a>)], ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send - where - R: RowMapper, + where R: RowMapper { postgres_query_launcher::query_one(stmt, params, self) } @@ -76,13 +76,14 @@ pub(crate) mod postgres_query_launcher { use tokio_postgres::types::ToSql; #[inline(always)] - pub(crate) async fn query>( + pub(crate) async fn query( stmt: S, params: &[&'_ (dyn QueryParameter<'_>)], conn: &PostgreSqlConnection, ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, + R: RowMapper { Ok(conn .client @@ -110,11 +111,13 @@ pub(crate) mod postgres_query_launcher { /// *NOTE*: implementation details of `query_one` when handling errors are /// discussed [here](https://github.com/sfackler/rust-postgres/issues/790#issuecomment-2095729043) #[inline(always)] - pub(crate) async fn query_one<'a, T: RowMapper>( + pub(crate) async fn query_one<'a, R>( stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &PostgreSqlConnection, - ) -> Result, Box<(dyn Error + Sync + Send)>> { + ) -> Result, Box<(dyn Error + Sync + Send)>> + where R: RowMapper + { let m_params: Vec<_> = params .iter() .map(|param| param.as_postgres_param()) @@ -122,7 +125,7 @@ pub(crate) mod postgres_query_launcher { let result = conn.client.query_one(stmt, m_params.as_slice()).await; match result { - Ok(row) => { Ok(Some(T::deserialize_postgresql(&row))) }, + Ok(row) => { Ok(Some(R::deserialize_postgresql(&row))) }, Err(e) => match e.to_string().contains("unexpected number of rows") { true => { Ok(None) }, _ => Err(e)?, diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index db18dba0..acb5ede2 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -24,24 +24,26 @@ pub trait DbConnection { params: &[&'a dyn QueryParameter<'a>], ) -> impl Future>> + Send; - fn query<'a, S, R: RowMapper>( + fn query<'a, S, R>( &self, stmt: S, params: &[&'a (dyn QueryParameter<'a>)], ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where - S: AsRef + Display + Send; + S: AsRef + Display + Send, + R: RowMapper; - fn query_one<'a, R: RowMapper>( + fn query_one<'a, R>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send; + ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send + where R: RowMapper; /// Flexible and general method that queries the target database for a concrete instance /// of some type T. /// - /// This is useful on statements that won't be mapped to user types (impl RowMapper) but + /// This is useful on statements that won't be mapped to user types (impl RowMapper) but /// there's a need for more flexibility on the return type. Ex: SELECT COUNT(*) from , /// where there will be a single result of some numerical type fn query_one_for<'a, T: FromSqlOwnedValue>( @@ -75,23 +77,26 @@ impl DbConnection for &str { conn.query_rows(stmt, params).await } - async fn query<'a, S, R: RowMapper>( + async fn query<'a, S, R>( &self, stmt: S, params: &[&'a (dyn QueryParameter<'a>)], ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, + R: RowMapper { let conn = get_database_connection_by_ds(Some(self)).await?; conn.query(stmt, params).await } - async fn query_one<'a, R: RowMapper>( + async fn query_one<'a, R>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result, Box<(dyn Error + Sync + Send)>> { + ) -> Result, Box<(dyn Error + Sync + Send)>> + where R: RowMapper + { let sane_ds_name = if !self.is_empty() { Some(*self) } else { None }; let conn = get_database_connection_by_ds(sane_ds_name).await?; conn.query_one(stmt, params).await @@ -145,13 +150,14 @@ impl DbConnection for DatabaseConnection { db_conn_launch_impl(self, stmt, params).await } - async fn query<'a, S, R: RowMapper>( + async fn query<'a, S, R>( &self, stmt: S, params: &[&'a (dyn QueryParameter<'a>)], ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, + R: RowMapper { match self { #[cfg(feature = "postgres")] @@ -165,11 +171,13 @@ impl DbConnection for DatabaseConnection { } } - async fn query_one<'a, R: RowMapper>( + async fn query_one<'a, R>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result, Box<(dyn Error + Sync + Send)>> { + ) -> Result, Box<(dyn Error + Sync + Send)>> + where R: RowMapper + { db_conn_query_one_impl(self, stmt, params).await } @@ -219,13 +227,14 @@ impl DbConnection for &mut DatabaseConnection { ) -> Result> { db_conn_launch_impl(self, stmt, params).await } - async fn query<'a, S, R: RowMapper>( + async fn query<'a, S, R>( &self, stmt: S, params: &[&'a (dyn QueryParameter<'a>)], ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, + R: RowMapper { match self { #[cfg(feature = "postgres")] @@ -239,11 +248,13 @@ impl DbConnection for &mut DatabaseConnection { } } - async fn query_one<'a, R: RowMapper>( + async fn query_one<'a, R>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result, Box<(dyn Error + Sync + Send)>> { + ) -> Result, Box<(dyn Error + Sync + Send)>> + where R: RowMapper + { db_conn_query_one_impl(self, stmt, params).await } @@ -450,11 +461,13 @@ mod connection_helpers { } } - pub(crate) async fn db_conn_query_one_impl<'a, T: RowMapper>( + pub(crate) async fn db_conn_query_one_impl<'a, R>( c: &DatabaseConnection, stmt: &str, params: &[&'a (dyn QueryParameter<'a> + 'a)], - ) -> Result, Box> { + ) -> Result, Box> + where R: RowMapper + { match c { #[cfg(feature = "postgres")] DatabaseConnection::Postgres(client) => client.query_one(stmt, params).await, diff --git a/canyon_core/src/connection/mod.rs b/canyon_core/src/connection/mod.rs index fca7de8d..9cae4f6b 100644 --- a/canyon_core/src/connection/mod.rs +++ b/canyon_core/src/connection/mod.rs @@ -37,11 +37,14 @@ lazy_static! { .expect("Failed initializing the Canyon-SQL Tokio Runtime"); static ref CONFIG_FILE: CanyonSqlConfig = toml::from_str(&fs::read_to_string(find_canyon_config_file()) - .expect("Error opening or reading the Canyon configuration file")) + .expect("Error opening or reading the Canyon configuration file")) // unwrap or default, to allow the builder to later configure manually data .expect("Error generating the configuration for Canyon-SQL"); pub static ref DATASOURCES: Vec = CONFIG_FILE.canyon_sql.datasources.clone(); + + pub static ref DEFAULT_DATASOURCE: &'static DatasourceConfig = DATASOURCES.first() + .expect("No datasource configured"); pub static ref CACHED_DATABASE_CONN: Mutex> = Mutex::new(IndexMap::new()); diff --git a/canyon_core/src/mapper.rs b/canyon_core/src/mapper.rs index bf0f47f9..c3c0ce55 100644 --- a/canyon_core/src/mapper.rs +++ b/canyon_core/src/mapper.rs @@ -1,19 +1,20 @@ /// Declares functions that takes care to deserialize data incoming /// from some supported database in Canyon-SQL into a user's defined /// type `T` -pub trait RowMapper: Sized { +pub trait RowMapper: Sized { + type Output; + #[cfg(feature = "postgres")] - fn deserialize_postgresql(row: &tokio_postgres::Row) -> T; + fn deserialize_postgresql(row: &tokio_postgres::Row) -> Self::Output; #[cfg(feature = "mssql")] - fn deserialize_sqlserver(row: &tiberius::Row) -> T; + fn deserialize_sqlserver(row: &tiberius::Row) -> Self::Output; #[cfg(feature = "mysql")] - fn deserialize_mysql(row: &mysql_async::Row) -> T; + fn deserialize_mysql(row: &mysql_async::Row) -> Self::Output; } pub type CanyonError = Box<(dyn std::error::Error + Send + Sync)>; // TODO: convert this into a // real error pub trait IntoResults { - fn into_results(self) -> Result, CanyonError> - where - T: RowMapper; + fn into_results(self) -> Result, CanyonError> + where R: RowMapper; } diff --git a/canyon_core/src/rows.rs b/canyon_core/src/rows.rs index b2e84f02..6e32d253 100644 --- a/canyon_core/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -10,6 +10,7 @@ use crate::row::Row; use std::error::Error; use cfg_if::cfg_if; +use mysql_common::prelude::FromRow; // Helper macro to conditionally add trait bounds // these are the hacky intermediate traits @@ -60,11 +61,10 @@ pub enum CanyonRows { } impl IntoResults for Result { - fn into_results(self) -> Result, CanyonError> - where - T: RowMapper, + fn into_results(self) -> Result, CanyonError> + where R: RowMapper, { - self.map(move |rows| rows.into_results::()) + self.map(move |rows| rows.into_results::()) } } @@ -94,14 +94,14 @@ impl CanyonRows { } /// Consumes `self` and returns the wrapped [`std::vec::Vec`] with the instances of T - pub fn into_results>(self) -> Vec { + pub fn into_results>(self) -> Vec { match self { #[cfg(feature = "postgres")] - Self::Postgres(v) => v.iter().map(|row| Z::deserialize_postgresql(row)).collect(), + Self::Postgres(v) => v.iter().map(|row| R::deserialize_postgresql(row)).collect(), #[cfg(feature = "mssql")] - Self::Tiberius(v) => v.iter().map(|row| Z::deserialize_sqlserver(row)).collect(), + Self::Tiberius(v) => v.iter().map(|row| R::deserialize_sqlserver(row)).collect(), #[cfg(feature = "mysql")] - Self::MySQL(v) => v.iter().map(|row| Z::deserialize_mysql(row)).collect(), + Self::MySQL(v) => v.iter().map(|row| R::deserialize_mysql(row)).collect(), } } @@ -119,7 +119,7 @@ impl CanyonRows { } } - pub fn first_row>(&self) -> Option { + pub fn first_row>(&self) -> Option { match self { #[cfg(feature = "postgres")] Self::Postgres(v) => v.first().map(|r| T::deserialize_postgresql(r)), @@ -155,8 +155,8 @@ impl CanyonRows { .get::(column_name) .ok_or_else(|| { format!( - "{:?} - Failed to obtain the RETURNING value for an insert operation", - self + "{:?} - Failure getting the row: {} at index: {}", + self, column_name, index ) .into() }), @@ -167,8 +167,8 @@ impl CanyonRows { .get::(0) .ok_or_else(|| { format!( - "{:?} - Failed to obtain the RETURNING value for an insert operation", - self + "{:?} - Failure getting the row: {} at index: {}", + self, column_name, index ) .into() }), diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index fc4bb339..355e6a74 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -5,19 +5,20 @@ use crate::{query_parameters::QueryParameter, rows::CanyonRows}; use std::error::Error; use std::{fmt::Display, future::Future}; -pub trait Transaction { - fn query<'a, S, R: RowMapper>( +pub trait Transaction { + fn query<'a, S, R>( stmt: S, params: &[&'a (dyn QueryParameter<'a>)], input: impl DbConnection + Send, ) -> impl Future, Box<(dyn Error + Sync + Send)>>> where S: AsRef + Display + Send, + R: RowMapper { async move { input.query(stmt, params).await } } - fn query_one<'a, S, Z, R: RowMapper>( + fn query_one<'a, S, Z, R>( stmt: S, params: Z, input: impl DbConnection + Send + 'a, @@ -25,6 +26,7 @@ pub trait Transaction { where S: AsRef + Display + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send + 'a, + R: RowMapper { async move { input.query_one(stmt.as_ref(), params.as_ref()).await } } diff --git a/canyon_crud/src/bounds.rs b/canyon_crud/src/bounds.rs index f6965195..84e36eab 100644 --- a/canyon_crud/src/bounds.rs +++ b/canyon_crud/src/bounds.rs @@ -24,10 +24,10 @@ use crate::crud::CrudOperations; /// /// // Something like: /// `let struct_field_name_from_variant = StructField::some_field.field_name_as_str();` -pub trait FieldIdentifier -where - // TODO: maybe just QueryParameter? - T: Transaction + CrudOperations + RowMapper, +pub trait FieldIdentifier +// where +// // TODO: maybe just QueryParameter? +// T: QueryParameter<'a>, { fn as_str(&self) -> &'static str; } @@ -52,9 +52,7 @@ where /// IntVariant(i32) /// } /// ``` -pub trait FieldValueIdentifier<'a, T> -where - T: Transaction + CrudOperations + RowMapper, +pub trait FieldValueIdentifier<'a> { fn value(self) -> (&'static str, &'a dyn QueryParameter<'a>); } diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index fb0a14bf..88de5676 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -3,9 +3,10 @@ use crate::query_elements::query_builder::{ }; use canyon_core::connection::db_connector::DbConnection; use canyon_core::query_parameters::QueryParameter; -use canyon_core::{mapper::RowMapper, transaction::Transaction}; use std::error::Error; use std::future::Future; +use canyon_core::connection::database_type::DatabaseType; +use canyon_core::mapper::RowMapper; /// *CrudOperations* it's the core part of Canyon-SQL. /// @@ -13,38 +14,33 @@ use std::future::Future; /// that the user has available, just by deriving the `CanyonCrud` /// derive macro when a struct contains the annotation. /// -/// Also, these traits needs that the type T over what it's generified +/// Also, these traits needs that the type R over what it's generified /// to implement certain types in order to work correctly. /// -/// The most notorious one it's the [`RowMapper`] one, which allows +/// The most notorious one it's the [`RowMapper`] one, which allows /// Canyon to directly maps database results into structs. /// /// See it's definition and docs to see the implementations. /// Also, you can find the written macro-code that performs the auto-mapping /// in the *canyon_sql_root::canyon_macros* crates, on the root of this project. -pub trait CrudOperations: Transaction -where - T: CrudOperations + RowMapper, -{ - fn find_all() -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send; +pub trait CrudOperations>: Send + Sync { + fn find_all() -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send; fn find_all_with<'a, I>( input: I, - ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send + ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send where I: DbConnection + Send + 'a; - fn find_all_unchecked() -> impl Future> + Send; + fn find_all_unchecked() -> impl Future> + Send; - fn find_all_unchecked_with<'a, I>(input: I) -> impl Future> + Send + fn find_all_unchecked_with<'a, I>(input: I) -> impl Future> + Send where I: DbConnection + Send + 'a; - fn select_query<'a>() -> SelectQueryBuilder<'a, T, &'a str>; + fn select_query<'a>() -> SelectQueryBuilder<'a, R>; - fn select_query_with<'a, I>(input: I) -> SelectQueryBuilder<'a, T, I> - where - I: DbConnection + Send + 'a; + fn select_query_with<'a>(database_type: DatabaseType) -> SelectQueryBuilder<'a, R>; fn count() -> impl Future>> + Send; @@ -56,12 +52,12 @@ where fn find_by_pk<'a>( value: &'a dyn QueryParameter<'a>, - ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send; + ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send; fn find_by_pk_with<'a, I>( value: &'a dyn QueryParameter<'a>, input: I, - ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send + ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send where I: DbConnection + Send + 'a; @@ -77,11 +73,11 @@ where I: DbConnection + Send + 'a; fn multi_insert<'a>( - instances: &'a mut [&'a mut T], + instances: &'a mut [&'a mut R], ) -> impl Future>> + Send; fn multi_insert_with<'a, I>( - instances: &'a mut [&'a mut T], + instances: &'a mut [&'a mut R], input: I, ) -> impl Future>> + Send where @@ -96,11 +92,11 @@ where where I: DbConnection + Send + 'a; - fn update_query<'a>() -> UpdateQueryBuilder<'a, T, &'a str>; - - fn update_query_with<'a, I>(input: I) -> UpdateQueryBuilder<'a, T, I> - where - I: DbConnection + Send + 'a; + // fn update_query<'a>() -> UpdateQueryBuilder<'a>; + // + // fn update_query_with<'a, I>(input: I) -> UpdateQueryBuilder<'a> + // where + // I: DbConnection + Send + 'a; fn delete(&self) -> impl Future>> + Send; @@ -111,9 +107,9 @@ where where I: DbConnection + Send + 'a; - fn delete_query<'a>() -> DeleteQueryBuilder<'a, T, &'a str>; - - fn delete_query_with<'a, I>(input: I) -> DeleteQueryBuilder<'a, T, I> - where - I: DbConnection + Send + 'a; + // fn delete_query<'a>() -> DeleteQueryBuilder<'a>; + // + // fn delete_query_with<'a, I>(input: I) -> DeleteQueryBuilder<'a> + // where + // I: DbConnection + Send + 'a; } diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index 01cb7864..4e085d99 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -50,16 +50,13 @@ pub mod ops { /// of the `SET` clause on a [`super::UpdateQueryBuilder`], /// without mixing types or polluting everything into /// just one type. - pub trait QueryBuilder<'a, T> - where - T: CrudOperations + Transaction + RowMapper, - { + pub trait QueryBuilder<'a> { /// Returns a read-only reference to the underlying SQL sentence, /// with the same lifetime as self fn read_sql(&'a self) -> &'a str; /// Public interface for append the content of an slice to the end of - /// the underlying SQL sentece. + /// the underlying SQL sentence. /// /// This mutator will allow the user to wire SQL code to the already /// generated one @@ -73,9 +70,7 @@ pub mod ops { /// column name and the value for the filter /// * `op` - Any element that implements [`Operator`] for create the comparison /// or equality binary operator - fn r#where>(self, column: Z, op: impl Operator) -> Self - where - T: Debug + CrudOperations + Transaction + RowMapper; + fn r#where>(self, column: Z, op: impl Operator) -> Self; /// Generates an `AND` SQL clause for constraint the query. /// @@ -83,7 +78,7 @@ pub mod ops { /// column name and the value for the filter /// * `op` - Any element that implements [`Operator`] for create the comparison /// or equality binary operator - fn and>(self, column: Z, op: impl Operator) -> Self; + fn and>(self, column: Z, op: impl Operator) -> Self; /// Generates an `AND` SQL clause for constraint the query that will create /// the filter in conjunction with an `IN` operator that will ac @@ -95,7 +90,7 @@ pub mod ops { /// inside the `IN` operator fn and_values_in(self, column: Z, values: &'a [Q]) -> Self where - Z: FieldIdentifier, + Z: FieldIdentifier, Q: QueryParameter<'a>; /// Generates an `OR` SQL clause for constraint the query that will create @@ -108,7 +103,7 @@ pub mod ops { /// inside the `IN` operator fn or_values_in(self, r#or: Z, values: &'a [Q]) -> Self where - Z: FieldIdentifier, + Z: FieldIdentifier, Q: QueryParameter<'a>; /// Generates an `OR` SQL clause for constraint the query. @@ -117,167 +112,147 @@ pub mod ops { /// column name and the value for the filter /// * `op` - Any element that implements [`Operator`] for create the comparison /// or equality binary operator - fn or>(self, column: Z, op: impl Operator) -> Self; + fn or>(self, column: Z, op: impl Operator) -> Self; /// Generates a `ORDER BY` SQL clause for constraint the query. /// /// * `order_by` - A [`FieldIdentifier`] that will provide the target column name /// * `desc` - a boolean indicating if the generated `ORDER_BY` must be in ascending or descending order - fn order_by>(self, order_by: Z, desc: bool) -> Self; + fn order_by(self, order_by: Z, desc: bool) -> Self; } } /// Type for construct more complex queries than the classical CRUD ones. -pub struct QueryBuilder<'a, T, I> -where - T: CrudOperations + Transaction + RowMapper, - I: DbConnection, -{ - query: Query<'a>, - input: I, - datasource_type: DatabaseType, - pd: PhantomData, // TODO: provisional while reworking the bounds +pub struct QueryBuilder<'a, R: RowMapper>{ + // query: Query<'a>, + sql: String, + params: Vec<&'a dyn QueryParameter<'a>>, + database_type: DatabaseType, + pd: PhantomData, } -unsafe impl<'a, T, I> Send for QueryBuilder<'a, T, I> -where - T: CrudOperations + Transaction + RowMapper, - I: DbConnection + Send + 'a, -{ -} -unsafe impl<'a, T, I> Sync for QueryBuilder<'a, T, I> -where - T: CrudOperations + Transaction + RowMapper, - I: DbConnection + Send + 'a, -{ -} +unsafe impl<'a, R: RowMapper> Sync for QueryBuilder<'a, R> {} -impl<'a, T, I> QueryBuilder<'a, T, I> -where - T: CrudOperations + Transaction + RowMapper, - I: DbConnection + Send + 'a, -{ - /// Returns a new instance of the [`QueryBuilder`] - pub fn new(query: Query<'a>, input: I) -> Self { - let db_type = input - .get_database_type() - .expect("QueryBuilder::::get_database_type(). Querybuilder new must return Result on it's public API, refactor it"); // TODO: +impl<'a, R: RowMapper> QueryBuilder<'a, R> { + pub fn new(sql: String, database_type: DatabaseType) -> Self { Self { - query, - input, - datasource_type: db_type, + sql, + params: vec![], + database_type, pd: Default::default(), } } /// Launches the generated query against the database targeted /// by the selected datasource - pub async fn query( + /// /// TODO: this is not definitive => QueryBuilder -> Query -> Transaction -> RowMapper + pub async fn query( mut self, - ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> { - self.query.sql.push(';'); + input: I + ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> { + self.sql.push(';'); - T::query(&self.query.sql, &self.query.params, self.input).await + T::query(&self.sql, &self.params, input).await } - pub fn r#where>(&mut self, r#where: Z, op: impl Operator) { + pub fn r#where>(&mut self, r#where: Z, op: impl Operator) { let (column_name, value) = r#where.value(); let where_ = String::from(" WHERE ") + column_name - + &op.as_str(self.query.params.len() + 1, &self.datasource_type); + + &op.as_str(self.params.len() + 1, &self.database_type); - self.query.sql.push_str(&where_); - self.query.params.push(value); + self.sql.push_str(&where_); + self.params.push(value); } - pub fn and>(&mut self, r#and: Z, op: impl Operator) { + pub fn and>(&mut self, r#and: Z, op: impl Operator) { let (column_name, value) = r#and.value(); let and_ = String::from(" AND ") + column_name - + &op.as_str(self.query.params.len() + 1, &self.datasource_type); + + &op.as_str(self.params.len() + 1, &self.database_type); - self.query.sql.push_str(&and_); - self.query.params.push(value); + self.sql.push_str(&and_); + self.params.push(value); } - pub fn or>(&mut self, r#and: Z, op: impl Operator) { + pub fn or>(&mut self, r#and: Z, op: impl Operator) { let (column_name, value) = r#and.value(); let and_ = String::from(" OR ") + column_name - + &op.as_str(self.query.params.len() + 1, &self.datasource_type); + + &op.as_str(self.params.len() + 1, &self.database_type); - self.query.sql.push_str(&and_); - self.query.params.push(value); + self.sql.push_str(&and_); + self.params.push(value); } pub fn and_values_in(&mut self, r#and: Z, values: &'a [Q]) where - Z: FieldIdentifier, + Z: FieldIdentifier, Q: QueryParameter<'a>, { if values.is_empty() { return; } - self.query + self .sql .push_str(&format!(" AND {} IN (", r#and.as_str())); let mut counter = 1; values.iter().for_each(|qp| { if values.len() != counter { - self.query + self .sql - .push_str(&format!("${}, ", self.query.params.len())); + .push_str(&format!("${}, ", self.params.len())); counter += 1; } else { - self.query + self .sql - .push_str(&format!("${}", self.query.params.len())); + .push_str(&format!("${}", self.params.len())); } - self.query.params.push(qp) + self.params.push(qp) }); - self.query.sql.push(')') + self.sql.push(')') } fn or_values_in(&mut self, r#or: Z, values: &'a [Q]) where - Z: FieldIdentifier, + Z: FieldIdentifier, Q: QueryParameter<'a>, { if values.is_empty() { return; } - self.query + self .sql .push_str(&format!(" OR {} IN (", r#or.as_str())); let mut counter = 1; values.iter().for_each(|qp| { if values.len() != counter { - self.query + self .sql - .push_str(&format!("${}, ", self.query.params.len())); + .push_str(&format!("${}, ", self.params.len())); counter += 1; } else { - self.query + self .sql - .push_str(&format!("${}", self.query.params.len())); + .push_str(&format!("${}", self.params.len())); } - self.query.params.push(qp) + self.params.push(qp) }); - self.query.sql.push(')') + self.sql.push(')') } #[inline] - pub fn order_by>(&mut self, order_by: Z, desc: bool) { - self.query.sql.push_str( + pub fn order_by(&mut self, order_by: Z, desc: bool) { + self.sql.push_str( &(format!( " ORDER BY {}{}", order_by.as_str(), @@ -287,25 +262,18 @@ where } } -pub struct SelectQueryBuilder<'a, T, I> -where - T: CrudOperations + Transaction + RowMapper, - I: DbConnection + Send + 'a, +pub struct SelectQueryBuilder<'a, R: RowMapper> { - _inner: QueryBuilder<'a, T, I>, + _inner: QueryBuilder<'a, R>, } -impl<'a, T, I> SelectQueryBuilder<'a, T, I> -where - T: CrudOperations + Transaction + RowMapper, - I: DbConnection + Send + 'a, -{ +impl<'a, R: RowMapper> SelectQueryBuilder<'a, R> { /// Generates a new public instance of the [`SelectQueryBuilder`] - pub fn new(table_schema_data: &str, input: I) -> Self { + pub fn new(table_schema_data: &str, database_type: DatabaseType) -> Self { Self { - _inner: QueryBuilder::::new( - Query::new(format!("SELECT * FROM {table_schema_data}")), - input, + _inner: QueryBuilder::new( + format!("SELECT * FROM {table_schema_data}"), + database_type, ), } } @@ -313,8 +281,10 @@ where /// Launches the generated query to the database pointed by the /// selected datasource #[inline] - pub async fn query(self) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> { - self._inner.query().await + pub async fn query(self, input: I) + -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + { + self._inner.query::(input).await } /// Adds a *LEFT JOIN* SQL statement to the underlying @@ -327,7 +297,6 @@ where /// > Note: The order on the column parameters is irrelevant pub fn left_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { self._inner - .query .sql .push_str(&format!(" LEFT JOIN {join_table} ON {col1} = {col2}")); self @@ -343,7 +312,6 @@ where /// > Note: The order on the column parameters is irrelevant pub fn inner_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { self._inner - .query .sql .push_str(&format!(" INNER JOIN {join_table} ON {col1} = {col2}")); self @@ -359,7 +327,6 @@ where /// > Note: The order on the column parameters is irrelevant pub fn right_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { self._inner - .query .sql .push_str(&format!(" RIGHT JOIN {join_table} ON {col1} = {col2}")); self @@ -375,36 +342,31 @@ where /// > Note: The order on the column parameters is irrelevant pub fn full_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { self._inner - .query .sql .push_str(&format!(" FULL JOIN {join_table} ON {col1} = {col2}")); self } } -impl<'a, T, I> ops::QueryBuilder<'a, T> for SelectQueryBuilder<'a, T, I> -where - T: Debug + CrudOperations + Transaction + RowMapper + Send, - I: DbConnection + Send + 'a, -{ +impl<'a, R: RowMapper> ops::QueryBuilder<'a> for SelectQueryBuilder<'a, R> { #[inline] fn read_sql(&'a self) -> &'a str { - self._inner.query.sql.as_str() + self._inner.sql.as_str() } #[inline(always)] fn push_sql(mut self, sql: &str) { - self._inner.query.sql.push_str(sql); + self._inner.sql.push_str(sql); } #[inline] - fn r#where>(mut self, r#where: Z, op: impl Operator) -> Self { + fn r#where>(mut self, r#where: Z, op: impl Operator) -> Self { self._inner.r#where(r#where, op); self } #[inline] - fn and>(mut self, column: Z, op: impl Operator) -> Self { + fn and>(mut self, column: Z, op: impl Operator) -> Self { self._inner.and(column, op); self } @@ -412,7 +374,7 @@ where #[inline] fn and_values_in(mut self, r#and: Z, values: &'a [Q]) -> Self where - Z: FieldIdentifier, + Z: FieldIdentifier, Q: QueryParameter<'a>, { self._inner.and_values_in(and, values); @@ -422,7 +384,7 @@ where #[inline] fn or_values_in(mut self, r#and: Z, values: &'a [Q]) -> Self where - Z: FieldIdentifier, + Z: FieldIdentifier, Q: QueryParameter<'a>, { self._inner.or_values_in(and, values); @@ -430,13 +392,13 @@ where } #[inline] - fn or>(mut self, column: Z, op: impl Operator) -> Self { + fn or>(mut self, column: Z, op: impl Operator) -> Self { self._inner.or(column, op); self } #[inline] - fn order_by>(mut self, order_by: Z, desc: bool) -> Self { + fn order_by(mut self, order_by: Z, desc: bool) -> Self { self._inner.order_by(order_by, desc); self } @@ -446,25 +408,17 @@ where /// /// * `set` - To construct a new `SET` clause to determine the columns to /// update with the provided values -pub struct UpdateQueryBuilder<'a, T, I> -where - T: CrudOperations + Transaction + RowMapper, - I: DbConnection + Send + 'a, -{ - _inner: QueryBuilder<'a, T, I>, +pub struct UpdateQueryBuilder<'a, R: RowMapper> { + _inner: QueryBuilder<'a, R>, } -impl<'a, T, I> UpdateQueryBuilder<'a, T, I> -where - T: CrudOperations + Transaction + RowMapper, - I: DbConnection + Send + 'a, -{ +impl<'a, R: RowMapper> UpdateQueryBuilder<'a, R> { /// Generates a new public instance of the [`UpdateQueryBuilder`] - pub fn new(table_schema_data: &str, input: I) -> Self { + pub fn new(table_schema_data: &str, database_type: DatabaseType) -> Self { Self { - _inner: QueryBuilder::::new( - Query::new(format!("UPDATE {table_schema_data}")), - input, + _inner: QueryBuilder::new( + format!("UPDATE {table_schema_data}"), + database_type ), } } @@ -472,20 +426,22 @@ where /// Launches the generated query to the database pointed by the /// selected datasource #[inline] - pub async fn query(self) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> { - self._inner.query().await + pub async fn query(self, input: I) + -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + { + self._inner.query::(input).await } /// Creates an SQL `SET` clause to specify the columns that must be updated in the sentence pub fn set(mut self, columns: &'a [(Z, Q)]) -> Self where - Z: FieldIdentifier + Clone, + Z: FieldIdentifier + Clone, Q: QueryParameter<'a>, { if columns.is_empty() { return self; } - if self._inner.query.sql.contains("SET") { + if self._inner.sql.contains("SET") { panic!( // TODO: this should return an Err and not panic! "\n{}", @@ -502,43 +458,39 @@ where set_clause.push_str(&format!( "{} = ${}", column.0.as_str(), - self._inner.query.params.len() + 1 + self._inner.params.len() + 1 )); if idx < columns.len() - 1 { set_clause.push_str(", "); } - self._inner.query.params.push(&column.1); + self._inner.params.push(&column.1); } - self._inner.query.sql.push_str(&set_clause); + self._inner.sql.push_str(&set_clause); self } } -impl<'a, T, I> ops::QueryBuilder<'a, T> for UpdateQueryBuilder<'a, T, I> -where - T: Debug + CrudOperations + Transaction + RowMapper + Send, - I: DbConnection + Send + 'a, -{ +impl<'a, R: RowMapper> ops::QueryBuilder<'a> for UpdateQueryBuilder<'a, R> { #[inline] fn read_sql(&'a self) -> &'a str { - self._inner.query.sql.as_str() + self._inner.sql.as_str() } #[inline(always)] fn push_sql(mut self, sql: &str) { - self._inner.query.sql.push_str(sql); + self._inner.sql.push_str(sql); } #[inline] - fn r#where>(mut self, r#where: Z, op: impl Operator) -> Self { + fn r#where>(mut self, r#where: Z, op: impl Operator) -> Self { self._inner.r#where(r#where, op); self } #[inline] - fn and>(mut self, column: Z, op: impl Operator) -> Self { + fn and>(mut self, column: Z, op: impl Operator) -> Self { self._inner.and(column, op); self } @@ -546,7 +498,7 @@ where #[inline] fn and_values_in(mut self, r#and: Z, values: &'a [Q]) -> Self where - Z: FieldIdentifier, + Z: FieldIdentifier, Q: QueryParameter<'a>, { self._inner.and_values_in(and, values); @@ -556,7 +508,7 @@ where #[inline] fn or_values_in(mut self, r#or: Z, values: &'a [Q]) -> Self where - Z: FieldIdentifier, + Z: FieldIdentifier, Q: QueryParameter<'a>, { self._inner.or_values_in(or, values); @@ -564,13 +516,13 @@ where } #[inline] - fn or>(mut self, column: Z, op: impl Operator) -> Self { + fn or>(mut self, column: Z, op: impl Operator) -> Self { self._inner.or(column, op); self } #[inline] - fn order_by>(mut self, order_by: Z, desc: bool) -> Self { + fn order_by(mut self, order_by: Z, desc: bool) -> Self { self._inner.order_by(order_by, desc); self } @@ -581,25 +533,17 @@ where /// /// * `set` - To construct a new `SET` clause to determine the columns to /// update with the provided values -pub struct DeleteQueryBuilder<'a, T, I> -where - T: CrudOperations + Transaction + RowMapper, - I: DbConnection + Send + 'a, -{ - _inner: QueryBuilder<'a, T, I>, +pub struct DeleteQueryBuilder<'a, R: RowMapper> { + _inner: QueryBuilder<'a, R>, } -impl<'a, T, I> DeleteQueryBuilder<'a, T, I> -where - T: CrudOperations + Transaction + RowMapper, - I: DbConnection + Send + 'a, -{ +impl<'a, R: RowMapper> DeleteQueryBuilder<'a, R> { /// Generates a new public instance of the [`DeleteQueryBuilder`] - pub fn new(table_schema_data: &str, input: I) -> Self { + pub fn new(table_schema_data: &str, database_type: DatabaseType) -> Self { Self { - _inner: QueryBuilder::::new( - Query::new(format!("DELETE FROM {table_schema_data}")), - input, + _inner: QueryBuilder::new( + format!("DELETE FROM {table_schema_data}"), + database_type ), } } @@ -607,34 +551,32 @@ where /// Launches the generated query to the database pointed by the /// selected datasource #[inline] - pub async fn query(self) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> { - self._inner.query().await + pub async fn query(self, input: I) + -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + { + self._inner.query::(input).await } } -impl<'a, T, I> ops::QueryBuilder<'a, T> for DeleteQueryBuilder<'a, T, I> -where - T: Debug + CrudOperations + Transaction + RowMapper + Send, - I: DbConnection + Send + 'a, -{ +impl<'a, R: RowMapper> ops::QueryBuilder<'a> for DeleteQueryBuilder<'a, R> { #[inline] fn read_sql(&'a self) -> &'a str { - self._inner.query.sql.as_str() + self._inner.sql.as_str() } #[inline(always)] fn push_sql(mut self, sql: &str) { - self._inner.query.sql.push_str(sql); + self._inner.sql.push_str(sql); } #[inline] - fn r#where>(mut self, r#where: Z, op: impl Operator) -> Self { + fn r#where>(mut self, r#where: Z, op: impl Operator) -> Self { self._inner.r#where(r#where, op); self } #[inline] - fn and>(mut self, column: Z, op: impl Operator) -> Self { + fn and>(mut self, column: Z, op: impl Operator) -> Self { self._inner.and(column, op); self } @@ -642,7 +584,7 @@ where #[inline] fn and_values_in(mut self, r#and: Z, values: &'a [Q]) -> Self where - Z: FieldIdentifier, + Z: FieldIdentifier, Q: QueryParameter<'a>, { self._inner.or_values_in(and, values); @@ -652,7 +594,7 @@ where #[inline] fn or_values_in(mut self, r#or: Z, values: &'a [Q]) -> Self where - Z: FieldIdentifier, + Z: FieldIdentifier, Q: QueryParameter<'a>, { self._inner.or_values_in(or, values); @@ -660,13 +602,13 @@ where } #[inline] - fn or>(mut self, column: Z, op: impl Operator) -> Self { + fn or>(mut self, column: Z, op: impl Operator) -> Self { self._inner.or(column, op); self } #[inline] - fn order_by>(mut self, order_by: Z, desc: bool) -> Self { + fn order_by(mut self, order_by: Z, desc: bool) -> Self { self._inner.order_by(order_by, desc); self } diff --git a/canyon_entities/src/manager_builder.rs b/canyon_entities/src/manager_builder.rs index d717909f..6f51bd64 100644 --- a/canyon_entities/src/manager_builder.rs +++ b/canyon_entities/src/manager_builder.rs @@ -77,7 +77,7 @@ pub fn generate_enum_with_fields(canyon_entity: &CanyonEntity) -> TokenStream { #(#fields_names),* } - impl #generics canyon_sql::crud::bounds::FieldIdentifier<#ty> for #generics #enum_name #generics { + impl #generics canyon_sql::crud::bounds::FieldIdentifier for #generics #enum_name #generics { fn as_str(&self) -> &'static str { match *self { #(#match_arms_str),* @@ -129,7 +129,7 @@ pub fn generate_enum_with_fields_values(canyon_entity: &CanyonEntity) -> TokenSt #(#fields_names),* } - impl<'a> canyon_sql::crud::bounds::FieldValueIdentifier<'a, #ty> for #enum_name<'a> { + impl<'a> canyon_sql::crud::bounds::FieldValueIdentifier<'a> for #enum_name<'a> { fn value(self) -> (&'static str, &'a dyn QueryParameter<'a>) { match self { #(#match_arms),* diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index cc2a08df..78595594 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -26,7 +26,7 @@ pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { let pg_implementation = create_postgres_fields_mapping(&fields); #[cfg(feature = "postgres")] impl_methods.extend(quote! { - fn deserialize_postgresql(row: &canyon_sql::db_clients::tokio_postgres::Row) -> #ty { + fn deserialize_postgresql(row: &canyon_sql::db_clients::tokio_postgres::Row) -> Self::Output { Self { #(#pg_implementation),* } @@ -37,7 +37,7 @@ pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { let sqlserver_implementation = create_sqlserver_fields_mapping(&fields); #[cfg(feature = "mssql")] impl_methods.extend(quote! { - fn deserialize_sqlserver(row: &canyon_sql::db_clients::tiberius::Row) -> #ty { + fn deserialize_sqlserver(row: &canyon_sql::db_clients::tiberius::Row) -> Self::Output { Self { #(#sqlserver_implementation),* } @@ -48,7 +48,7 @@ pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { let mysql_implementation = create_mysql_fields_mapping(&fields); #[cfg(feature = "mysql")] impl_methods.extend(quote! { - fn deserialize_mysql(row: &canyon_sql::db_clients::mysql_async::Row) -> #ty { + fn deserialize_mysql(row: &canyon_sql::db_clients::mysql_async::Row) -> Self::Output { Self { #(#mysql_implementation),* } @@ -56,7 +56,8 @@ pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { }); quote! { - impl canyon_sql::core::RowMapper for #ty { + impl canyon_sql::core::RowMapper for #ty { + type Output = #ty; #impl_methods } } diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index 792466a0..c41dbab1 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -119,7 +119,6 @@ pub fn crud_operations(input: proc_macro::TokenStream) -> proc_macro::TokenStrea let ast: DeriveInput = syn::parse(input).expect("Error parsing `Canyon Entity for generate the CRUD methods"); let macro_data = MacroTokens::new(&ast); - let table_name_res = helpers::table_schema_parser(¯o_data); let table_schema_data = if let Err(err) = table_name_res { diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index 7939675d..f94ccb60 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -51,8 +51,8 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri }); } - let delete_with_querybuilder = generate_delete_query_tokens(ty, table_schema_data); - delete_ops_tokens.extend(delete_with_querybuilder); + // let delete_with_querybuilder = generate_delete_query_tokens(ty, table_schema_data); + // delete_ops_tokens.extend(delete_with_querybuilder); delete_ops_tokens } diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index 9499c67b..513b644d 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -48,7 +48,7 @@ pub fn generate_find_by_fk_ops( where #ty: std::fmt::Debug + canyon_sql::crud::CrudOperations<#ty> + - canyon_sql::core::RowMapper<#ty> + canyon_sql::core::RowMapper { #(#fk_method_implementations)* #(#rev_fk_method_implementations)* @@ -100,7 +100,7 @@ fn generate_find_by_foreign_key_tokens( quote! { /// Searches the parent entity (if exists) for this type #quoted_method_signature { - <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query_one( + <#fk_ty as canyon_sql::core::Transaction>::query_one( #stmt, &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], "" @@ -114,7 +114,7 @@ fn generate_find_by_foreign_key_tokens( quote! { /// Searches the parent entity (if exists) for this type with the specified datasource #quoted_with_method_signature { - <#fk_ty as canyon_sql::core::Transaction<#fk_ty>>::query_one( + <#fk_ty as canyon_sql::core::Transaction>::query_one( #stmt, &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], input @@ -180,7 +180,7 @@ fn generate_find_by_reverse_foreign_key_tokens( format!("\"{}\"", #f_ident).as_str() ); - <#ty as canyon_sql::core::Transaction<#ty>>::query( + <#ty as canyon_sql::core::Transaction>::query( stmt, &[lookage_value], "" @@ -208,7 +208,7 @@ fn generate_find_by_reverse_foreign_key_tokens( format!("\"{}\"", #f_ident).as_str() ); - <#ty as canyon_sql::core::Transaction<#ty>>::query( + <#ty as canyon_sql::core::Transaction>::query( stmt, &[lookage_value], input diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 4afbc01c..f398561d 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -50,7 +50,7 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let stmt = format!("{} RETURNING {}", #stmt , #primary_key); - self.#pk_ident = <#ty as canyon_sql::core::Transaction<#ty>>::query_one_for::< + self.#pk_ident = <#ty as canyon_sql::core::Transaction>::query_one_for::< String, Vec<&'_ dyn QueryParameter<'_>>, #pk_type @@ -64,7 +64,7 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri } } else { quote! { - <#ty as canyon_sql::core::Transaction<#ty>>::query_rows( // TODO: this should be execute + <#ty as canyon_sql::core::Transaction>::query_rows( // TODO: this should be execute #stmt, values, input @@ -279,7 +279,7 @@ fn generate_multiple_insert_tokens( } } - let multi_insert_result = <#ty as canyon_sql::core::Transaction<#ty>>::query_rows( + let multi_insert_result = <#ty as canyon_sql::core::Transaction>::query_rows( stmt, v_arr, input @@ -378,7 +378,7 @@ fn generate_multiple_insert_tokens( } } - <#ty as canyon_sql::core::Transaction<#ty>>::query_rows( + <#ty as canyon_sql::core::Transaction>::query_rows( stmt, v_arr, input diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index cca36bd9..487b21b0 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -43,7 +43,6 @@ pub struct MacroOperationBuilder { with_no_result_value: bool, // Ok(()) transaction_as_variable: bool, direct_error_return: Option, - enable_mapping: bool, raw_return: bool, propagate_transaction_result: bool, post_body: Option, @@ -77,7 +76,6 @@ impl MacroOperationBuilder { with_no_result_value: false, transaction_as_variable: false, direct_error_return: None, - enable_mapping: false, raw_return: false, propagate_transaction_result: false, post_body: None, @@ -351,7 +349,7 @@ impl MacroOperationBuilder { let unwrap = self.get_unwrap(); let mut base_body_tokens = quote! { - <#ty as canyon_sql::core::Transaction<#ty>>::#transaction_method( + <#ty as canyon_sql::core::Transaction>::#transaction_method( #query_string, #forwarded_parameters, #input_fwd_arg @@ -361,9 +359,7 @@ impl MacroOperationBuilder { if self.propagate_transaction_result { base_body_tokens.extend(quote! { ? }) }; - if self.enable_mapping { - base_body_tokens.extend(quote! { .into_results::<#ty>() }) - }; + if self.with_no_result_value { // TODO: should we validate some combinations? in the future, some of them can be hard to reason about // like transaction_as_variable and with_no_result_value, they can't coexist diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index 9bc17de0..e5451121 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -37,15 +37,17 @@ pub fn impl_crud_operations_trait_for_struct( }; crud_ops_tokens.extend(quote! { - use canyon_sql::core::IntoResults; + // use canyon_sql::core::IntoResults; // TODO: isn't being used anymore + use canyon_sql::core::RowMapper; impl canyon_sql::crud::CrudOperations<#ty> for #ty { #crud_operations_tokens } - impl canyon_sql::core::Transaction<#ty> for #ty {} + impl canyon_sql::core::Transaction for #ty {} }); + // NOTE: this extends should be documented WHY is needed to be after the base impl of CrudOperations let foreign_key_ops_tokens = generate_find_by_fk_ops(macro_data, &table_schema_data); crud_ops_tokens.extend(quote! { #foreign_key_ops_tokens }); diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 2815faf1..c80aa588 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -58,8 +58,8 @@ fn generate_find_all_query_tokens( /// entity but converted to the corresponding database convention, /// unless concrete values are set on the available parameters of the /// `canyon_macro(table_name = "table_name", schema = "schema")` - fn select_query<'a>() -> canyon_sql::query::SelectQueryBuilder<'a, #ty, &'a str> { - canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, "") + fn select_query<'a>() -> canyon_sql::query::SelectQueryBuilder<'a, #ty> { + canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, canyon_sql::connection::DatabaseType::default()) } /// Generates a [`canyon_sql::query::SelectQueryBuilder`] @@ -73,10 +73,10 @@ fn generate_find_all_query_tokens( /// The query it's made against the database with the configured datasource /// described in the configuration file, and selected with the [`&str`] /// passed as parameter. - fn select_query_with<'a, I>(input: I) -> canyon_sql::query::SelectQueryBuilder<'a, #ty, I> - where I: canyon_sql::core::DbConnection + Send + 'a + fn select_query_with<'a>(database_type: canyon_sql::connection::DatabaseType) + -> canyon_sql::query::SelectQueryBuilder<'a, #ty> { - canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, input) + canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, database_type) } } } @@ -126,14 +126,6 @@ fn generate_find_by_pk_tokens( }; } - // // TODO: this can be functionally handled, instead of this impl - // let result_handling = quote! { - // n if n.len() == 0 => Ok(None), - // _ => Ok( - // Some(transaction_result.into_results::<#ty>().remove(0)) - // ) - // }; - let find_by_pk = create_find_by_pk_macro(ty, &stmt); let find_by_pk_with = create_find_by_pk_with(ty, &stmt); diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index 41de2f5a..082551dd 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -39,7 +39,7 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri ); let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = &[#(#update_values),*]; - <#ty as canyon_sql::core::Transaction<#ty>>::execute(stmt, update_values, "").await + <#ty as canyon_sql::core::Transaction>::execute(stmt, update_values, "").await } /// Updates a database record that matches /// the current instance of a T type, returning a result @@ -55,7 +55,7 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri ); let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = &[#(#update_values_cloned),*]; - <#ty as canyon_sql::core::Transaction<#ty>>::execute(stmt, update_values, input).await + <#ty as canyon_sql::core::Transaction>::execute(stmt, update_values, input).await } }); } else { @@ -70,8 +70,8 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri }); } - let querybuilder_update_tokens = generate_update_query_tokens(ty, table_schema_data); - update_ops_tokens.extend(querybuilder_update_tokens); + // let querybuilder_update_tokens = generate_update_query_tokens(ty, table_schema_data); + // update_ops_tokens.extend(querybuilder_update_tokens); update_ops_tokens } diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index ec5ff3e2..59f827c0 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -21,7 +21,7 @@ use crate::{ #[derive(PartialDebug)] pub struct Migrations; // Makes this structure able to make queries to the database -impl Transaction for Migrations {} +impl Transaction for Migrations {} impl Migrations { /// Launches the mechanism to parse the Database schema, the Canyon register diff --git a/canyon_migrations/src/migrations/memory.rs b/canyon_migrations/src/migrations/memory.rs index 508ac02c..21e3e318 100644 --- a/canyon_migrations/src/migrations/memory.rs +++ b/canyon_migrations/src/migrations/memory.rs @@ -54,7 +54,7 @@ pub struct CanyonMemory { } // Makes this structure able to make queries to the database -impl Transaction for CanyonMemory {} +impl Transaction for CanyonMemory {} impl CanyonMemory { /// Queries the database to retrieve internal data about the structures diff --git a/canyon_migrations/src/migrations/processor.rs b/canyon_migrations/src/migrations/processor.rs index 3bb98fc6..ff74b564 100644 --- a/canyon_migrations/src/migrations/processor.rs +++ b/canyon_migrations/src/migrations/processor.rs @@ -32,7 +32,7 @@ pub struct MigrationsProcessor { constraints_column_operations: Vec, constraints_sequence_operations: Vec, } -impl Transaction for MigrationsProcessor {} +impl Transaction for MigrationsProcessor {} impl MigrationsProcessor { pub async fn process<'a>( @@ -776,7 +776,7 @@ enum TableOperation { DeleteTablePrimaryKey(String, String), } -impl Transaction for TableOperation {} +impl Transaction for TableOperation {} impl DatabaseOperation for TableOperation { async fn generate_sql(&self, datasource: &DatasourceConfig) { @@ -928,7 +928,7 @@ enum ColumnOperation { AlterColumnDropIdentity(String, CanyonRegisterEntityField), } -impl Transaction for ColumnOperation {} +impl Transaction for ColumnOperation {} impl DatabaseOperation for ColumnOperation { async fn generate_sql(&self, datasource: &DatasourceConfig) { @@ -1033,7 +1033,7 @@ enum SequenceOperation { ModifySequence(String, CanyonRegisterEntityField), } #[cfg(feature = "postgres")] -impl Transaction for SequenceOperation {} +impl Transaction for SequenceOperation {} #[cfg(feature = "postgres")] impl DatabaseOperation for SequenceOperation { diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index e713913f..281642cb 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -39,187 +39,187 @@ fn test_generated_sql_by_the_select_querybuilder() { ) } -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder() { - // Find all the leagues with ID less or equals that 7 - // and where it's region column value is equals to 'Korea' - let filtered_leagues_result: Result, _> = League::select_query() - .r#where(LeagueFieldValue::id(&50), Comp::LtEq) - .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) - .query() - .await; - - let filtered_leagues: Vec = filtered_leagues_result.unwrap(); - assert!(!filtered_leagues.is_empty()); - - let league_idx_0 = filtered_leagues.first().unwrap(); - assert_eq!(league_idx_0.id, 34); - assert_eq!(league_idx_0.region, "KOREA"); -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_fulllike() { - // Find all the leagues with "LC" in their name - let filtered_leagues_result = - League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Full); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_fulllike_with_mssql() { - // Find all the leagues with "LC" in their name - let filtered_leagues_result = - League::select_query_with(SQL_SERVER_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Full); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_fulllike_with_mysql() { - // Find all the leagues with "LC" in their name - let filtered_leagues_result = - League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Full); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR) ,'%')" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_leftlike() { - // Find all the leagues whose name ends with "CK" - let filtered_leagues_result = - League::select_query().r#where(LeagueFieldValue::name(&"CK"), Like::Left); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_leftlike_with_mssql() { - // Find all the leagues whose name ends with "CK" - let filtered_leagues_result = - League::select_query().r#where(LeagueFieldValue::name(&"CK"), Like::Left); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_leftlike_with_mysql() { - // Find all the leagues whose name ends with "CK" - let filtered_leagues_result = - League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"CK"), Like::Left); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR))" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_rightlike() { - // Find all the leagues whose name starts with "LC" - let filtered_leagues_result = - League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Right); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_rightlike_with_mssql() { - // Find all the leagues whose name starts with "LC" - let filtered_leagues_result = League::select_query_with(SQL_SERVER_DS) - .r#where(LeagueFieldValue::name(&"LC"), Like::Right); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_rightlike_with_mysql() { - // Find all the leagues whose name starts with "LC" - let filtered_leagues_result = - League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Right); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS CHAR) ,'%')" - ) -} - -/// Same than the above but with the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_with_mssql() { - // Find all the players where its ID column value is greater than 50 - let filtered_find_players = Player::select_query_with(SQL_SERVER_DS) - .r#where(PlayerFieldValue::id(&50), Comp::Gt) - .query() - .await; - - assert!(!filtered_find_players.unwrap().is_empty()); -} - -/// Same than the above but with the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_with_mysql() { - // Find all the players where its ID column value is greater than 50 - let filtered_find_players = Player::select_query_with(MYSQL_DS) - .r#where(PlayerFieldValue::id(&50), Comp::Gt) - .query() - .await; - - assert!(!filtered_find_players.unwrap().is_empty()); -} +// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder() { +// // Find all the leagues with ID less or equals that 7 +// // and where it's region column value is equals to 'Korea' +// let filtered_leagues_result: Result, _> = League::select_query() +// .r#where(LeagueFieldValue::id(&50), Comp::LtEq) +// .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) +// .query::(MYSQL_DS) +// .await; +// +// let filtered_leagues: Vec = filtered_leagues_result.unwrap(); +// assert!(!filtered_leagues.is_empty()); +// +// let league_idx_0 = filtered_leagues.first().unwrap(); +// assert_eq!(league_idx_0.id, 34); +// assert_eq!(league_idx_0.region, "KOREA"); +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_fulllike() { +// // Find all the leagues with "LC" in their name +// let filtered_leagues_result = +// League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Full); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" +// ) +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_fulllike_with_mssql() { +// // Find all the leagues with "LC" in their name +// let filtered_leagues_result = +// League::select_query_with(SQL_SERVER_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Full); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" +// ) +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_fulllike_with_mysql() { +// // Find all the leagues with "LC" in their name +// let filtered_leagues_result = +// League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Full); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR) ,'%')" +// ) +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_leftlike() { +// // Find all the leagues whose name ends with "CK" +// let filtered_leagues_result = +// League::select_query().r#where(LeagueFieldValue::name(&"CK"), Like::Left); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" +// ) +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_leftlike_with_mssql() { +// // Find all the leagues whose name ends with "CK" +// let filtered_leagues_result = +// League::select_query().r#where(LeagueFieldValue::name(&"CK"), Like::Left); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" +// ) +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_leftlike_with_mysql() { +// // Find all the leagues whose name ends with "CK" +// let filtered_leagues_result = +// League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"CK"), Like::Left); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR))" +// ) +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_rightlike() { +// // Find all the leagues whose name starts with "LC" +// let filtered_leagues_result = +// League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Right); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" +// ) +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_rightlike_with_mssql() { +// // Find all the leagues whose name starts with "LC" +// let filtered_leagues_result = League::select_query_with(SQL_SERVER_DS) +// .r#where(LeagueFieldValue::name(&"LC"), Like::Right); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" +// ) +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_rightlike_with_mysql() { +// // Find all the leagues whose name starts with "LC" +// let filtered_leagues_result = +// League::select_query_with(DatabaseType::MySQL).r#where(LeagueFieldValue::name(&"LC"), Like::Right); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS CHAR) ,'%')" +// ) +// } +// +// /// Same than the above but with the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_with_mssql() { +// // Find all the players where its ID column value is greater than 50 +// let filtered_find_players = Player::select_query_with(DatabaseType::SqlServer) +// .r#where(PlayerFieldValue::id(&50), Comp::Gt) +// .query::(SQL_SERVER_DS) +// .await; +// +// assert!(!filtered_find_players.unwrap().is_empty()); +// } +// +// /// Same than the above but with the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_with_mysql() { +// // Find all the players where its ID column value is greater than 50 +// let filtered_find_players = Player::select_query_with(DatabaseType::MySQL) +// .r#where(PlayerFieldValue::id(&50), Comp::Gt) +// .query::(MYSQL_DS) +// .await; +// +// assert!(!filtered_find_players.unwrap().is_empty()); +// } // // /// Updates the values of the range on entries defined by the constraint parameters // /// in the database entity From 4d7c013bcfadd3810b82b91af272df4f2a61169a Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 13 Feb 2025 15:33:37 +0100 Subject: [PATCH 081/155] fix: removing the unnecessary bound of CrudOperations on Foreign Key --- canyon_macros/src/query_operations/foreign_key.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index 513b644d..76e2859b 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -47,7 +47,6 @@ pub fn generate_find_by_fk_ops( impl #fk_trait_ident<#ty> for #ty where #ty: std::fmt::Debug + - canyon_sql::crud::CrudOperations<#ty> + canyon_sql::core::RowMapper { #(#fk_method_implementations)* From 4466aadfc6975f961520e405b591642b650200ea Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 13 Feb 2025 16:51:11 +0100 Subject: [PATCH 082/155] feat(WIP!): adjusting the output type parameter on RowMapper across the dependent types --- .../src/connection/db_clients/mssql.rs | 6 ++- .../src/connection/db_clients/mysql.rs | 6 ++- .../src/connection/db_clients/postgresql.rs | 6 ++- canyon_core/src/connection/db_connector.rs | 21 +++++---- canyon_core/src/transaction.rs | 5 +- canyon_crud/src/crud.rs | 37 ++++++++------- .../src/query_elements/query_builder.rs | 10 ++-- .../src/query_operations/foreign_key.rs | 12 +++-- canyon_macros/src/query_operations/insert.rs | 4 +- .../src/query_operations/macro_template.rs | 47 +++++++++++++++---- canyon_macros/src/query_operations/mod.rs | 2 +- canyon_macros/src/query_operations/read.rs | 20 +++++--- 12 files changed, 115 insertions(+), 61 deletions(-) diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs index f3c6a515..6d8cd2cc 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -32,7 +32,8 @@ impl DbConnection for SqlServerConnection { ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where S: AsRef + Display + Send, - R: RowMapper + R: RowMapper, + Vec: FromIterator<::Output> { sqlserver_query_launcher::query(stmt, params, self) } @@ -84,7 +85,8 @@ pub(crate) mod sqlserver_query_launcher { ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, - R: RowMapper + R: RowMapper, + Vec: FromIterator<::Output> { Ok(execute_query(stmt.as_ref(), params, conn) .await? diff --git a/canyon_core/src/connection/db_clients/mysql.rs b/canyon_core/src/connection/db_clients/mysql.rs index 5de270b7..4b210a1d 100644 --- a/canyon_core/src/connection/db_clients/mysql.rs +++ b/canyon_core/src/connection/db_clients/mysql.rs @@ -34,7 +34,8 @@ impl DbConnection for MysqlConnection { ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where S: AsRef + Display + Send, - R: RowMapper + R: RowMapper, + Vec: FromIterator<::Output> { mysql_query_launcher::query(stmt, params, self) } @@ -93,7 +94,8 @@ pub(crate) mod mysql_query_launcher { ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, - R: RowMapper + R: RowMapper, + Vec: FromIterator<::Output> { Ok(execute_query(stmt, params, conn) .await? diff --git a/canyon_core/src/connection/db_clients/postgresql.rs b/canyon_core/src/connection/db_clients/postgresql.rs index 3273e100..0c962e8b 100644 --- a/canyon_core/src/connection/db_clients/postgresql.rs +++ b/canyon_core/src/connection/db_clients/postgresql.rs @@ -32,7 +32,8 @@ impl DbConnection for PostgreSqlConnection { ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where S: AsRef + Display + Send, - R: RowMapper + R: RowMapper, + Vec: FromIterator<::Output> { postgres_query_launcher::query(stmt, params, self) } @@ -83,7 +84,8 @@ pub(crate) mod postgres_query_launcher { ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, - R: RowMapper + R: RowMapper, + Vec: FromIterator<::Output> { Ok(conn .client diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index acb5ede2..e0b37517 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -31,14 +31,15 @@ pub trait DbConnection { ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where S: AsRef + Display + Send, - R: RowMapper; + R: RowMapper, + Vec: FromIterator<::Output>; fn query_one<'a, R>( &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send - where R: RowMapper; + where R: RowMapper; /// Flexible and general method that queries the target database for a concrete instance /// of some type T. @@ -84,7 +85,7 @@ impl DbConnection for &str { ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, - R: RowMapper + R: RowMapper { let conn = get_database_connection_by_ds(Some(self)).await?; conn.query(stmt, params).await @@ -95,7 +96,7 @@ impl DbConnection for &str { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result, Box<(dyn Error + Sync + Send)>> - where R: RowMapper + where R: RowMapper { let sane_ds_name = if !self.is_empty() { Some(*self) } else { None }; let conn = get_database_connection_by_ds(sane_ds_name).await?; @@ -157,7 +158,8 @@ impl DbConnection for DatabaseConnection { ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, - R: RowMapper + R: RowMapper, + Vec: FromIterator<::Output> { match self { #[cfg(feature = "postgres")] @@ -176,7 +178,7 @@ impl DbConnection for DatabaseConnection { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result, Box<(dyn Error + Sync + Send)>> - where R: RowMapper + where R: RowMapper { db_conn_query_one_impl(self, stmt, params).await } @@ -234,7 +236,8 @@ impl DbConnection for &mut DatabaseConnection { ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, - R: RowMapper + R: RowMapper, + Vec: FromIterator<::Output> { match self { #[cfg(feature = "postgres")] @@ -253,7 +256,7 @@ impl DbConnection for &mut DatabaseConnection { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result, Box<(dyn Error + Sync + Send)>> - where R: RowMapper + where R: RowMapper { db_conn_query_one_impl(self, stmt, params).await } @@ -466,7 +469,7 @@ mod connection_helpers { stmt: &str, params: &[&'a (dyn QueryParameter<'a> + 'a)], ) -> Result, Box> - where R: RowMapper + where R: RowMapper { match c { #[cfg(feature = "postgres")] diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index 355e6a74..c7af5a5d 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -13,7 +13,8 @@ pub trait Transaction { ) -> impl Future, Box<(dyn Error + Sync + Send)>>> where S: AsRef + Display + Send, - R: RowMapper + R: RowMapper, + Vec: FromIterator<::Output> { async move { input.query(stmt, params).await } } @@ -26,7 +27,7 @@ pub trait Transaction { where S: AsRef + Display + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send + 'a, - R: RowMapper + R: RowMapper { async move { input.query_one(stmt.as_ref(), params.as_ref()).await } } diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 88de5676..7f69e153 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -23,24 +23,28 @@ use canyon_core::mapper::RowMapper; /// See it's definition and docs to see the implementations. /// Also, you can find the written macro-code that performs the auto-mapping /// in the *canyon_sql_root::canyon_macros* crates, on the root of this project. -pub trait CrudOperations>: Send + Sync { - fn find_all() -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send; +pub trait CrudOperations: Send + Sync { + fn find_all() + -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send + where R: RowMapper; - fn find_all_with<'a, I>( + fn find_all_with<'a, R, I>( input: I, ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send where - I: DbConnection + Send + 'a; + I: DbConnection + Send + 'a, + R: RowMapper; - fn find_all_unchecked() -> impl Future> + Send; + fn find_all_unchecked() -> impl Future> + Send; - fn find_all_unchecked_with<'a, I>(input: I) -> impl Future> + Send + fn find_all_unchecked_with<'a, R, I>(input: I) -> impl Future> + Send where - I: DbConnection + Send + 'a; + I: DbConnection + Send + 'a, + R: RowMapper; - fn select_query<'a>() -> SelectQueryBuilder<'a, R>; + fn select_query<'a, R: RowMapper>() -> SelectQueryBuilder<'a, R>; - fn select_query_with<'a>(database_type: DatabaseType) -> SelectQueryBuilder<'a, R>; + fn select_query_with<'a, R: RowMapper>(database_type: DatabaseType) -> SelectQueryBuilder<'a, R>; fn count() -> impl Future>> + Send; @@ -50,16 +54,17 @@ pub trait CrudOperations>: Send + Sync { where I: DbConnection + Send + 'a; - fn find_by_pk<'a>( + fn find_by_pk<'a, R: RowMapper>( value: &'a dyn QueryParameter<'a>, ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send; - fn find_by_pk_with<'a, I>( + fn find_by_pk_with<'a, R, I>( value: &'a dyn QueryParameter<'a>, input: I, ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send where - I: DbConnection + Send + 'a; + I: DbConnection + Send + 'a, + R: RowMapper; fn insert<'a>( &'a mut self, @@ -72,12 +77,12 @@ pub trait CrudOperations>: Send + Sync { where I: DbConnection + Send + 'a; - fn multi_insert<'a>( - instances: &'a mut [&'a mut R], + fn multi_insert<'a, T>( + instances: &'a mut [&'a mut T], ) -> impl Future>> + Send; - fn multi_insert_with<'a, I>( - instances: &'a mut [&'a mut R], + fn multi_insert_with<'a, T, I>( + instances: &'a mut [&'a mut T], input: I, ) -> impl Future>> + Send where diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index 4e085d99..8c47142a 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -123,7 +123,7 @@ pub mod ops { } /// Type for construct more complex queries than the classical CRUD ones. -pub struct QueryBuilder<'a, R: RowMapper>{ +pub struct QueryBuilder<'a, R: RowMapper>{ // query: Query<'a>, sql: String, params: Vec<&'a dyn QueryParameter<'a>>, @@ -131,9 +131,9 @@ pub struct QueryBuilder<'a, R: RowMapper>{ pd: PhantomData, } -unsafe impl<'a, R: RowMapper> Sync for QueryBuilder<'a, R> {} +unsafe impl<'a, R: RowMapper> Sync for QueryBuilder<'a, R> {} -impl<'a, R: RowMapper> QueryBuilder<'a, R> { +impl<'a, R: RowMapper> QueryBuilder<'a, R> { pub fn new(sql: String, database_type: DatabaseType) -> Self { Self { sql, @@ -262,12 +262,12 @@ impl<'a, R: RowMapper> QueryBuilder<'a, R> { } } -pub struct SelectQueryBuilder<'a, R: RowMapper> +pub struct SelectQueryBuilder<'a, R: RowMapper> { _inner: QueryBuilder<'a, R>, } -impl<'a, R: RowMapper> SelectQueryBuilder<'a, R> { +impl<'a, R: RowMapper> SelectQueryBuilder<'a, R> { /// Generates a new public instance of the [`SelectQueryBuilder`] pub fn new(table_schema_data: &str, database_type: DatabaseType) -> Self { Self { diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index 76e2859b..a1486e82 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -150,13 +150,17 @@ fn generate_find_by_reverse_foreign_key_tokens( proc_macro2::Span::call_site(), ); let quoted_method_signature: TokenStream = quote! { - async fn #method_name_ident<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send>(value: &F) -> - Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + async fn #method_name_ident<'a, R, F>(value: &F) + -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + where R: RowMapper, + F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send }; let quoted_with_method_signature: TokenStream = quote! { - async fn #method_name_ident_with<'a, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send, I> (value: &F, input: I) + async fn #method_name_ident_with<'a, R, F, I> (value: &F, input: I) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> - where I: canyon_sql::core::DbConnection + Send + 'a + where R: RowMapper, + F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send, + I: canyon_sql::core::DbConnection + Send + 'a }; let f_ident = field_ident.to_string(); diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index f398561d..daeaca92 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -422,7 +422,7 @@ fn generate_multiple_insert_tokens( /// ).await ///.ok(); /// ``` - async fn multi_insert<'a>(instances: &'a mut [&'a mut #ty]) -> ( + async fn multi_insert<'a, T>(instances: &'a mut [&'a mut T]) -> ( Result<(), Box> ) { use canyon_sql::core::QueryParameter; @@ -479,7 +479,7 @@ fn generate_multiple_insert_tokens( /// ).await /// .ok(); /// ``` - async fn multi_insert_with<'a, I>(instances: &'a mut [&'a mut #ty], input: I) -> + async fn multi_insert_with<'a, T, I>(instances: &'a mut [&'a mut T], input: I) -> Result<(), Box> where I: canyon_sql::core::DbConnection + Send + 'a diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index 487b21b0..14c88b37 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -28,6 +28,7 @@ pub struct MacroOperationBuilder { user_type: Option, lifetime: bool, self_as_ref: bool, + type_is_row_mapper: bool, input_param: Option, input_fwd_arg: Option, return_type: Option, @@ -61,6 +62,7 @@ impl MacroOperationBuilder { user_type: None, lifetime: false, self_as_ref: false, + type_is_row_mapper: false, input_param: None, input_fwd_arg: None, return_type: None, @@ -109,13 +111,23 @@ impl MacroOperationBuilder { } fn compose_fn_signature_generics(&self) -> TokenStream { - if !&self.lifetime && self.input_param.is_none() { - quote! {} - } else if self.lifetime && self.input_param.is_none() { - quote! { <'a> } - } else { - quote! { <'a, I> } + if !&self.lifetime && self.input_param.is_none() && !self.type_is_row_mapper { + return quote! {}; + } + + let mut generics = quote!{ < }; + + if self.lifetime { + generics.extend(quote! { 'a, }); + } + if self.type_is_row_mapper { + generics.extend(quote! { R, }); } + if self.input_param.is_some() { + generics.extend(quote! { I }); + } + generics.extend(quote!{ > }); + generics } fn compose_self_params_separator(&self) -> TokenStream { @@ -173,19 +185,34 @@ impl MacroOperationBuilder { self.input_fwd_arg = Some(quote! { input }); self.lifetime = true; self.where_clause_bounds.push(quote! { - I: canyon_sql::core::DbConnection + Send + 'a, + I: canyon_sql::core::DbConnection + Send + 'a }); self } - fn get_return_type(&self) -> TokenStream { - let organic_ret_type = if let Some(return_ty_ts) = &self.return_type_ts { + pub fn type_is_row_mapper(mut self) -> Self { + self.type_is_row_mapper = true; + let ret_ty = self.get_organic_ret_ty(); + self.where_clause_bounds.push(quote!{ + R: RowMapper + }); + self + } + + fn get_organic_ret_ty(&self) -> TokenStream { + if let Some(return_ty_ts) = &self.return_type_ts { let rt_ts = return_ty_ts; quote! { #rt_ts } + } else if self.type_is_row_mapper { + quote! { R } } else { let rt = &self.return_type; quote! { #rt } - }; + } + } + + fn get_return_type(&self) -> TokenStream { + let organic_ret_type = self.get_organic_ret_ty(); let container_ret_type = if self.single_result { quote! { Option } diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index e5451121..9a19d57c 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -40,7 +40,7 @@ pub fn impl_crud_operations_trait_for_struct( // use canyon_sql::core::IntoResults; // TODO: isn't being used anymore use canyon_sql::core::RowMapper; - impl canyon_sql::crud::CrudOperations<#ty> for #ty { + impl canyon_sql::crud::CrudOperations for #ty { #crud_operations_tokens } diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index c80aa588..8fe97945 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -58,7 +58,7 @@ fn generate_find_all_query_tokens( /// entity but converted to the corresponding database convention, /// unless concrete values are set on the available parameters of the /// `canyon_macro(table_name = "table_name", schema = "schema")` - fn select_query<'a>() -> canyon_sql::query::SelectQueryBuilder<'a, #ty> { + fn select_query<'a, R: RowMapper>() -> canyon_sql::query::SelectQueryBuilder<'a, #ty> { canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, canyon_sql::connection::DatabaseType::default()) } @@ -73,7 +73,7 @@ fn generate_find_all_query_tokens( /// The query it's made against the database with the configured datasource /// described in the configuration file, and selected with the [`&str`] /// passed as parameter. - fn select_query_with<'a>(database_type: canyon_sql::connection::DatabaseType) + fn select_query_with<'a, R: RowMapper>(database_type: canyon_sql::connection::DatabaseType) -> canyon_sql::query::SelectQueryBuilder<'a, #ty> { canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, database_type) @@ -95,8 +95,8 @@ fn generate_find_by_pk_tokens( // Disabled if there's no `primary_key` annotation if pk.is_empty() { return quote! { - async fn find_by_pk<'a>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) - -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + async fn find_by_pk<'a, R: RowMapper>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) + -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> { Err( std::io::Error::new( @@ -108,11 +108,13 @@ fn generate_find_by_pk_tokens( ) } - async fn find_by_pk_with<'a, I>( + async fn find_by_pk_with<'a, R, I>( value: &'a dyn canyon_sql::core::QueryParameter<'a>, input: I ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> - where I: canyon_sql::core::DbConnection + Send + 'a, + where + I: canyon_sql::core::DbConnection + Send + 'a, + R: RowMapper { Err( std::io::Error::new( @@ -147,6 +149,7 @@ mod __details { pub fn create_find_all_macro(ty: &syn::Ident, stmt: &str) -> MacroOperationBuilder { MacroOperationBuilder::new() .fn_name("find_all") + .type_is_row_mapper() .user_type(ty) .return_type(ty) .add_doc_comment("Executes a 'SELECT * FROM '") @@ -157,6 +160,7 @@ mod __details { pub fn create_find_all_with_macro(ty: &syn::Ident, stmt: &str) -> MacroOperationBuilder { MacroOperationBuilder::new() .fn_name("find_all_with") + .type_is_row_mapper() .with_input_param() .user_type(ty) .return_type(ty) @@ -171,6 +175,7 @@ mod __details { ) -> MacroOperationBuilder { MacroOperationBuilder::new() .fn_name("find_all_unchecked") + .type_is_row_mapper() .user_type(ty) .return_type(ty) .add_doc_comment("Executes a 'SELECT * FROM '") @@ -185,6 +190,7 @@ mod __details { ) -> MacroOperationBuilder { MacroOperationBuilder::new() .fn_name("find_all_unchecked_with") + .type_is_row_mapper() .user_type(ty) .return_type(ty) .with_input_param() @@ -282,6 +288,7 @@ mod __details { MacroOperationBuilder::new() .fn_name("find_by_pk") .with_lifetime() + .type_is_row_mapper() .user_type(ty) .return_type(ty) .add_doc_comment(doc_comments::FIND_BY_PK) @@ -295,6 +302,7 @@ mod __details { pub fn create_find_by_pk_with(ty: &Ident, stmt: &str) -> MacroOperationBuilder { MacroOperationBuilder::new() .fn_name("find_by_pk_with") + .type_is_row_mapper() .with_input_param() .user_type(ty) .return_type(ty) From 5d64f7690b08cf9d38e99cc1dece194d24c38301 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 14 Feb 2025 13:05:15 +0100 Subject: [PATCH 083/155] feat(WIP!): preparing the introduction of an EntityMetadata trait to dynamically retrieve the tokens that compose the RowMapper implementor on the queries --- .../src/connection/db_clients/mssql.rs | 24 +- .../src/connection/db_clients/mysql.rs | 16 +- .../src/connection/db_clients/postgresql.rs | 24 +- canyon_core/src/connection/db_connector.rs | 44 +- canyon_core/src/mapper.rs | 6 +- canyon_core/src/rows.rs | 12 +- canyon_core/src/transaction.rs | 8 +- canyon_crud/src/crud.rs | 78 ++- .../src/query_elements/query_builder.rs | 89 ++- .../src/query_operations/foreign_key.rs | 15 +- canyon_macros/src/query_operations/insert.rs | 4 +- canyon_macros/src/query_operations/mod.rs | 2 +- canyon_macros/src/query_operations/read.rs | 166 +++-- canyon_macros/src/utils/helpers.rs | 54 +- canyon_macros/src/utils/macro_tokens.rs | 8 +- tests/crud/delete_operations.rs | 318 ++++----- tests/crud/foreign_key_operations.rs | 326 ++++----- tests/crud/init_mssql.rs | 124 ++-- tests/crud/insert_operations.rs | 634 +++++++++--------- tests/crud/querybuilder_operations.rs | 78 +-- tests/crud/read_operations.rs | 175 ++--- tests/crud/update_operations.rs | 284 ++++---- 22 files changed, 1255 insertions(+), 1234 deletions(-) diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs index 6d8cd2cc..5bdf0bea 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -32,8 +32,8 @@ impl DbConnection for SqlServerConnection { ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where S: AsRef + Display + Send, - R: RowMapper, - Vec: FromIterator<::Output> + R: RowMapper, + Vec: FromIterator<::Output>, { sqlserver_query_launcher::query(stmt, params, self) } @@ -42,11 +42,11 @@ impl DbConnection for SqlServerConnection { &self, stmt: &str, params: &[&'a (dyn QueryParameter<'a>)], - ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where - R: RowMapper + R: RowMapper, { - sqlserver_query_launcher::query_one(stmt, params, self) + sqlserver_query_launcher::query_one::(stmt, params, self) } fn query_one_for<'a, T: FromSqlOwnedValue>( @@ -85,8 +85,8 @@ pub(crate) mod sqlserver_query_launcher { ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, - R: RowMapper, - Vec: FromIterator<::Output> + R: RowMapper, + Vec: FromIterator<::Output>, { Ok(execute_query(stmt.as_ref(), params, conn) .await? @@ -119,9 +119,9 @@ pub(crate) mod sqlserver_query_launcher { stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &SqlServerConnection, - ) -> Result, Box<(dyn Error + Send + Sync)>> + ) -> Result, Box<(dyn Error + Send + Sync)>> where - R: RowMapper, + R: RowMapper, { let result = execute_query(stmt, params, conn).await?.into_row().await?; @@ -202,8 +202,10 @@ pub(crate) mod sqlserver_query_launcher { // TODO: We must address the query generation let mut mssql_query = Query::new(stmt.to_owned().replace('$', "@P")); - params.iter().for_each(|param| { mssql_query.bind(*param); }); - + params.iter().for_each(|param| { + mssql_query.bind(*param); + }); + mssql_query } } diff --git a/canyon_core/src/connection/db_clients/mysql.rs b/canyon_core/src/connection/db_clients/mysql.rs index 4b210a1d..dc0b247e 100644 --- a/canyon_core/src/connection/db_clients/mysql.rs +++ b/canyon_core/src/connection/db_clients/mysql.rs @@ -35,7 +35,7 @@ impl DbConnection for MysqlConnection { where S: AsRef + Display + Send, R: RowMapper, - Vec: FromIterator<::Output> + Vec: FromIterator<::Output>, { mysql_query_launcher::query(stmt, params, self) } @@ -44,10 +44,11 @@ impl DbConnection for MysqlConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send - where R: RowMapper + ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send + where + R: RowMapper, { - mysql_query_launcher::query_one(stmt, params, self) + mysql_query_launcher::query_one::(stmt, params, self) } fn query_one_for<'a, T: FromSqlOwnedValue>( @@ -95,7 +96,7 @@ pub(crate) mod mysql_query_launcher { where S: AsRef + Display + Send, R: RowMapper, - Vec: FromIterator<::Output> + Vec: FromIterator<::Output>, { Ok(execute_query(stmt, params, conn) .await? @@ -118,8 +119,9 @@ pub(crate) mod mysql_query_launcher { stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &MysqlConnection, - ) -> Result, Box<(dyn Error + Sync + Send)>> - where R: RowMapper + ) -> Result, Box<(dyn Error + Sync + Send)>> + where + R: RowMapper, { let result = execute_query(stmt, params, conn).await?; diff --git a/canyon_core/src/connection/db_clients/postgresql.rs b/canyon_core/src/connection/db_clients/postgresql.rs index 0c962e8b..d47bacd7 100644 --- a/canyon_core/src/connection/db_clients/postgresql.rs +++ b/canyon_core/src/connection/db_clients/postgresql.rs @@ -33,7 +33,7 @@ impl DbConnection for PostgreSqlConnection { where S: AsRef + Display + Send, R: RowMapper, - Vec: FromIterator<::Output> + Vec: FromIterator<::Output>, { postgres_query_launcher::query(stmt, params, self) } @@ -42,10 +42,11 @@ impl DbConnection for PostgreSqlConnection { &self, stmt: &str, params: &[&'a (dyn QueryParameter<'a>)], - ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send - where R: RowMapper + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send + where + R: RowMapper, { - postgres_query_launcher::query_one(stmt, params, self) + postgres_query_launcher::query_one::(stmt, params, self) } fn query_one_for<'a, T: FromSqlOwnedValue>( @@ -85,7 +86,7 @@ pub(crate) mod postgres_query_launcher { where S: AsRef + Display + Send, R: RowMapper, - Vec: FromIterator<::Output> + Vec: FromIterator<::Output>, { Ok(conn .client @@ -117,21 +118,22 @@ pub(crate) mod postgres_query_launcher { stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &PostgreSqlConnection, - ) -> Result, Box<(dyn Error + Sync + Send)>> - where R: RowMapper + ) -> Result, Box<(dyn Error + Sync + Send)>> + where + R: RowMapper, { let m_params: Vec<_> = params .iter() .map(|param| param.as_postgres_param()) .collect(); let result = conn.client.query_one(stmt, m_params.as_slice()).await; - + match result { - Ok(row) => { Ok(Some(R::deserialize_postgresql(&row))) }, + Ok(row) => Ok(Some(R::deserialize_postgresql(&row))), Err(e) => match e.to_string().contains("unexpected number of rows") { - true => { Ok(None) }, + true => Ok(None), _ => Err(e)?, - } + }, } } diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index e0b37517..d59eafff 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -38,8 +38,9 @@ pub trait DbConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send - where R: RowMapper; + ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send + where + R: RowMapper; /// Flexible and general method that queries the target database for a concrete instance /// of some type T. @@ -85,7 +86,8 @@ impl DbConnection for &str { ) -> Result, Box<(dyn Error + Sync + Send)>> where S: AsRef + Display + Send, - R: RowMapper + R: RowMapper, + Vec: FromIterator<::Output>, { let conn = get_database_connection_by_ds(Some(self)).await?; conn.query(stmt, params).await @@ -95,12 +97,13 @@ impl DbConnection for &str { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result, Box<(dyn Error + Sync + Send)>> - where R: RowMapper + ) -> Result, Box<(dyn Error + Sync + Send)>> + where + R: RowMapper, { let sane_ds_name = if !self.is_empty() { Some(*self) } else { None }; let conn = get_database_connection_by_ds(sane_ds_name).await?; - conn.query_one(stmt, params).await + conn.query_one::(stmt, params).await } async fn query_one_for<'a, T: FromSqlOwnedValue>( @@ -159,7 +162,7 @@ impl DbConnection for DatabaseConnection { where S: AsRef + Display + Send, R: RowMapper, - Vec: FromIterator<::Output> + Vec: FromIterator<::Output>, { match self { #[cfg(feature = "postgres")] @@ -177,10 +180,11 @@ impl DbConnection for DatabaseConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result, Box<(dyn Error + Sync + Send)>> - where R: RowMapper + ) -> Result, Box<(dyn Error + Sync + Send)>> + where + R: RowMapper, { - db_conn_query_one_impl(self, stmt, params).await + db_conn_query_one_impl::(self, stmt, params).await } async fn query_one_for<'a, T: FromSqlOwnedValue>( @@ -237,7 +241,7 @@ impl DbConnection for &mut DatabaseConnection { where S: AsRef + Display + Send, R: RowMapper, - Vec: FromIterator<::Output> + Vec: FromIterator<::Output>, { match self { #[cfg(feature = "postgres")] @@ -255,10 +259,11 @@ impl DbConnection for &mut DatabaseConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result, Box<(dyn Error + Sync + Send)>> - where R: RowMapper + ) -> Result, Box<(dyn Error + Sync + Send)>> + where + R: RowMapper, { - db_conn_query_one_impl(self, stmt, params).await + db_conn_query_one_impl::(self, stmt, params).await } async fn query_one_for<'a, T: FromSqlOwnedValue>( @@ -468,18 +473,19 @@ mod connection_helpers { c: &DatabaseConnection, stmt: &str, params: &[&'a (dyn QueryParameter<'a> + 'a)], - ) -> Result, Box> - where R: RowMapper + ) -> Result, Box> + where + R: RowMapper, { match c { #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(client) => client.query_one(stmt, params).await, + DatabaseConnection::Postgres(client) => client.query_one::(stmt, params).await, #[cfg(feature = "mssql")] - DatabaseConnection::SqlServer(client) => client.query_one(stmt, params).await, + DatabaseConnection::SqlServer(client) => client.query_one::(stmt, params).await, #[cfg(feature = "mysql")] - DatabaseConnection::MySQL(client) => client.query_one(stmt, params).await, + DatabaseConnection::MySQL(client) => client.query_one::(stmt, params).await, } } } diff --git a/canyon_core/src/mapper.rs b/canyon_core/src/mapper.rs index c3c0ce55..1261a197 100644 --- a/canyon_core/src/mapper.rs +++ b/canyon_core/src/mapper.rs @@ -5,7 +5,7 @@ pub trait RowMapper: Sized { type Output; #[cfg(feature = "postgres")] - fn deserialize_postgresql(row: &tokio_postgres::Row) -> Self::Output; + fn deserialize_postgresql(row: &tokio_postgres::Row) -> ::Output; #[cfg(feature = "mssql")] fn deserialize_sqlserver(row: &tiberius::Row) -> Self::Output; #[cfg(feature = "mysql")] @@ -16,5 +16,7 @@ pub type CanyonError = Box<(dyn std::error::Error + Send + Sync)>; // TODO: conv // real error pub trait IntoResults { fn into_results(self) -> Result, CanyonError> - where R: RowMapper; + where + R: RowMapper, + Vec: FromIterator<::Output>; } diff --git a/canyon_core/src/rows.rs b/canyon_core/src/rows.rs index 6e32d253..c449b86e 100644 --- a/canyon_core/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -62,7 +62,9 @@ pub enum CanyonRows { impl IntoResults for Result { fn into_results(self) -> Result, CanyonError> - where R: RowMapper, + where + R: RowMapper, + Vec: FromIterator<::Output>, { self.map(move |rows| rows.into_results::()) } @@ -93,8 +95,12 @@ impl CanyonRows { } } - /// Consumes `self` and returns the wrapped [`std::vec::Vec`] with the instances of T - pub fn into_results>(self) -> Vec { + /// Consumes `self` and returns the wrapped [`std::vec::Vec`] with the instances of R + pub fn into_results(self) -> Vec + where + R: RowMapper, + Vec: FromIterator<::Output>, + { match self { #[cfg(feature = "postgres")] Self::Postgres(v) => v.iter().map(|row| R::deserialize_postgresql(row)).collect(), diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index c7af5a5d..dad7d556 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -14,7 +14,7 @@ pub trait Transaction { where S: AsRef + Display + Send, R: RowMapper, - Vec: FromIterator<::Output> + Vec: FromIterator<::Output>, { async move { input.query(stmt, params).await } } @@ -23,13 +23,13 @@ pub trait Transaction { stmt: S, params: Z, input: impl DbConnection + Send + 'a, - ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send + ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where S: AsRef + Display + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send + 'a, - R: RowMapper + R: RowMapper, { - async move { input.query_one(stmt.as_ref(), params.as_ref()).await } + async move { input.query_one::(stmt.as_ref(), params.as_ref()).await } } fn query_one_for<'a, S, Z, F: FromSqlOwnedValue>( diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 7f69e153..044266d8 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -1,12 +1,12 @@ use crate::query_elements::query_builder::{ DeleteQueryBuilder, SelectQueryBuilder, UpdateQueryBuilder, }; +use canyon_core::connection::database_type::DatabaseType; use canyon_core::connection::db_connector::DbConnection; +use canyon_core::mapper::RowMapper; use canyon_core::query_parameters::QueryParameter; use std::error::Error; use std::future::Future; -use canyon_core::connection::database_type::DatabaseType; -use canyon_core::mapper::RowMapper; /// *CrudOperations* it's the core part of Canyon-SQL. /// @@ -24,27 +24,33 @@ use canyon_core::mapper::RowMapper; /// Also, you can find the written macro-code that performs the auto-mapping /// in the *canyon_sql_root::canyon_macros* crates, on the root of this project. pub trait CrudOperations: Send + Sync { - fn find_all() - -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send - where R: RowMapper; + fn find_all() -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send + where + R: RowMapper, + Vec: FromIterator<::Output>; fn find_all_with<'a, R, I>( input: I, - ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send + ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where + R: RowMapper, I: DbConnection + Send + 'a, - R: RowMapper; + Vec: FromIterator<::Output>; - fn find_all_unchecked() -> impl Future> + Send; + fn find_all_unchecked() -> impl Future> + Send + where + R: RowMapper, + Vec: FromIterator<::Output>; fn find_all_unchecked_with<'a, R, I>(input: I) -> impl Future> + Send where I: DbConnection + Send + 'a, - R: RowMapper; + R: RowMapper, + Vec: FromIterator<::Output>; - fn select_query<'a, R: RowMapper>() -> SelectQueryBuilder<'a, R>; - - fn select_query_with<'a, R: RowMapper>(database_type: DatabaseType) -> SelectQueryBuilder<'a, R>; + // fn select_query<'a, R: RowMapper>() -> SelectQueryBuilder<'a, R>; + // + // fn select_query_with<'a, R: RowMapper>(database_type: DatabaseType) -> SelectQueryBuilder<'a, R>; fn count() -> impl Future>> + Send; @@ -54,17 +60,17 @@ pub trait CrudOperations: Send + Sync { where I: DbConnection + Send + 'a; - fn find_by_pk<'a, R: RowMapper>( - value: &'a dyn QueryParameter<'a>, - ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send; - - fn find_by_pk_with<'a, R, I>( - value: &'a dyn QueryParameter<'a>, - input: I, - ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send - where - I: DbConnection + Send + 'a, - R: RowMapper; + // fn find_by_pk<'a, R: RowMapper>( + // value: &'a dyn QueryParameter<'a>, + // ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send; + // + // fn find_by_pk_with<'a, R, I>( + // value: &'a dyn QueryParameter<'a>, + // input: I, + // ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send + // where + // I: DbConnection + Send + 'a, + // R: RowMapper; fn insert<'a>( &'a mut self, @@ -77,16 +83,16 @@ pub trait CrudOperations: Send + Sync { where I: DbConnection + Send + 'a; - fn multi_insert<'a, T>( - instances: &'a mut [&'a mut T], - ) -> impl Future>> + Send; - - fn multi_insert_with<'a, T, I>( - instances: &'a mut [&'a mut T], - input: I, - ) -> impl Future>> + Send - where - I: DbConnection + Send + 'a; + // fn multi_insert<'a, T>( + // instances: &'a mut [&'a mut T], + // ) -> impl Future>> + Send; + // + // fn multi_insert_with<'a, T, I>( + // instances: &'a mut [&'a mut T], + // input: I, + // ) -> impl Future>> + Send + // where + // I: DbConnection + Send + 'a; fn update(&self) -> impl Future>> + Send; @@ -98,7 +104,7 @@ pub trait CrudOperations: Send + Sync { I: DbConnection + Send + 'a; // fn update_query<'a>() -> UpdateQueryBuilder<'a>; - // + // // fn update_query_with<'a, I>(input: I) -> UpdateQueryBuilder<'a> // where // I: DbConnection + Send + 'a; @@ -111,9 +117,9 @@ pub trait CrudOperations: Send + Sync { ) -> impl Future>> + Send where I: DbConnection + Send + 'a; - + // fn delete_query<'a>() -> DeleteQueryBuilder<'a>; - // + // // fn delete_query_with<'a, I>(input: I) -> DeleteQueryBuilder<'a> // where // I: DbConnection + Send + 'a; diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index 8c47142a..764eb563 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -123,7 +123,7 @@ pub mod ops { } /// Type for construct more complex queries than the classical CRUD ones. -pub struct QueryBuilder<'a, R: RowMapper>{ +pub struct QueryBuilder<'a, R: RowMapper> { // query: Query<'a>, sql: String, params: Vec<&'a dyn QueryParameter<'a>>, @@ -148,8 +148,11 @@ impl<'a, R: RowMapper> QueryBuilder<'a, R> { /// /// TODO: this is not definitive => QueryBuilder -> Query -> Transaction -> RowMapper pub async fn query( mut self, - input: I - ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> { + input: I, + ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + where + Vec: FromIterator<::Output>, + { self.sql.push(';'); T::query(&self.sql, &self.params, input).await @@ -197,21 +200,15 @@ impl<'a, R: RowMapper> QueryBuilder<'a, R> { return; } - self - .sql - .push_str(&format!(" AND {} IN (", r#and.as_str())); + self.sql.push_str(&format!(" AND {} IN (", r#and.as_str())); let mut counter = 1; values.iter().for_each(|qp| { if values.len() != counter { - self - .sql - .push_str(&format!("${}, ", self.params.len())); + self.sql.push_str(&format!("${}, ", self.params.len())); counter += 1; } else { - self - .sql - .push_str(&format!("${}", self.params.len())); + self.sql.push_str(&format!("${}", self.params.len())); } self.params.push(qp) }); @@ -228,21 +225,15 @@ impl<'a, R: RowMapper> QueryBuilder<'a, R> { return; } - self - .sql - .push_str(&format!(" OR {} IN (", r#or.as_str())); + self.sql.push_str(&format!(" OR {} IN (", r#or.as_str())); let mut counter = 1; values.iter().for_each(|qp| { if values.len() != counter { - self - .sql - .push_str(&format!("${}, ", self.params.len())); + self.sql.push_str(&format!("${}, ", self.params.len())); counter += 1; } else { - self - .sql - .push_str(&format!("${}", self.params.len())); + self.sql.push_str(&format!("${}", self.params.len())); } self.params.push(qp) }); @@ -262,8 +253,7 @@ impl<'a, R: RowMapper> QueryBuilder<'a, R> { } } -pub struct SelectQueryBuilder<'a, R: RowMapper> -{ +pub struct SelectQueryBuilder<'a, R: RowMapper> { _inner: QueryBuilder<'a, R>, } @@ -271,18 +261,19 @@ impl<'a, R: RowMapper> SelectQueryBuilder<'a, R> { /// Generates a new public instance of the [`SelectQueryBuilder`] pub fn new(table_schema_data: &str, database_type: DatabaseType) -> Self { Self { - _inner: QueryBuilder::new( - format!("SELECT * FROM {table_schema_data}"), - database_type, - ), + _inner: QueryBuilder::new(format!("SELECT * FROM {table_schema_data}"), database_type), } } /// Launches the generated query to the database pointed by the /// selected datasource #[inline] - pub async fn query(self, input: I) - -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + pub async fn query( + self, + input: I, + ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + where + Vec: FromIterator<::Output>, { self._inner.query::(input).await } @@ -348,7 +339,7 @@ impl<'a, R: RowMapper> SelectQueryBuilder<'a, R> { } } -impl<'a, R: RowMapper> ops::QueryBuilder<'a> for SelectQueryBuilder<'a, R> { +impl<'a, R: RowMapper> ops::QueryBuilder<'a> for SelectQueryBuilder<'a, R> { #[inline] fn read_sql(&'a self) -> &'a str { self._inner.sql.as_str() @@ -408,26 +399,27 @@ impl<'a, R: RowMapper> ops::QueryBuilder<'a> for SelectQueryBuilder< /// /// * `set` - To construct a new `SET` clause to determine the columns to /// update with the provided values -pub struct UpdateQueryBuilder<'a, R: RowMapper> { +pub struct UpdateQueryBuilder<'a, R: RowMapper> { _inner: QueryBuilder<'a, R>, } -impl<'a, R: RowMapper> UpdateQueryBuilder<'a, R> { +impl<'a, R: RowMapper> UpdateQueryBuilder<'a, R> { /// Generates a new public instance of the [`UpdateQueryBuilder`] pub fn new(table_schema_data: &str, database_type: DatabaseType) -> Self { Self { - _inner: QueryBuilder::new( - format!("UPDATE {table_schema_data}"), - database_type - ), + _inner: QueryBuilder::new(format!("UPDATE {table_schema_data}"), database_type), } } /// Launches the generated query to the database pointed by the /// selected datasource #[inline] - pub async fn query(self, input: I) - -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + pub async fn query( + self, + input: I, + ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + where + Vec: FromIterator<::Output>, { self._inner.query::(input).await } @@ -472,7 +464,7 @@ impl<'a, R: RowMapper> UpdateQueryBuilder<'a, R> { } } -impl<'a, R: RowMapper> ops::QueryBuilder<'a> for UpdateQueryBuilder<'a, R> { +impl<'a, R: RowMapper> ops::QueryBuilder<'a> for UpdateQueryBuilder<'a, R> { #[inline] fn read_sql(&'a self) -> &'a str { self._inner.sql.as_str() @@ -533,32 +525,33 @@ impl<'a, R: RowMapper> ops::QueryBuilder<'a> for UpdateQueryBuilder< /// /// * `set` - To construct a new `SET` clause to determine the columns to /// update with the provided values -pub struct DeleteQueryBuilder<'a, R: RowMapper> { +pub struct DeleteQueryBuilder<'a, R: RowMapper> { _inner: QueryBuilder<'a, R>, } -impl<'a, R: RowMapper> DeleteQueryBuilder<'a, R> { +impl<'a, R: RowMapper> DeleteQueryBuilder<'a, R> { /// Generates a new public instance of the [`DeleteQueryBuilder`] pub fn new(table_schema_data: &str, database_type: DatabaseType) -> Self { Self { - _inner: QueryBuilder::new( - format!("DELETE FROM {table_schema_data}"), - database_type - ), + _inner: QueryBuilder::new(format!("DELETE FROM {table_schema_data}"), database_type), } } /// Launches the generated query to the database pointed by the /// selected datasource #[inline] - pub async fn query(self, input: I) - -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + pub async fn query( + self, + input: I, + ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + where + Vec: FromIterator<::Output>, { self._inner.query::(input).await } } -impl<'a, R: RowMapper> ops::QueryBuilder<'a> for DeleteQueryBuilder<'a, R> { +impl<'a, R: RowMapper> ops::QueryBuilder<'a> for DeleteQueryBuilder<'a, R> { #[inline] fn read_sql(&'a self) -> &'a str { self._inner.sql.as_str() diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index a1486e82..2c5af716 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -99,7 +99,11 @@ fn generate_find_by_foreign_key_tokens( quote! { /// Searches the parent entity (if exists) for this type #quoted_method_signature { - <#fk_ty as canyon_sql::core::Transaction>::query_one( + <#fk_ty as canyon_sql::core::Transaction>::query_one::< + &str, + &[&dyn canyon_sql::core::QueryParameter<'_>], + #fk_ty + >( #stmt, &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], "" @@ -113,10 +117,9 @@ fn generate_find_by_foreign_key_tokens( quote! { /// Searches the parent entity (if exists) for this type with the specified datasource #quoted_with_method_signature { - <#fk_ty as canyon_sql::core::Transaction>::query_one( + input.query_one::<#fk_ty>( #stmt, - &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], - input + &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>] ).await } }, @@ -152,13 +155,13 @@ fn generate_find_by_reverse_foreign_key_tokens( let quoted_method_signature: TokenStream = quote! { async fn #method_name_ident<'a, R, F>(value: &F) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> - where R: RowMapper, + where R: RowMapper, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send }; let quoted_with_method_signature: TokenStream = quote! { async fn #method_name_ident_with<'a, R, F, I> (value: &F, input: I) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> - where R: RowMapper, + where R: RowMapper, F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send, I: canyon_sql::core::DbConnection + Send + 'a }; diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index daeaca92..9821a55c 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -170,8 +170,8 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri }); - let multi_insert_tokens = generate_multiple_insert_tokens(macro_data, table_schema_data); - insert_ops_tokens.extend(multi_insert_tokens); + // let multi_insert_tokens = generate_multiple_insert_tokens(macro_data, table_schema_data); + // insert_ops_tokens.extend(multi_insert_tokens); insert_ops_tokens } diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index 9a19d57c..1906c4e3 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -37,7 +37,7 @@ pub fn impl_crud_operations_trait_for_struct( }; crud_ops_tokens.extend(quote! { - // use canyon_sql::core::IntoResults; // TODO: isn't being used anymore + use canyon_sql::core::IntoResults; use canyon_sql::core::RowMapper; impl canyon_sql::crud::CrudOperations for #ty { diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 8fe97945..b0626f64 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -17,7 +17,7 @@ pub fn generate_read_operations_tokens( // TODO: remember that this queries statements must be autogenerated by some automatic procedure let find_all = create_find_all_macro(ty, &fa_stmt); - let find_all_with = create_find_all_with_macro(ty, &fa_stmt); + let find_all_with = create_find_all_with_macro(&fa_stmt); let find_all_unchecked = create_find_all_unchecked_macro(ty, &fa_stmt); let find_all_unchecked_with = create_find_all_unchecked_with_macro(ty, &fa_stmt); @@ -27,7 +27,7 @@ pub fn generate_read_operations_tokens( let find_by_pk_complex_tokens = generate_find_by_pk_tokens(macro_data, table_schema_data); - let read_querybuilder_ops = generate_find_all_query_tokens(macro_data, table_schema_data); + // let read_querybuilder_ops = generate_find_all_query_tokens(macro_data, table_schema_data); quote! { #find_all @@ -38,9 +38,9 @@ pub fn generate_read_operations_tokens( #count #count_with - #find_by_pk_complex_tokens + // #find_by_pk_complex_tokens - #read_querybuilder_ops + // #read_querybuilder_ops } } @@ -74,7 +74,7 @@ fn generate_find_all_query_tokens( /// described in the configuration file, and selected with the [`&str`] /// passed as parameter. fn select_query_with<'a, R: RowMapper>(database_type: canyon_sql::connection::DatabaseType) - -> canyon_sql::query::SelectQueryBuilder<'a, #ty> + -> canyon_sql::query::SelectQueryBuilder<'a, #ty> { canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, database_type) } @@ -95,7 +95,7 @@ fn generate_find_by_pk_tokens( // Disabled if there's no `primary_key` annotation if pk.is_empty() { return quote! { - async fn find_by_pk<'a, R: RowMapper>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) + async fn find_by_pk<'a, R: RowMapper>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> { Err( @@ -112,9 +112,9 @@ fn generate_find_by_pk_tokens( value: &'a dyn canyon_sql::core::QueryParameter<'a>, input: I ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> - where + where I: canyon_sql::core::DbConnection + Send + 'a, - R: RowMapper + R: RowMapper { Err( std::io::Error::new( @@ -145,65 +145,68 @@ mod __details { pub mod find_all_generators { use super::*; + use proc_macro2::TokenStream; - pub fn create_find_all_macro(ty: &syn::Ident, stmt: &str) -> MacroOperationBuilder { - MacroOperationBuilder::new() - .fn_name("find_all") - .type_is_row_mapper() - .user_type(ty) - .return_type(ty) - .add_doc_comment("Executes a 'SELECT * FROM '") - .add_doc_comment("This operation retrieves all the users records stored with the default datasource") - .query_string(stmt) + pub fn create_find_all_macro(ty: &syn::Ident, stmt: &str) -> TokenStream { + quote! { + async fn find_all() + -> Result, Box<(dyn std::error::Error + Sync + Send)>> + where R: RowMapper, Vec: FromIterator<::Output> { + <#ty as canyon_sql::core::Transaction>::query::<&str, R>( + #stmt, + &[], + "" + ).await + } + } } - pub fn create_find_all_with_macro(ty: &syn::Ident, stmt: &str) -> MacroOperationBuilder { - MacroOperationBuilder::new() - .fn_name("find_all_with") - .type_is_row_mapper() - .with_input_param() - .user_type(ty) - .return_type(ty) - .add_doc_comment("Executes a 'SELECT * FROM '") - .add_doc_comment("This operation retrieves all the users records stored with the provided datasource") - .query_string(stmt) + pub fn create_find_all_with_macro(stmt: &str) -> TokenStream { + quote! { + async fn find_all_with<'a, R, I>(input: I) + -> Result, Box<(dyn std::error::Error + Sync + Send)>> + where + R: RowMapper, + I: canyon_sql::core::DbConnection + Send + 'a, + Vec: FromIterator<::Output> + { + input.query::<&str, R>(#stmt, &[]).await + } + } } - pub fn create_find_all_unchecked_macro( - ty: &syn::Ident, - stmt: &str, - ) -> MacroOperationBuilder { - MacroOperationBuilder::new() - .fn_name("find_all_unchecked") - .type_is_row_mapper() - .user_type(ty) - .return_type(ty) - .add_doc_comment("Executes a 'SELECT * FROM '") - .add_doc_comment("This operation retrieves all the users records stored with the provided datasource") - .query_string(stmt) - .with_unwrap() + pub fn create_find_all_unchecked_macro(ty: &syn::Ident, stmt: &str) -> TokenStream { + let expect_msg = format!("Failed to execute find all query for: {:?}", ty.to_string()); + quote! { + async fn find_all_unchecked() -> Vec + where R: RowMapper, + Vec: FromIterator<::Output> + { + <#ty as canyon_sql::core::Transaction>::query(#stmt, &[], "") + .await + .expect(#expect_msg) + } + } } - pub fn create_find_all_unchecked_with_macro( - ty: &syn::Ident, - stmt: &str, - ) -> MacroOperationBuilder { - MacroOperationBuilder::new() - .fn_name("find_all_unchecked_with") - .type_is_row_mapper() - .user_type(ty) - .return_type(ty) - .with_input_param() - .add_doc_comment("Executes a 'SELECT * FROM '") - .add_doc_comment("This operation retrieves all the users records stored with the provided datasource") - .query_string(stmt) - .with_unwrap() + pub fn create_find_all_unchecked_with_macro(ty: &syn::Ident, stmt: &str) -> TokenStream { + let expect_msg = format!("Failed to execute find all query for: {:?}", ty.to_string()); + quote! { + async fn find_all_unchecked_with<'a, R, I>(input: I) -> Vec + where + R: RowMapper, + I: canyon_sql::core::DbConnection + Send + 'a, + Vec: FromIterator<::Output> + { + input.query(#stmt, &[]).await + .expect(#expect_msg) + } + } } } pub mod count_generators { use super::*; - use crate::query_operations::macro_template::TransactionMethod; use proc_macro2::TokenStream; // NOTE: We can't use the QueryOneFor here due that the Tiberius `.get::(0)` for @@ -233,56 +236,40 @@ mod __details { } } - pub fn create_count_macro(ty: &syn::Ident, stmt: &str) -> MacroOperationBuilder { + pub fn create_count_macro(ty: &syn::Ident, stmt: &str) -> TokenStream { let result_handling = generate_count_manual_result_handling(ty); - MacroOperationBuilder::new() - .fn_name("count") - .user_type(ty) - .return_type(&Ident::new("i64", Span::call_site())) // TODO: into ident or take by value - .add_doc_comment( - "Performs a COUNT(*) query over the table related to the entity T'", - ) - .add_doc_comment("Executed with the default datasource") - .query_string(stmt) - .with_transaction_method(TransactionMethod::QueryRows) - .transaction_as_variable(quote! { - match transaction_result { // NOTE: dark magic. Should be refactored + quote! { + async fn count() -> Result> { + let res = <#ty as canyon_sql::core::Transaction>::query_rows(#stmt, &[], "") + .await?; + match res { #result_handling } - }) - .propagate_transaction_result() - .raw_return() + } + } } - pub fn create_count_with_macro(ty: &syn::Ident, stmt: &str) -> MacroOperationBuilder { + pub fn create_count_with_macro(ty: &syn::Ident, stmt: &str) -> TokenStream { let result_handling = generate_count_manual_result_handling(ty); - MacroOperationBuilder::new() - .fn_name("count_with") - .user_type(ty) - .with_input_param() - .return_type(&Ident::new("i64", Span::call_site())) // TODO: into ident or take by value - .add_doc_comment( - "Performs a COUNT(*) query over the table related to the entity T'", - ) - .add_doc_comment("It will be executed with the specified datasource") - .query_string(stmt) - .with_transaction_method(TransactionMethod::QueryRows) - .transaction_as_variable(quote! { - match transaction_result { // NOTE: dark magic. Should be refactored + quote! { + async fn count_with<'a, I>(input: I) -> Result> + where I: canyon_sql::core::DbConnection + Send + 'a + { + let res = input.query_rows(#stmt, &[]).await?; + + match res { #result_handling } - }) - .propagate_transaction_result() - .raw_return() + } + } } } pub mod pk_generators { use super::*; use crate::query_operations::macro_template::TransactionMethod; - pub fn create_find_by_pk_macro(ty: &Ident, stmt: &str) -> MacroOperationBuilder { MacroOperationBuilder::new() @@ -320,7 +307,6 @@ mod __details { mod macro_builder_read_ops_tests { use super::__details::{count_generators::*, find_all_generators::*, pk_generators::*}; use crate::query_operations::consts::*; - const SELECT_ALL_STMT: &str = "SELECT * FROM public.user"; // TODO: introduce the const_format crate const COUNT_STMT: &str = "SELECT COUNT(*) FROM public.user"; diff --git a/canyon_macros/src/utils/helpers.rs b/canyon_macros/src/utils/helpers.rs index aa673823..42323f9f 100644 --- a/canyon_macros/src/utils/helpers.rs +++ b/canyon_macros/src/utils/helpers.rs @@ -121,33 +121,6 @@ pub fn _database_table_name_from_struct(ty: &Ident) -> String { table_name } -/// Parses a syn::Identifier to create a defaulted snake case database table name -#[test] -#[cfg(not(target_env = "msvc"))] -fn test_entity_database_name_defaulter() { - assert_eq!( - default_database_table_name_from_entity_name("League"), - "league".to_owned() - ); - assert_eq!( - default_database_table_name_from_entity_name("MajorLeague"), - "major_league".to_owned() - ); - assert_eq!( - default_database_table_name_from_entity_name("MajorLeagueTournament"), - "major_league_tournament".to_owned() - ); - - assert_ne!( - default_database_table_name_from_entity_name("MajorLeague"), - "majorleague".to_owned() - ); - assert_ne!( - default_database_table_name_from_entity_name("MajorLeague"), - "MajorLeague".to_owned() - ); -} - /// Autogenerates a default table name for an entity given their struct name pub fn default_database_table_name_from_entity_name(ty: &str) -> String { let struct_name: String = ty.to_string(); @@ -202,3 +175,30 @@ pub fn database_table_name_to_struct_ident(name: &str) -> Ident { Ident::new(&struct_name, proc_macro2::Span::call_site()) } + +/// Parses a syn::Identifier to create a defaulted snake case database table name +#[test] +#[cfg(not(target_env = "msvc"))] +fn test_entity_database_name_defaulter() { + assert_eq!( + default_database_table_name_from_entity_name("League"), + "league".to_owned() + ); + assert_eq!( + default_database_table_name_from_entity_name("MajorLeague"), + "major_league".to_owned() + ); + assert_eq!( + default_database_table_name_from_entity_name("MajorLeagueTournament"), + "major_league_tournament".to_owned() + ); + + assert_ne!( + default_database_table_name_from_entity_name("MajorLeague"), + "majorleague".to_owned() + ); + assert_ne!( + default_database_table_name_from_entity_name("MajorLeague"), + "MajorLeague".to_owned() + ); +} diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index 7eec7875..2473b7dc 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -2,7 +2,7 @@ use std::convert::TryFrom; use canyon_entities::field_annotation::EntityFieldAnnotation; use proc_macro2::Ident; -use syn::{Attribute, DeriveInput, Fields, Generics, Type, Visibility}; +use syn::{Attribute, DeriveInput, Fields, GenericParam, Generics, Type, TypeParam, Visibility}; /// Provides a convenient way of store the data for the TokenStream /// received on a macro @@ -29,6 +29,12 @@ impl<'a> MacroTokens<'a> { } } + // pub fn retrieve_row_mapper_implementor(&self) -> Ident { + // let caller = self.ty; + // let row_mapper_type_parameter = self.generics.type_params() + // caller.clone() + // } + /// Gives a Vec of tuples that contains the visibility, the name and /// the type of every field on a Struct pub fn _fields_with_visibility_and_types(&self) -> Vec<(Visibility, Ident, Type)> { diff --git a/tests/crud/delete_operations.rs b/tests/crud/delete_operations.rs index e9bb61ef..22ffacca 100644 --- a/tests/crud/delete_operations.rs +++ b/tests/crud/delete_operations.rs @@ -1,159 +1,159 @@ -//! Integration tests for the CRUD operations available in `Canyon` that -//! generates and executes *INSERT* statements -use canyon_sql::crud::CrudOperations; - -#[cfg(feature = "mysql")] -use crate::constants::MYSQL_DS; -#[cfg(feature = "postgres")] -use crate::constants::PSQL_DS; -#[cfg(feature = "mssql")] -use crate::constants::SQL_SERVER_DS; - -use crate::tests_models::league::*; - -/// Deletes a row from the database that is mapped into some instance of a `T` entity. -/// -/// The `t.delete(&self)` operation is only enabled for types that -/// has, at least, one of it's fields annotated with a `#[primary_key]` -/// operation, because we use that concrete field to construct the clause that targets -/// that entity. -/// -/// Attempt of usage the `t.delete(&self)` method on an entity without `#[primary_key]` -/// will raise a runtime error. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_delete_method_operation() { - // For test the delete operation, we will insert a new instance of the database, and then, - // after inspect it, we will proceed to delete it - let mut new_league: League = League { - id: Default::default(), - ext_id: 7892635306594_i64, - slug: "some-new-league".to_string(), - name: "Some New League".to_string(), - region: "Bahía de cochinos".to_string(), - image_url: "https://nobodyspectsandimage.io".to_string(), - }; - - // We insert the instance on the database, on the `League` entity - new_league.insert().await.expect("Failed insert operation"); - - assert_eq!( - new_league.id, - League::find_by_pk_with(&new_league.id, PSQL_DS) - .await - .expect("Request error") - .expect("None value") - .id - ); - - // Now that we have an instance mapped to some entity by a primary key, we can now - // remove that entry from the database with the delete operation - new_league - .delete() - .await - .expect("Failed to delete the operation"); - - // To check the success, we can query by the primary key value and check if, after unwrap() - // the result of the operation, the find by primary key contains Some(v) or None - // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> - assert_eq!( - League::find_by_pk(&new_league.id) - .await - .expect("Unwrapping the result, letting the Option"), - None - ); -} - -/// Same as the delete test, but performing the operations with the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_delete_with_mssql_method_operation() { - // For test the delete, we will insert a new instance of the database, and then, - // after inspect it, we will proceed to delete it - let mut new_league: League = League { - id: Default::default(), - ext_id: 7892635306594_i64, - slug: "some-new-league".to_string(), - name: "Some New League".to_string(), - region: "Bahía de cochinos".to_string(), - image_url: "https://nobodyspectsandimage.io".to_string(), - }; - - // We insert the instance on the database, on the `League` entity - new_league - .insert_with(SQL_SERVER_DS) - .await - .expect("Failed insert operation"); - assert_eq!( - new_league.id, - League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) - .await - .expect("Request error") - .expect("None value") - .id - ); - - // Now that we have an instance mapped to some entity by a primary key, we can now - // remove that entry from the database with the delete operation - new_league - .delete_with(SQL_SERVER_DS) - .await - .expect("Failed to delete the operation"); - - // To check the success, we can query by the primary key value and check if, after unwrap() - // the result of the operation, the find by primary key contains Some(v) or None - // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> - assert_eq!( - League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) - .await - .expect("Unwrapping the result, letting the Option"), - None - ); -} - -/// Same as the delete test, but performing the operations with the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_delete_with_mysql_method_operation() { - // For test the delete, we will insert a new instance of the database, and then, - // after inspect it, we will proceed to delete it - let mut new_league: League = League { - id: Default::default(), - ext_id: 7892635306594_i64, - slug: "some-new-league".to_string(), - name: "Some New League".to_string(), - region: "Bahía de cochinos".to_string(), - image_url: "https://nobodyspectsandimage.io".to_string(), - }; - - // We insert the instance on the database, on the `League` entity - new_league - .insert_with(MYSQL_DS) - .await - .expect("Failed insert operation"); - assert_eq!( - new_league.id, - League::find_by_pk_with(&new_league.id, MYSQL_DS) - .await - .expect("Request error") - .expect("None value") - .id - ); - - // Now that we have an instance mapped to some entity by a primary key, we can now - // remove that entry from the database with the delete operation - new_league - .delete_with(MYSQL_DS) - .await - .expect("Failed to delete the operation"); - - // To check the success, we can query by the primary key value and check if, after unwrap() - // the result of the operation, the find by primary key contains Some(v) or None - // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> - assert_eq!( - League::find_by_pk_with(&new_league.id, MYSQL_DS) - .await - .expect("Unwrapping the result, letting the Option"), - None - ); -} +// //! Integration tests for the CRUD operations available in `Canyon` that +// //! generates and executes *INSERT* statements +// use canyon_sql::crud::CrudOperations; +// +// #[cfg(feature = "mysql")] +// use crate::constants::MYSQL_DS; +// #[cfg(feature = "postgres")] +// use crate::constants::PSQL_DS; +// #[cfg(feature = "mssql")] +// use crate::constants::SQL_SERVER_DS; +// +// use crate::tests_models::league::*; +// +// /// Deletes a row from the database that is mapped into some instance of a `T` entity. +// /// +// /// The `t.delete(&self)` operation is only enabled for types that +// /// has, at least, one of it's fields annotated with a `#[primary_key]` +// /// operation, because we use that concrete field to construct the clause that targets +// /// that entity. +// /// +// /// Attempt of usage the `t.delete(&self)` method on an entity without `#[primary_key]` +// /// will raise a runtime error. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_delete_method_operation() { +// // For test the delete operation, we will insert a new instance of the database, and then, +// // after inspect it, we will proceed to delete it +// let mut new_league: League = League { +// id: Default::default(), +// ext_id: 7892635306594_i64, +// slug: "some-new-league".to_string(), +// name: "Some New League".to_string(), +// region: "Bahía de cochinos".to_string(), +// image_url: "https://nobodyspectsandimage.io".to_string(), +// }; +// +// // We insert the instance on the database, on the `League` entity +// new_league.insert().await.expect("Failed insert operation"); +// +// assert_eq!( +// new_league.id, +// League::find_by_pk_with(&new_league.id, PSQL_DS) +// .await +// .expect("Request error") +// .expect("None value") +// .id +// ); +// +// // Now that we have an instance mapped to some entity by a primary key, we can now +// // remove that entry from the database with the delete operation +// new_league +// .delete() +// .await +// .expect("Failed to delete the operation"); +// +// // To check the success, we can query by the primary key value and check if, after unwrap() +// // the result of the operation, the find by primary key contains Some(v) or None +// // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> +// assert_eq!( +// League::find_by_pk(&new_league.id) +// .await +// .expect("Unwrapping the result, letting the Option"), +// None +// ); +// } +// +// /// Same as the delete test, but performing the operations with the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_delete_with_mssql_method_operation() { +// // For test the delete, we will insert a new instance of the database, and then, +// // after inspect it, we will proceed to delete it +// let mut new_league: League = League { +// id: Default::default(), +// ext_id: 7892635306594_i64, +// slug: "some-new-league".to_string(), +// name: "Some New League".to_string(), +// region: "Bahía de cochinos".to_string(), +// image_url: "https://nobodyspectsandimage.io".to_string(), +// }; +// +// // We insert the instance on the database, on the `League` entity +// new_league +// .insert_with(SQL_SERVER_DS) +// .await +// .expect("Failed insert operation"); +// assert_eq!( +// new_league.id, +// League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) +// .await +// .expect("Request error") +// .expect("None value") +// .id +// ); +// +// // Now that we have an instance mapped to some entity by a primary key, we can now +// // remove that entry from the database with the delete operation +// new_league +// .delete_with(SQL_SERVER_DS) +// .await +// .expect("Failed to delete the operation"); +// +// // To check the success, we can query by the primary key value and check if, after unwrap() +// // the result of the operation, the find by primary key contains Some(v) or None +// // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> +// assert_eq!( +// League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) +// .await +// .expect("Unwrapping the result, letting the Option"), +// None +// ); +// } +// +// /// Same as the delete test, but performing the operations with the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_delete_with_mysql_method_operation() { +// // For test the delete, we will insert a new instance of the database, and then, +// // after inspect it, we will proceed to delete it +// let mut new_league: League = League { +// id: Default::default(), +// ext_id: 7892635306594_i64, +// slug: "some-new-league".to_string(), +// name: "Some New League".to_string(), +// region: "Bahía de cochinos".to_string(), +// image_url: "https://nobodyspectsandimage.io".to_string(), +// }; +// +// // We insert the instance on the database, on the `League` entity +// new_league +// .insert_with(MYSQL_DS) +// .await +// .expect("Failed insert operation"); +// assert_eq!( +// new_league.id, +// League::find_by_pk_with(&new_league.id, MYSQL_DS) +// .await +// .expect("Request error") +// .expect("None value") +// .id +// ); +// +// // Now that we have an instance mapped to some entity by a primary key, we can now +// // remove that entry from the database with the delete operation +// new_league +// .delete_with(MYSQL_DS) +// .await +// .expect("Failed to delete the operation"); +// +// // To check the success, we can query by the primary key value and check if, after unwrap() +// // the result of the operation, the find by primary key contains Some(v) or None +// // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> +// assert_eq!( +// League::find_by_pk_with(&new_league.id, MYSQL_DS) +// .await +// .expect("Unwrapping the result, letting the Option"), +// None +// ); +// } diff --git a/tests/crud/foreign_key_operations.rs b/tests/crud/foreign_key_operations.rs index 00f153e3..e8544df2 100644 --- a/tests/crud/foreign_key_operations.rs +++ b/tests/crud/foreign_key_operations.rs @@ -1,163 +1,163 @@ -/// Integration tests for the CRUD operations available in `Canyon` that -/// generates and executes *SELECT* statements based on a entity -/// annotated with the `#[foreign_key(... args)]` annotation looking -/// for the related data with some entity `U` that acts as is parent, where `U` -/// impls `ForeignKeyable` (isn't required, but it won't unlock the -/// reverse search features parent -> child, only the child -> parent ones). -/// -/// Names of the foreign key methods are autogenerated for the direct and -/// reverse side of the implementations. -/// For more info: TODO -> Link to the docs of the foreign key chapter -use canyon_sql::crud::CrudOperations; - -#[cfg(feature = "mssql")] -use crate::constants::MYSQL_DS; -#[cfg(feature = "mssql")] -use crate::constants::SQL_SERVER_DS; - -use crate::tests_models::league::*; -use crate::tests_models::tournament::*; - -/// Given an entity `T` which has some field declaring a foreign key relation -/// with some another entity `U`, for example, performs a search to find -/// what is the parent type `U` of `T` -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_search_by_foreign_key() { - let some_tournament: Tournament = Tournament::find_by_pk(&1) - .await - .expect("Result variant of the query is err") - .expect("No result found for the given parameter"); - - // We can get the parent entity for the retrieved child instance - let parent_entity: Option = some_tournament - .search_league() - .await - .expect("Result variant of the query is err"); - - if let Some(league) = parent_entity { - assert_eq!(some_tournament.league, league.id) - } else { - assert_eq!(parent_entity, None) - } -} - -/// Same as the search by foreign key, but with the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_search_by_foreign_key_with_mssql() { - let some_tournament: Tournament = Tournament::find_by_pk_with(&10, SQL_SERVER_DS) - .await - .expect("Result variant of the query is err") - .expect("No result found for the given parameter"); - - // We can get the parent entity for the retrieved child instance - let parent_entity: Option = some_tournament - .search_league_with(SQL_SERVER_DS) - .await - .expect("Result variant of the query is err"); - - // These are tests, and we could unwrap the result contained in the option, because - // it always should exist that search for the data inserted when the docker starts. - // But, just for change the style a little bit and offer more options about how to - // handle things done with Canyon - if let Some(league) = parent_entity { - assert_eq!(some_tournament.league, league.id) - } else { - assert_eq!(parent_entity, None) - } -} - -/// Same as the search by foreign key, but with the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_search_by_foreign_key_with_mysql() { - let some_tournament: Tournament = Tournament::find_by_pk_with(&10, MYSQL_DS) - .await - .expect("Result variant of the query is err") - .expect("No result found for the given parameter"); - - // We can get the parent entity for the retrieved child instance - let parent_entity: Option = some_tournament - .search_league_with(MYSQL_DS) - .await - .expect("Result variant of the query is err"); - - // These are tests, and we could unwrap the result contained in the option, because - // it always should exist that search for the data inserted when the docker starts. - // But, just for change the style a little bit and offer more options about how to - // handle things done with Canyon - if let Some(league) = parent_entity { - assert_eq!(some_tournament.league, league.id) - } else { - assert_eq!(parent_entity, None) - } -} - -/// Given an entity `U` that is know as the "parent" side of the relation with another -/// entity `T`, for example, we can ask to the parent for the childrens that belongs -/// to `U`. -/// -/// For this to work, `U`, the parent, must have derived the `ForeignKeyable` proc macro -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_search_reverse_side_foreign_key() { - let some_league: League = League::find_by_pk(&1) - .await - .expect("Result variant of the query is err") - .expect("No result found for the given parameter"); - - // Computes how many tournaments are pointing to the retrieved league - let child_tournaments: Vec = Tournament::search_league_childrens(&some_league) - .await - .expect("Result variant of the query is err"); - - assert!(!child_tournaments.is_empty()); - child_tournaments - .iter() - .for_each(|t| assert_eq!(t.league, some_league.id)); -} - -/// Same as the search by the reverse side of a foreign key relation -/// but with the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_search_reverse_side_foreign_key_with_mssql() { - let some_league: League = League::find_by_pk_with(&1, SQL_SERVER_DS) - .await - .expect("Result variant of the query is err") - .expect("No result found for the given parameter"); - - // Computes how many tournaments are pointing to the retrieved league - let child_tournaments: Vec = - Tournament::search_league_childrens_with(&some_league, SQL_SERVER_DS) - .await - .expect("Result variant of the query is err"); - - assert!(!child_tournaments.is_empty()); - child_tournaments - .iter() - .for_each(|t| assert_eq!(t.league, some_league.id)); -} - -/// Same as the search by the reverse side of a foreign key relation -/// but with the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_search_reverse_side_foreign_key_with_mysql() { - let some_league: League = League::find_by_pk_with(&1, MYSQL_DS) - .await - .expect("Result variant of the query is err") - .expect("No result found for the given parameter"); - - // Computes how many tournaments are pointing to the retrieved league - let child_tournaments: Vec = - Tournament::search_league_childrens_with(&some_league, MYSQL_DS) - .await - .expect("Result variant of the query is err"); - - assert!(!child_tournaments.is_empty()); - child_tournaments - .iter() - .for_each(|t| assert_eq!(t.league, some_league.id)); -} +// /// Integration tests for the CRUD operations available in `Canyon` that +// /// generates and executes *SELECT* statements based on a entity +// /// annotated with the `#[foreign_key(... args)]` annotation looking +// /// for the related data with some entity `U` that acts as is parent, where `U` +// /// impls `ForeignKeyable` (isn't required, but it won't unlock the +// /// reverse search features parent -> child, only the child -> parent ones). +// /// +// /// Names of the foreign key methods are autogenerated for the direct and +// /// reverse side of the implementations. +// /// For more info: TODO -> Link to the docs of the foreign key chapter +// use canyon_sql::crud::CrudOperations; +// +// #[cfg(feature = "mssql")] +// use crate::constants::MYSQL_DS; +// #[cfg(feature = "mssql")] +// use crate::constants::SQL_SERVER_DS; +// +// use crate::tests_models::league::*; +// use crate::tests_models::tournament::*; +// +// /// Given an entity `T` which has some field declaring a foreign key relation +// /// with some another entity `U`, for example, performs a search to find +// /// what is the parent type `U` of `T` +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_search_by_foreign_key() { +// let some_tournament: Tournament = Tournament::find_by_pk(&1) +// .await +// .expect("Result variant of the query is err") +// .expect("No result found for the given parameter"); +// +// // We can get the parent entity for the retrieved child instance +// let parent_entity: Option = some_tournament +// .search_league() +// .await +// .expect("Result variant of the query is err"); +// +// if let Some(league) = parent_entity { +// assert_eq!(some_tournament.league, league.id) +// } else { +// assert_eq!(parent_entity, None) +// } +// } +// +// /// Same as the search by foreign key, but with the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_search_by_foreign_key_with_mssql() { +// let some_tournament: Tournament = Tournament::find_by_pk_with(&10, SQL_SERVER_DS) +// .await +// .expect("Result variant of the query is err") +// .expect("No result found for the given parameter"); +// +// // We can get the parent entity for the retrieved child instance +// let parent_entity: Option = some_tournament +// .search_league_with(SQL_SERVER_DS) +// .await +// .expect("Result variant of the query is err"); +// +// // These are tests, and we could unwrap the result contained in the option, because +// // it always should exist that search for the data inserted when the docker starts. +// // But, just for change the style a little bit and offer more options about how to +// // handle things done with Canyon +// if let Some(league) = parent_entity { +// assert_eq!(some_tournament.league, league.id) +// } else { +// assert_eq!(parent_entity, None) +// } +// } +// +// /// Same as the search by foreign key, but with the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_search_by_foreign_key_with_mysql() { +// let some_tournament: Tournament = Tournament::find_by_pk_with(&10, MYSQL_DS) +// .await +// .expect("Result variant of the query is err") +// .expect("No result found for the given parameter"); +// +// // We can get the parent entity for the retrieved child instance +// let parent_entity: Option = some_tournament +// .search_league_with(MYSQL_DS) +// .await +// .expect("Result variant of the query is err"); +// +// // These are tests, and we could unwrap the result contained in the option, because +// // it always should exist that search for the data inserted when the docker starts. +// // But, just for change the style a little bit and offer more options about how to +// // handle things done with Canyon +// if let Some(league) = parent_entity { +// assert_eq!(some_tournament.league, league.id) +// } else { +// assert_eq!(parent_entity, None) +// } +// } +// +// /// Given an entity `U` that is know as the "parent" side of the relation with another +// /// entity `T`, for example, we can ask to the parent for the childrens that belongs +// /// to `U`. +// /// +// /// For this to work, `U`, the parent, must have derived the `ForeignKeyable` proc macro +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_search_reverse_side_foreign_key() { +// let some_league: League = League::find_by_pk(&1) +// .await +// .expect("Result variant of the query is err") +// .expect("No result found for the given parameter"); +// +// // Computes how many tournaments are pointing to the retrieved league +// let child_tournaments: Vec = Tournament::search_league_childrens(&some_league) +// .await +// .expect("Result variant of the query is err"); +// +// assert!(!child_tournaments.is_empty()); +// child_tournaments +// .iter() +// .for_each(|t| assert_eq!(t.league, some_league.id)); +// } +// +// /// Same as the search by the reverse side of a foreign key relation +// /// but with the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_search_reverse_side_foreign_key_with_mssql() { +// let some_league: League = League::find_by_pk_with(&1, SQL_SERVER_DS) +// .await +// .expect("Result variant of the query is err") +// .expect("No result found for the given parameter"); +// +// // Computes how many tournaments are pointing to the retrieved league +// let child_tournaments: Vec = +// Tournament::search_league_childrens_with(&some_league, SQL_SERVER_DS) +// .await +// .expect("Result variant of the query is err"); +// +// assert!(!child_tournaments.is_empty()); +// child_tournaments +// .iter() +// .for_each(|t| assert_eq!(t.league, some_league.id)); +// } +// +// /// Same as the search by the reverse side of a foreign key relation +// /// but with the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_search_reverse_side_foreign_key_with_mysql() { +// let some_league: League = League::find_by_pk_with(&1, MYSQL_DS) +// .await +// .expect("Result variant of the query is err") +// .expect("No result found for the given parameter"); +// +// // Computes how many tournaments are pointing to the retrieved league +// let child_tournaments: Vec = +// Tournament::search_league_childrens_with(&some_league, MYSQL_DS) +// .await +// .expect("Result variant of the query is err"); +// +// assert!(!child_tournaments.is_empty()); +// child_tournaments +// .iter() +// .for_each(|t| assert_eq!(t.league, some_league.id)); +// } diff --git a/tests/crud/init_mssql.rs b/tests/crud/init_mssql.rs index 2be533bf..8c76da9c 100644 --- a/tests/crud/init_mssql.rs +++ b/tests/crud/init_mssql.rs @@ -1,62 +1,62 @@ -use crate::constants::SQL_SERVER_CREATE_TABLES; -use crate::constants::SQL_SERVER_DS; -use crate::constants::SQL_SERVER_FILL_TABLE_VALUES; -use crate::tests_models::league::League; - -use canyon_sql::crud::CrudOperations; -use canyon_sql::db_clients::tiberius::{Client, Config}; -use canyon_sql::runtime::tokio::net::TcpStream; -use canyon_sql::runtime::tokio_util::compat::TokioAsyncWriteCompatExt; - -/// In order to initialize data on `SqlServer`. we must manually insert it -/// when the docker starts. SqlServer official docker from Microsoft does -/// not allow you to run `.sql` files against the database (not at least, without) -/// using a workaround. So, we are going to query the `SqlServer` to check if already -/// has some data (other processes, persistence or multi-threading envs), af if not, -/// we are going to retrieve the inserted data on the `postgreSQL` at start-up and -/// inserting into the `SqlServer` instance. -/// -/// This will be marked as `#[ignore]`, so we can force to run first the marked as -/// ignored, check the data available, perform the necessary init operations and -/// then *cargo test * the real integration tests -#[canyon_sql::macros::canyon_tokio_test] -#[ignore] -fn initialize_sql_server_docker_instance() { - static CONN_STR: &str = // TODO: change this for the DS when will be in the public API - "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true"; - - canyon_sql::runtime::futures::executor::block_on(async { - let config = Config::from_ado_string(CONN_STR).unwrap(); - - let tcp = TcpStream::connect(config.get_addr()).await.unwrap(); - let tcp2 = TcpStream::connect(config.get_addr()).await.unwrap(); - tcp.set_nodelay(true).ok(); - - let mut client = Client::connect(config.clone(), tcp.compat_write()) - .await - .unwrap(); - - // Create the tables - let query_result = client.query(SQL_SERVER_CREATE_TABLES, &[]).await; - assert!(query_result.is_ok()); - - let leagues_sql = League::find_all_with(SQL_SERVER_DS).await; - println!("LSqlServer: {leagues_sql:?}"); - assert!(leagues_sql.is_ok()); - - match leagues_sql { - Ok(ref leagues) => { - let leagues_len = leagues.len(); - println!("Leagues already inserted on SQLSERVER: {:?}", &leagues_len); - if leagues.len() < 10 { - let mut client2 = Client::connect(config, tcp2.compat_write()) - .await - .expect("Can't connect to MSSQL"); - let result = client2.query(SQL_SERVER_FILL_TABLE_VALUES, &[]).await; - assert!(result.is_ok()); - } - } - Err(e) => eprintln!("Error retrieving the leagues: {e}"), - } - }); -} +// use crate::constants::SQL_SERVER_CREATE_TABLES; +// use crate::constants::SQL_SERVER_DS; +// use crate::constants::SQL_SERVER_FILL_TABLE_VALUES; +// use crate::tests_models::league::League; +// +// use canyon_sql::crud::CrudOperations; +// use canyon_sql::db_clients::tiberius::{Client, Config}; +// use canyon_sql::runtime::tokio::net::TcpStream; +// use canyon_sql::runtime::tokio_util::compat::TokioAsyncWriteCompatExt; +// +// /// In order to initialize data on `SqlServer`. we must manually insert it +// /// when the docker starts. SqlServer official docker from Microsoft does +// /// not allow you to run `.sql` files against the database (not at least, without) +// /// using a workaround. So, we are going to query the `SqlServer` to check if already +// /// has some data (other processes, persistence or multi-threading envs), af if not, +// /// we are going to retrieve the inserted data on the `postgreSQL` at start-up and +// /// inserting into the `SqlServer` instance. +// /// +// /// This will be marked as `#[ignore]`, so we can force to run first the marked as +// /// ignored, check the data available, perform the necessary init operations and +// /// then *cargo test * the real integration tests +// #[canyon_sql::macros::canyon_tokio_test] +// #[ignore] +// fn initialize_sql_server_docker_instance() { +// static CONN_STR: &str = // TODO: change this for the DS when will be in the public API +// "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true"; +// +// canyon_sql::runtime::futures::executor::block_on(async { +// let config = Config::from_ado_string(CONN_STR).unwrap(); +// +// let tcp = TcpStream::connect(config.get_addr()).await.unwrap(); +// let tcp2 = TcpStream::connect(config.get_addr()).await.unwrap(); +// tcp.set_nodelay(true).ok(); +// +// let mut client = Client::connect(config.clone(), tcp.compat_write()) +// .await +// .unwrap(); +// +// // Create the tables +// let query_result = client.query(SQL_SERVER_CREATE_TABLES, &[]).await; +// assert!(query_result.is_ok()); +// +// let leagues_sql = League::find_all_with(SQL_SERVER_DS).await; +// println!("LSqlServer: {leagues_sql:?}"); +// assert!(leagues_sql.is_ok()); +// +// match leagues_sql { +// Ok(ref leagues) => { +// let leagues_len = leagues.len(); +// println!("Leagues already inserted on SQLSERVER: {:?}", &leagues_len); +// if leagues.len() < 10 { +// let mut client2 = Client::connect(config, tcp2.compat_write()) +// .await +// .expect("Can't connect to MSSQL"); +// let result = client2.query(SQL_SERVER_FILL_TABLE_VALUES, &[]).await; +// assert!(result.is_ok()); +// } +// } +// Err(e) => eprintln!("Error retrieving the leagues: {e}"), +// } +// }); +// } diff --git a/tests/crud/insert_operations.rs b/tests/crud/insert_operations.rs index 4fb742ca..4b135fff 100644 --- a/tests/crud/insert_operations.rs +++ b/tests/crud/insert_operations.rs @@ -1,317 +1,317 @@ -//! Integration tests for the CRUD operations available in `Canyon` that -//! generates and executes *INSERT* statements -use canyon_sql::crud::CrudOperations; - -#[cfg(feature = "mysql")] -use crate::constants::MYSQL_DS; -#[cfg(feature = "mssql")] -use crate::constants::SQL_SERVER_DS; - -use crate::tests_models::league::*; - -/// Inserts a new record on the database, given an entity that is -/// annotated with `#[canyon_entity]` macro over a *T* type. -/// -/// For insert a new record on a database, the *insert* operation needs -/// some special requirements: -/// > - We need a mutable instance of `T`. If the operation completes -/// successfully, the insert operation will automatically set the autogenerated -/// value for the `primary_key` annotated field in it. -/// -/// > - It's considered a good practice to initialize that concrete field with -/// the `Default` trait, because the value on the primary key field will be -/// ignored at the execution time of the insert, and updated with the autogenerated -/// value by the database. -/// -/// By default, the `#[primary_key]` annotation means autogenerated and autoincremental. -/// You can configure not autoincremental via macro annotation parameters (please, -/// refer to the docs [here]() for more info.) -/// -/// If the type hasn't a `#[primary_key]` annotation, or the annotation contains -/// an argument specifying not autoincremental behaviour, all the fields will be -/// inserted on the database and no returning value will be placed in any field. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_insert_operation() { - let mut new_league: League = League { - id: Default::default(), - ext_id: 7892635306594_i64, - slug: "some-new-league".to_string(), - name: "Some New League".to_string(), - region: "Bahía de cochinos".to_string(), - image_url: "https://nobodyspectsandimage.io".to_string(), - }; - - // We insert the instance on the database, on the `League` entity - new_league.insert().await.expect("Failed insert operation"); - - // Now, in the `id` field of the instance, we have the autogenerated - // value for the primary key field, which is id. So, we can query the - // database again with the find by primary key operation to check if - // the value was really inserted - let inserted_league = League::find_by_pk(&new_league.id) - .await - .expect("Failed the query to the database") - .expect("No entity found for the primary key value passed in"); - - assert_eq!(new_league.id, inserted_league.id); -} - -/// Same as the insert operation above, but targeting the database defined in -/// the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_insert_with_mssql_operation() { - let mut new_league: League = League { - id: Default::default(), - ext_id: 7892635306594_i64, - slug: "some-new-league".to_string(), - name: "Some New League".to_string(), - region: "Bahía de cochinos".to_string(), - image_url: "https://nobodyspectsandimage.io".to_string(), - }; - - // We insert the instance on the database, on the `League` entity - new_league - .insert_with(SQL_SERVER_DS) - .await - .expect("Failed insert datasource operation"); - - // Now, in the `id` field of the instance, we have the autogenerated - // value for the primary key field, which is id. So, we can query the - // database again with the find by primary key operation to check if - // the value was really inserted - let inserted_league = League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) - .await - .expect("Failed the query to the database") - .expect("No entity found for the primary key value passed in"); - - assert_eq!(new_league.id, inserted_league.id); -} - -/// Same as the insert operation above, but targeting the database defined in -/// the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_insert_with_mysql_operation() { - let mut new_league: League = League { - id: Default::default(), - ext_id: 7892635306594_i64, - slug: "some-new-league".to_string(), - name: "Some New League".to_string(), - region: "Bahía de cochinos".to_string(), - image_url: "https://nobodyspectsandimage.io".to_string(), - }; - - // We insert the instance on the database, on the `League` entity - new_league - .insert_with(MYSQL_DS) - .await - .expect("Failed insert datasource operation"); - - // Now, in the `id` field of the instance, we have the autogenerated - // value for the primary key field, which is id. So, we can query the - // database again with the find by primary key operation to check if - // the value was really inserted - let inserted_league = League::find_by_pk_with(&new_league.id, MYSQL_DS) - .await - .expect("Failed the query to the database") - .expect("No entity found for the primary key value passed in"); - - assert_eq!(new_league.id, inserted_league.id); -} - -/// The multi insert operation is a shorthand for insert multiple instances of *T* -/// in the database at once. -/// -/// It works pretty much the same that the insert operation, with the same behaviour -/// of the `#[primary_key]` annotation over some field. It will auto set the primary -/// key field with the autogenerated value on the database on the insert operation, but -/// for every entity passed in as an array of mutable instances of `T`. -/// -/// The instances without `#[primary_key]` inserts all the values on the instaqce fields -/// on the database. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_multi_insert_operation() { - let mut new_league_mi: League = League { - id: Default::default(), - ext_id: 54376478_i64, - slug: "some-new-random-league".to_string(), - name: "Some New Random League".to_string(), - region: "Unknown".to_string(), - image_url: "https://what-a-league.io".to_string(), - }; - let mut new_league_mi_2: League = League { - id: Default::default(), - ext_id: 3475689769678906_i64, - slug: "new-league-2".to_string(), - name: "New League 2".to_string(), - region: "Really unknown".to_string(), - image_url: "https://what-an-unknown-league.io".to_string(), - }; - let mut new_league_mi_3: League = League { - id: Default::default(), - ext_id: 46756867_i64, - slug: "a-new-multinsert".to_string(), - name: "New League 3".to_string(), - region: "The dark side of the moon".to_string(), - image_url: "https://interplanetary-league.io".to_string(), - }; - - // Insert the instance as database entities - new_league_mi - .insert() - .await - .expect("Failed insert datasource operation"); - new_league_mi_2 - .insert() - .await - .expect("Failed insert datasource operation"); - new_league_mi_3 - .insert() - .await - .expect("Failed insert datasource operation"); - - // Recover the inserted data by primary key - let inserted_league = League::find_by_pk(&new_league_mi.id) - .await - .expect("[1] - Failed the query to the database") - .expect("[1] - No entity found for the primary key value passed in"); - let inserted_league_2 = League::find_by_pk(&new_league_mi_2.id) - .await - .expect("[2] - Failed the query to the database") - .expect("[2] - No entity found for the primary key value passed in"); - let inserted_league_3 = League::find_by_pk(&new_league_mi_3.id) - .await - .expect("[3] - Failed the query to the database") - .expect("[3] - No entity found for the primary key value passed in"); - - assert_eq!(new_league_mi.id, inserted_league.id); - assert_eq!(new_league_mi_2.id, inserted_league_2.id); - assert_eq!(new_league_mi_3.id, inserted_league_3.id); -} - -/// Same as the multi insert above, but with the specified datasource -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_multi_insert_with_mssql_operation() { - let mut new_league_mi: League = League { - id: Default::default(), - ext_id: 54376478_i64, - slug: "some-new-random-league".to_string(), - name: "Some New Random League".to_string(), - region: "Unknown".to_string(), - image_url: "https://what-a-league.io".to_string(), - }; - let mut new_league_mi_2: League = League { - id: Default::default(), - ext_id: 3475689769678906_i64, - slug: "new-league-2".to_string(), - name: "New League 2".to_string(), - region: "Really unknown".to_string(), - image_url: "https://what-an-unknown-league.io".to_string(), - }; - let mut new_league_mi_3: League = League { - id: Default::default(), - ext_id: 46756867_i64, - slug: "a-new-multinsert".to_string(), - name: "New League 3".to_string(), - region: "The dark side of the moon".to_string(), - image_url: "https://interplanetary-league.io".to_string(), - }; - - // Insert the instance as database entities - new_league_mi - .insert_with(SQL_SERVER_DS) - .await - .expect("Failed insert datasource operation"); - new_league_mi_2 - .insert_with(SQL_SERVER_DS) - .await - .expect("Failed insert datasource operation"); - new_league_mi_3 - .insert_with(SQL_SERVER_DS) - .await - .expect("Failed insert datasource operation"); - - // Recover the inserted data by primary key - let inserted_league = League::find_by_pk_with(&new_league_mi.id, SQL_SERVER_DS) - .await - .expect("[1] - Failed the query to the database") - .expect("[1] - No entity found for the primary key value passed in"); - let inserted_league_2 = League::find_by_pk_with(&new_league_mi_2.id, SQL_SERVER_DS) - .await - .expect("[2] - Failed the query to the database") - .expect("[2] - No entity found for the primary key value passed in"); - let inserted_league_3 = League::find_by_pk_with(&new_league_mi_3.id, SQL_SERVER_DS) - .await - .expect("[3] - Failed the query to the database") - .expect("[3] - No entity found for the primary key value passed in"); - - assert_eq!(new_league_mi.id, inserted_league.id); - assert_eq!(new_league_mi_2.id, inserted_league_2.id); - assert_eq!(new_league_mi_3.id, inserted_league_3.id); -} - -/// Same as the multi insert above, but with the specified datasource -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_multi_insert_with_mysql_operation() { - let mut new_league_mi: League = League { - id: Default::default(), - ext_id: 54376478_i64, - slug: "some-new-random-league".to_string(), - name: "Some New Random League".to_string(), - region: "Unknown".to_string(), - image_url: "https://what-a-league.io".to_string(), - }; - let mut new_league_mi_2: League = League { - id: Default::default(), - ext_id: 3475689769678906_i64, - slug: "new-league-2".to_string(), - name: "New League 2".to_string(), - region: "Really unknown".to_string(), - image_url: "https://what-an-unknown-league.io".to_string(), - }; - let mut new_league_mi_3: League = League { - id: Default::default(), - ext_id: 46756867_i64, - slug: "a-new-multinsert".to_string(), - name: "New League 3".to_string(), - region: "The dark side of the moon".to_string(), - image_url: "https://interplanetary-league.io".to_string(), - }; - - // Insert the instance as database entities - new_league_mi - .insert_with(MYSQL_DS) - .await - .expect("Failed insert datasource operation"); - new_league_mi_2 - .insert_with(MYSQL_DS) - .await - .expect("Failed insert datasource operation"); - new_league_mi_3 - .insert_with(MYSQL_DS) - .await - .expect("Failed insert datasource operation"); - - // Recover the inserted data by primary key - let inserted_league = League::find_by_pk_with(&new_league_mi.id, MYSQL_DS) - .await - .expect("[1] - Failed the query to the database") - .expect("[1] - No entity found for the primary key value passed in"); - let inserted_league_2 = League::find_by_pk_with(&new_league_mi_2.id, MYSQL_DS) - .await - .expect("[2] - Failed the query to the database") - .expect("[2] - No entity found for the primary key value passed in"); - let inserted_league_3 = League::find_by_pk_with(&new_league_mi_3.id, MYSQL_DS) - .await - .expect("[3] - Failed the query to the database") - .expect("[3] - No entity found for the primary key value passed in"); - - assert_eq!(new_league_mi.id, inserted_league.id); - assert_eq!(new_league_mi_2.id, inserted_league_2.id); - assert_eq!(new_league_mi_3.id, inserted_league_3.id); -} +// //! Integration tests for the CRUD operations available in `Canyon` that +// //! generates and executes *INSERT* statements +// use canyon_sql::crud::CrudOperations; +// +// #[cfg(feature = "mysql")] +// use crate::constants::MYSQL_DS; +// #[cfg(feature = "mssql")] +// use crate::constants::SQL_SERVER_DS; +// +// use crate::tests_models::league::*; +// +// /// Inserts a new record on the database, given an entity that is +// /// annotated with `#[canyon_entity]` macro over a *T* type. +// /// +// /// For insert a new record on a database, the *insert* operation needs +// /// some special requirements: +// /// > - We need a mutable instance of `T`. If the operation completes +// /// successfully, the insert operation will automatically set the autogenerated +// /// value for the `primary_key` annotated field in it. +// /// +// /// > - It's considered a good practice to initialize that concrete field with +// /// the `Default` trait, because the value on the primary key field will be +// /// ignored at the execution time of the insert, and updated with the autogenerated +// /// value by the database. +// /// +// /// By default, the `#[primary_key]` annotation means autogenerated and autoincremental. +// /// You can configure not autoincremental via macro annotation parameters (please, +// /// refer to the docs [here]() for more info.) +// /// +// /// If the type hasn't a `#[primary_key]` annotation, or the annotation contains +// /// an argument specifying not autoincremental behaviour, all the fields will be +// /// inserted on the database and no returning value will be placed in any field. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_insert_operation() { +// let mut new_league: League = League { +// id: Default::default(), +// ext_id: 7892635306594_i64, +// slug: "some-new-league".to_string(), +// name: "Some New League".to_string(), +// region: "Bahía de cochinos".to_string(), +// image_url: "https://nobodyspectsandimage.io".to_string(), +// }; +// +// // We insert the instance on the database, on the `League` entity +// new_league.insert().await.expect("Failed insert operation"); +// +// // Now, in the `id` field of the instance, we have the autogenerated +// // value for the primary key field, which is id. So, we can query the +// // database again with the find by primary key operation to check if +// // the value was really inserted +// let inserted_league = League::find_by_pk(&new_league.id) +// .await +// .expect("Failed the query to the database") +// .expect("No entity found for the primary key value passed in"); +// +// assert_eq!(new_league.id, inserted_league.id); +// } +// +// /// Same as the insert operation above, but targeting the database defined in +// /// the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_insert_with_mssql_operation() { +// let mut new_league: League = League { +// id: Default::default(), +// ext_id: 7892635306594_i64, +// slug: "some-new-league".to_string(), +// name: "Some New League".to_string(), +// region: "Bahía de cochinos".to_string(), +// image_url: "https://nobodyspectsandimage.io".to_string(), +// }; +// +// // We insert the instance on the database, on the `League` entity +// new_league +// .insert_with(SQL_SERVER_DS) +// .await +// .expect("Failed insert datasource operation"); +// +// // Now, in the `id` field of the instance, we have the autogenerated +// // value for the primary key field, which is id. So, we can query the +// // database again with the find by primary key operation to check if +// // the value was really inserted +// let inserted_league = League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) +// .await +// .expect("Failed the query to the database") +// .expect("No entity found for the primary key value passed in"); +// +// assert_eq!(new_league.id, inserted_league.id); +// } +// +// /// Same as the insert operation above, but targeting the database defined in +// /// the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_insert_with_mysql_operation() { +// let mut new_league: League = League { +// id: Default::default(), +// ext_id: 7892635306594_i64, +// slug: "some-new-league".to_string(), +// name: "Some New League".to_string(), +// region: "Bahía de cochinos".to_string(), +// image_url: "https://nobodyspectsandimage.io".to_string(), +// }; +// +// // We insert the instance on the database, on the `League` entity +// new_league +// .insert_with(MYSQL_DS) +// .await +// .expect("Failed insert datasource operation"); +// +// // Now, in the `id` field of the instance, we have the autogenerated +// // value for the primary key field, which is id. So, we can query the +// // database again with the find by primary key operation to check if +// // the value was really inserted +// let inserted_league = League::find_by_pk_with(&new_league.id, MYSQL_DS) +// .await +// .expect("Failed the query to the database") +// .expect("No entity found for the primary key value passed in"); +// +// assert_eq!(new_league.id, inserted_league.id); +// } +// +// /// The multi insert operation is a shorthand for insert multiple instances of *T* +// /// in the database at once. +// /// +// /// It works pretty much the same that the insert operation, with the same behaviour +// /// of the `#[primary_key]` annotation over some field. It will auto set the primary +// /// key field with the autogenerated value on the database on the insert operation, but +// /// for every entity passed in as an array of mutable instances of `T`. +// /// +// /// The instances without `#[primary_key]` inserts all the values on the instaqce fields +// /// on the database. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_multi_insert_operation() { +// let mut new_league_mi: League = League { +// id: Default::default(), +// ext_id: 54376478_i64, +// slug: "some-new-random-league".to_string(), +// name: "Some New Random League".to_string(), +// region: "Unknown".to_string(), +// image_url: "https://what-a-league.io".to_string(), +// }; +// let mut new_league_mi_2: League = League { +// id: Default::default(), +// ext_id: 3475689769678906_i64, +// slug: "new-league-2".to_string(), +// name: "New League 2".to_string(), +// region: "Really unknown".to_string(), +// image_url: "https://what-an-unknown-league.io".to_string(), +// }; +// let mut new_league_mi_3: League = League { +// id: Default::default(), +// ext_id: 46756867_i64, +// slug: "a-new-multinsert".to_string(), +// name: "New League 3".to_string(), +// region: "The dark side of the moon".to_string(), +// image_url: "https://interplanetary-league.io".to_string(), +// }; +// +// // Insert the instance as database entities +// new_league_mi +// .insert() +// .await +// .expect("Failed insert datasource operation"); +// new_league_mi_2 +// .insert() +// .await +// .expect("Failed insert datasource operation"); +// new_league_mi_3 +// .insert() +// .await +// .expect("Failed insert datasource operation"); +// +// // Recover the inserted data by primary key +// let inserted_league = League::find_by_pk(&new_league_mi.id) +// .await +// .expect("[1] - Failed the query to the database") +// .expect("[1] - No entity found for the primary key value passed in"); +// let inserted_league_2 = League::find_by_pk(&new_league_mi_2.id) +// .await +// .expect("[2] - Failed the query to the database") +// .expect("[2] - No entity found for the primary key value passed in"); +// let inserted_league_3 = League::find_by_pk(&new_league_mi_3.id) +// .await +// .expect("[3] - Failed the query to the database") +// .expect("[3] - No entity found for the primary key value passed in"); +// +// assert_eq!(new_league_mi.id, inserted_league.id); +// assert_eq!(new_league_mi_2.id, inserted_league_2.id); +// assert_eq!(new_league_mi_3.id, inserted_league_3.id); +// } +// +// /// Same as the multi insert above, but with the specified datasource +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_multi_insert_with_mssql_operation() { +// let mut new_league_mi: League = League { +// id: Default::default(), +// ext_id: 54376478_i64, +// slug: "some-new-random-league".to_string(), +// name: "Some New Random League".to_string(), +// region: "Unknown".to_string(), +// image_url: "https://what-a-league.io".to_string(), +// }; +// let mut new_league_mi_2: League = League { +// id: Default::default(), +// ext_id: 3475689769678906_i64, +// slug: "new-league-2".to_string(), +// name: "New League 2".to_string(), +// region: "Really unknown".to_string(), +// image_url: "https://what-an-unknown-league.io".to_string(), +// }; +// let mut new_league_mi_3: League = League { +// id: Default::default(), +// ext_id: 46756867_i64, +// slug: "a-new-multinsert".to_string(), +// name: "New League 3".to_string(), +// region: "The dark side of the moon".to_string(), +// image_url: "https://interplanetary-league.io".to_string(), +// }; +// +// // Insert the instance as database entities +// new_league_mi +// .insert_with(SQL_SERVER_DS) +// .await +// .expect("Failed insert datasource operation"); +// new_league_mi_2 +// .insert_with(SQL_SERVER_DS) +// .await +// .expect("Failed insert datasource operation"); +// new_league_mi_3 +// .insert_with(SQL_SERVER_DS) +// .await +// .expect("Failed insert datasource operation"); +// +// // Recover the inserted data by primary key +// let inserted_league = League::find_by_pk_with(&new_league_mi.id, SQL_SERVER_DS) +// .await +// .expect("[1] - Failed the query to the database") +// .expect("[1] - No entity found for the primary key value passed in"); +// let inserted_league_2 = League::find_by_pk_with(&new_league_mi_2.id, SQL_SERVER_DS) +// .await +// .expect("[2] - Failed the query to the database") +// .expect("[2] - No entity found for the primary key value passed in"); +// let inserted_league_3 = League::find_by_pk_with(&new_league_mi_3.id, SQL_SERVER_DS) +// .await +// .expect("[3] - Failed the query to the database") +// .expect("[3] - No entity found for the primary key value passed in"); +// +// assert_eq!(new_league_mi.id, inserted_league.id); +// assert_eq!(new_league_mi_2.id, inserted_league_2.id); +// assert_eq!(new_league_mi_3.id, inserted_league_3.id); +// } +// +// /// Same as the multi insert above, but with the specified datasource +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_multi_insert_with_mysql_operation() { +// let mut new_league_mi: League = League { +// id: Default::default(), +// ext_id: 54376478_i64, +// slug: "some-new-random-league".to_string(), +// name: "Some New Random League".to_string(), +// region: "Unknown".to_string(), +// image_url: "https://what-a-league.io".to_string(), +// }; +// let mut new_league_mi_2: League = League { +// id: Default::default(), +// ext_id: 3475689769678906_i64, +// slug: "new-league-2".to_string(), +// name: "New League 2".to_string(), +// region: "Really unknown".to_string(), +// image_url: "https://what-an-unknown-league.io".to_string(), +// }; +// let mut new_league_mi_3: League = League { +// id: Default::default(), +// ext_id: 46756867_i64, +// slug: "a-new-multinsert".to_string(), +// name: "New League 3".to_string(), +// region: "The dark side of the moon".to_string(), +// image_url: "https://interplanetary-league.io".to_string(), +// }; +// +// // Insert the instance as database entities +// new_league_mi +// .insert_with(MYSQL_DS) +// .await +// .expect("Failed insert datasource operation"); +// new_league_mi_2 +// .insert_with(MYSQL_DS) +// .await +// .expect("Failed insert datasource operation"); +// new_league_mi_3 +// .insert_with(MYSQL_DS) +// .await +// .expect("Failed insert datasource operation"); +// +// // Recover the inserted data by primary key +// let inserted_league = League::find_by_pk_with(&new_league_mi.id, MYSQL_DS) +// .await +// .expect("[1] - Failed the query to the database") +// .expect("[1] - No entity found for the primary key value passed in"); +// let inserted_league_2 = League::find_by_pk_with(&new_league_mi_2.id, MYSQL_DS) +// .await +// .expect("[2] - Failed the query to the database") +// .expect("[2] - No entity found for the primary key value passed in"); +// let inserted_league_3 = League::find_by_pk_with(&new_league_mi_3.id, MYSQL_DS) +// .await +// .expect("[3] - Failed the query to the database") +// .expect("[3] - No entity found for the primary key value passed in"); +// +// assert_eq!(new_league_mi.id, inserted_league.id); +// assert_eq!(new_league_mi_2.id, inserted_league_2.id); +// assert_eq!(new_league_mi_3.id, inserted_league_3.id); +// } diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index 281642cb..0b6e8925 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -22,21 +22,21 @@ use crate::tests_models::tournament::*; /// with the parameters that modifies the base SQL to SELECT * FROM #[canyon_sql::macros::canyon_tokio_test] fn test_generated_sql_by_the_select_querybuilder() { - let select_with_joins = League::select_query() - .inner_join("tournament", "league.id", "tournament.league_id") - .left_join("team", "tournament.id", "player.tournament_id") - .r#where(LeagueFieldValue::id(&7), Comp::Gt) - .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) - .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); - // .query() - // .await; - // NOTE: We don't have in the docker the generated relationships - // with the joins, so for now, we are just going to check that the - // generated SQL by the SelectQueryBuilder is the expected - assert_eq!( - select_with_joins.read_sql(), - "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" - ) + // let select_with_joins = League::select_query() + // .inner_join("tournament", "league.id", "tournament.league_id") + // .left_join("team", "tournament.id", "player.tournament_id") + // .r#where(LeagueFieldValue::id(&7), Comp::Gt) + // .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) + // .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); + // // .query() + // // .await; + // // NOTE: We don't have in the docker the generated relationships + // // with the joins, so for now, we are just going to check that the + // // generated SQL by the SelectQueryBuilder is the expected + // assert_eq!( + // select_with_joins.read_sql(), + // "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" + // ) } // Builds a new SQL statement for retrieves entities of the `T` type, filtered @@ -51,15 +51,15 @@ fn test_generated_sql_by_the_select_querybuilder() { // .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) // .query::(MYSQL_DS) // .await; -// +// // let filtered_leagues: Vec = filtered_leagues_result.unwrap(); // assert!(!filtered_leagues.is_empty()); -// +// // let league_idx_0 = filtered_leagues.first().unwrap(); // assert_eq!(league_idx_0.id, 34); // assert_eq!(league_idx_0.region, "KOREA"); // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "postgres")] @@ -68,13 +68,13 @@ fn test_generated_sql_by_the_select_querybuilder() { // // Find all the leagues with "LC" in their name // let filtered_leagues_result = // League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mssql")] @@ -83,13 +83,13 @@ fn test_generated_sql_by_the_select_querybuilder() { // // Find all the leagues with "LC" in their name // let filtered_leagues_result = // League::select_query_with(SQL_SERVER_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mysql")] @@ -98,13 +98,13 @@ fn test_generated_sql_by_the_select_querybuilder() { // // Find all the leagues with "LC" in their name // let filtered_leagues_result = // League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR) ,'%')" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "postgres")] @@ -113,13 +113,13 @@ fn test_generated_sql_by_the_select_querybuilder() { // // Find all the leagues whose name ends with "CK" // let filtered_leagues_result = // League::select_query().r#where(LeagueFieldValue::name(&"CK"), Like::Left); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mssql")] @@ -128,13 +128,13 @@ fn test_generated_sql_by_the_select_querybuilder() { // // Find all the leagues whose name ends with "CK" // let filtered_leagues_result = // League::select_query().r#where(LeagueFieldValue::name(&"CK"), Like::Left); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mysql")] @@ -143,13 +143,13 @@ fn test_generated_sql_by_the_select_querybuilder() { // // Find all the leagues whose name ends with "CK" // let filtered_leagues_result = // League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"CK"), Like::Left); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR))" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "postgres")] @@ -158,13 +158,13 @@ fn test_generated_sql_by_the_select_querybuilder() { // // Find all the leagues whose name starts with "LC" // let filtered_leagues_result = // League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Right); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mssql")] @@ -173,13 +173,13 @@ fn test_generated_sql_by_the_select_querybuilder() { // // Find all the leagues whose name starts with "LC" // let filtered_leagues_result = League::select_query_with(SQL_SERVER_DS) // .r#where(LeagueFieldValue::name(&"LC"), Like::Right); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" // ) // } -// +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mysql")] @@ -188,13 +188,13 @@ fn test_generated_sql_by_the_select_querybuilder() { // // Find all the leagues whose name starts with "LC" // let filtered_leagues_result = // League::select_query_with(DatabaseType::MySQL).r#where(LeagueFieldValue::name(&"LC"), Like::Right); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS CHAR) ,'%')" // ) // } -// +// // /// Same than the above but with the specified datasource // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] @@ -204,10 +204,10 @@ fn test_generated_sql_by_the_select_querybuilder() { // .r#where(PlayerFieldValue::id(&50), Comp::Gt) // .query::(SQL_SERVER_DS) // .await; -// +// // assert!(!filtered_find_players.unwrap().is_empty()); // } -// +// // /// Same than the above but with the specified datasource // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] @@ -217,7 +217,7 @@ fn test_generated_sql_by_the_select_querybuilder() { // .r#where(PlayerFieldValue::id(&50), Comp::Gt) // .query::(MYSQL_DS) // .await; -// +// // assert!(!filtered_find_players.unwrap().is_empty()); // } // diff --git a/tests/crud/read_operations.rs b/tests/crud/read_operations.rs index 8c472a57..8172b004 100644 --- a/tests/crud/read_operations.rs +++ b/tests/crud/read_operations.rs @@ -11,7 +11,8 @@ use crate::Error; use canyon_sql::crud::CrudOperations; use crate::tests_models::league::*; -// use crate::tests_models::player::*; +use crate::tests_models::player::*; +use crate::tests_models::tournament::Tournament; /// Tests the behaviour of a SELECT * FROM {table_name} within Canyon, through the /// `::find_all()` associated function derived with the `CanyonCrud` derive proc-macro @@ -26,9 +27,9 @@ fn test_crud_find_all() { assert!(!find_all_result.is_err()); assert!(!find_all_result.unwrap().is_empty()); - // let find_all_players: Result, Box> = - // Player::find_all().await; - // assert!(!find_all_players.unwrap().is_empty()); + let find_all_players: Result, Box> = + Player::find_all().await; + assert!(!find_all_players.unwrap().is_empty()); } /// Same as the `find_all()`, but with the unchecked variant, which directly returns `Vec` not @@ -64,90 +65,90 @@ fn test_crud_find_all_with_mysql() { assert!(!find_all_result.unwrap().is_empty()); } -// /// Same as the `find_all_with()`, but with the unchecked variant and the specified dataosource, -// /// returning directly `Vec` and not `Result, Err>` -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_all_unchecked_with() { -// let find_all_result: Vec = League::find_all_unchecked_with(SQL_SERVER_DS).await; -// assert!(!find_all_result.is_empty()); -// } -// -// /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -// /// defined with the #[primary_key] attribute over some field of the type. -// /// -// /// Uses the *default datasource*. -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_by_pk() { -// let find_by_pk_result: Result, Box> = -// League::find_by_pk(&1).await; -// assert!(find_by_pk_result.as_ref().unwrap().is_some()); -// -// let some_league = find_by_pk_result.unwrap().unwrap(); -// assert_eq!(some_league.id, 1); -// assert_eq!(some_league.ext_id, 100695891328981122_i64); -// assert_eq!(some_league.slug, "european-masters"); -// assert_eq!(some_league.name, "European Masters"); -// assert_eq!(some_league.region, "EUROPE"); -// assert_eq!( -// some_league.image_url, -// "http://static.lolesports.com/leagues/EM_Bug_Outline1.png" -// ); -// } -// -// /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -// /// defined with the #[primary_key] attribute over some field of the type. -// /// -// /// Uses the *specified datasource mssql* in the second parameter of the function call. -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_by_pk_with_mssql() { -// let find_by_pk_result: Result, Box> = -// League::find_by_pk_with(&27, SQL_SERVER_DS).await; -// assert!(find_by_pk_result.as_ref().unwrap().is_some()); -// -// let some_league = find_by_pk_result.unwrap().unwrap(); -// assert_eq!(some_league.id, 27); -// assert_eq!(some_league.ext_id, 107898214974993351_i64); -// assert_eq!(some_league.slug, "college_championship"); -// assert_eq!(some_league.name, "College Championship"); -// assert_eq!(some_league.region, "NORTH AMERICA"); -// assert_eq!( -// some_league.image_url, -// "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" -// ); -// } -// -// /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -// /// defined with the #[primary_key] attribute over some field of the type. -// /// -// /// Uses the *specified datasource mysql* in the second parameter of the function call. -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_by_pk_with_mysql() { -// let find_by_pk_result: Result, Box> = -// League::find_by_pk_with(&27, MYSQL_DS).await; -// assert!(find_by_pk_result.as_ref().unwrap().is_some()); -// -// let some_league = find_by_pk_result.unwrap().unwrap(); -// assert_eq!(some_league.id, 27); -// assert_eq!(some_league.ext_id, 107898214974993351_i64); -// assert_eq!(some_league.slug, "college_championship"); -// assert_eq!(some_league.name, "College Championship"); -// assert_eq!(some_league.region, "NORTH AMERICA"); -// assert_eq!( -// some_league.image_url, -// "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" -// ); -// } +/// Same as the `find_all_with()`, but with the unchecked variant and the specified dataosource, +/// returning directly `Vec` and not `Result, Err>` +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_all_unchecked_with() { + let find_all_result: Vec = League::find_all_unchecked_with(SQL_SERVER_DS).await; + assert!(!find_all_result.is_empty()); +} + +// // /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +// // /// defined with the #[primary_key] attribute over some field of the type. +// // /// +// // /// Uses the *default datasource*. +// // #[cfg(feature = "postgres")] +// // #[canyon_sql::macros::canyon_tokio_test] +// // fn test_crud_find_by_pk() { +// // let find_by_pk_result: Result, Box> = +// // League::find_by_pk(&1).await; +// // assert!(find_by_pk_result.as_ref().unwrap().is_some()); +// // +// // let some_league = find_by_pk_result.unwrap().unwrap(); +// // assert_eq!(some_league.id, 1); +// // assert_eq!(some_league.ext_id, 100695891328981122_i64); +// // assert_eq!(some_league.slug, "european-masters"); +// // assert_eq!(some_league.name, "European Masters"); +// // assert_eq!(some_league.region, "EUROPE"); +// // assert_eq!( +// // some_league.image_url, +// // "http://static.lolesports.com/leagues/EM_Bug_Outline1.png" +// // ); +// // } +// // +// // /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +// // /// defined with the #[primary_key] attribute over some field of the type. +// // /// +// // /// Uses the *specified datasource mssql* in the second parameter of the function call. +// // #[cfg(feature = "mssql")] +// // #[canyon_sql::macros::canyon_tokio_test] +// // fn test_crud_find_by_pk_with_mssql() { +// // let find_by_pk_result: Result, Box> = +// // League::find_by_pk_with(&27, SQL_SERVER_DS).await; +// // assert!(find_by_pk_result.as_ref().unwrap().is_some()); +// // +// // let some_league = find_by_pk_result.unwrap().unwrap(); +// // assert_eq!(some_league.id, 27); +// // assert_eq!(some_league.ext_id, 107898214974993351_i64); +// // assert_eq!(some_league.slug, "college_championship"); +// // assert_eq!(some_league.name, "College Championship"); +// // assert_eq!(some_league.region, "NORTH AMERICA"); +// // assert_eq!( +// // some_league.image_url, +// // "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" +// // ); +// // } +// // +// // /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +// // /// defined with the #[primary_key] attribute over some field of the type. +// // /// +// // /// Uses the *specified datasource mysql* in the second parameter of the function call. +// // #[cfg(feature = "mysql")] +// // #[canyon_sql::macros::canyon_tokio_test] +// // fn test_crud_find_by_pk_with_mysql() { +// // let find_by_pk_result: Result, Box> = +// // League::find_by_pk_with(&27, MYSQL_DS).await; +// // assert!(find_by_pk_result.as_ref().unwrap().is_some()); +// // +// // let some_league = find_by_pk_result.unwrap().unwrap(); +// // assert_eq!(some_league.id, 27); +// // assert_eq!(some_league.ext_id, 107898214974993351_i64); +// // assert_eq!(some_league.slug, "college_championship"); +// // assert_eq!(some_league.name, "College Championship"); +// // assert_eq!(some_league.region, "NORTH AMERICA"); +// // assert_eq!( +// // some_league.image_url, +// // "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" +// // ); +// // } /// Counts how many rows contains an entity on the target database. #[cfg(feature = "postgres")] #[canyon_sql::macros::canyon_tokio_test] fn test_crud_count_operation() { assert_eq!( - League::find_all().await.unwrap().len() as i64, + League::find_all::().await.unwrap().len() as i64, League::count().await.unwrap() ); } @@ -158,7 +159,10 @@ fn test_crud_count_operation() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_count_with_operation_mssql() { assert_eq!( - League::find_all_with(SQL_SERVER_DS).await.unwrap().len() as i64, + League::find_all_with::(SQL_SERVER_DS) + .await + .unwrap() + .len() as i64, League::count_with(SQL_SERVER_DS).await.unwrap() ); } @@ -169,7 +173,10 @@ fn test_crud_count_with_operation_mssql() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_count_with_operation_mysql() { assert_eq!( - League::find_all_with(MYSQL_DS).await.unwrap().len() as i64, + League::find_all_with::(MYSQL_DS) + .await + .unwrap() + .len() as i64, League::count_with(MYSQL_DS).await.unwrap() ); } diff --git a/tests/crud/update_operations.rs b/tests/crud/update_operations.rs index 592468d5..2c126458 100644 --- a/tests/crud/update_operations.rs +++ b/tests/crud/update_operations.rs @@ -1,142 +1,142 @@ -use crate::tests_models::league::*; -// Integration tests for the CRUD operations available in `Canyon` that -/// generates and executes *UPDATE* statements -use canyon_sql::crud::CrudOperations; - -#[cfg(feature = "mysql")] -use crate::constants::MYSQL_DS; -#[cfg(feature = "mssql")] -use crate::constants::SQL_SERVER_DS; - -/// Update operation is a *CRUD* method defined for some entity `T`, that works by appliying -/// some change to a Rust's entity instance, and persisting them into the database. -/// -/// The `t.update(&self)` operation is only enabled for types that -/// has, at least, one of it's fields annotated with a `#[primary_key]` -/// operation, because we use that concrete field to construct the clause that targets -/// that entity. -/// -/// Attempt of usage the `t.update(&self)` method on an entity without `#[primary_key]` -/// will raise a runtime error. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_update_method_operation() { - // We first retrieve some entity from the database. Note that we must make - // the retrieved instance mutable of clone it to a new mutable resource - let mut updt_candidate: League = League::find_by_pk(&1) - .await - .expect("[1] - Failed the query to the database") - .expect("[1] - No entity found for the primary key value passed in"); - - // The ext_id field value is extracted from the sql scripts under the - // docker/sql folder. We are retrieving the first entity inserted at the - // wake-up time of the database, and now checking some of its properties. - assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); - - // Modify the value, and perform the update - let updt_value: i64 = 593064_i64; - updt_candidate.ext_id = updt_value; - updt_candidate - .update() - .await - .expect("Failed the update operation"); - - // Retrieve it again, and check if the value was really updated - let updt_entity: League = League::find_by_pk(&1) - .await - .expect("[2] - Failed the query to the database") - .expect("[2] - No entity found for the primary key value passed in"); - - assert_eq!(updt_entity.ext_id, updt_value); - - // We roll back the changes to the initial value to don't broke other tests - // the next time that will run - updt_candidate.ext_id = 100695891328981122_i64; - updt_candidate - .update() - .await - .expect("Failed to restore the initial value in the psql update operation"); -} - -/// Same as the above test, but with the specified datasource. -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_update_with_mssql_method_operation() { - // We first retrieve some entity from the database. Note that we must make - // the retrieved instance mutable of clone it to a new mutable resource - let mut updt_candidate: League = League::find_by_pk_with(&1, SQL_SERVER_DS) - .await - .expect("[1] - Failed the query to the database") - .expect("[1] - No entity found for the primary key value passed in"); - - // The ext_id field value is extracted from the sql scripts under the - // docker/sql folder. We are retrieving the first entity inserted at the - // wake-up time of the database, and now checking some of its properties. - assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); - - // Modify the value, and perform the update - let updt_value: i64 = 59306442534_i64; - updt_candidate.ext_id = updt_value; - updt_candidate - .update_with(SQL_SERVER_DS) - .await - .expect("Failed the update operation"); - - // Retrieve it again, and check if the value was really updated - let updt_entity: League = League::find_by_pk_with(&1, SQL_SERVER_DS) - .await - .expect("[2] - Failed the query to the database") - .expect("[2] - No entity found for the primary key value passed in"); - - assert_eq!(updt_entity.ext_id, updt_value); - - // We roll back the changes to the initial value to don't broke other tests - // the next time that will run - updt_candidate.ext_id = 100695891328981122_i64; - updt_candidate - .update_with(SQL_SERVER_DS) - .await - .expect("Failed to restablish the initial value update operation"); -} - -/// Same as the above test, but with the specified datasource. -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_update_with_mysql_method_operation() { - // We first retrieve some entity from the database. Note that we must make - // the retrieved instance mutable of clone it to a new mutable resource - - let mut updt_candidate: League = League::find_by_pk_with(&1, MYSQL_DS) - .await - .expect("[1] - Failed the query to the database") - .expect("[1] - No entity found for the primary key value passed in"); - - // The ext_id field value is extracted from the sql scripts under the - // docker/sql folder. We are retrieving the first entity inserted at the - // wake up time of the database, and now checking some of its properties. - assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); - - // Modify the value, and perform the update - let updt_value: i64 = 59306442534_i64; - updt_candidate.ext_id = updt_value; - updt_candidate - .update_with(MYSQL_DS) - .await - .expect("Failed the update operation"); - - // Retrieve it again, and check if the value was really updated - let updt_entity: League = League::find_by_pk_with(&1, MYSQL_DS) - .await - .expect("[2] - Failed the query to the database") - .expect("[2] - No entity found for the primary key value passed in"); - - assert_eq!(updt_entity.ext_id, updt_value); - - // We rollback the changes to the initial value to don't broke other tests - // the next time that will run - updt_candidate.ext_id = 100695891328981122_i64; - updt_candidate - .update_with(MYSQL_DS) - .await - .expect("Failed to restablish the initial value update operation"); -} +// use crate::tests_models::league::*; +// // Integration tests for the CRUD operations available in `Canyon` that +// /// generates and executes *UPDATE* statements +// use canyon_sql::crud::CrudOperations; +// +// #[cfg(feature = "mysql")] +// use crate::constants::MYSQL_DS; +// #[cfg(feature = "mssql")] +// use crate::constants::SQL_SERVER_DS; +// +// /// Update operation is a *CRUD* method defined for some entity `T`, that works by appliying +// /// some change to a Rust's entity instance, and persisting them into the database. +// /// +// /// The `t.update(&self)` operation is only enabled for types that +// /// has, at least, one of it's fields annotated with a `#[primary_key]` +// /// operation, because we use that concrete field to construct the clause that targets +// /// that entity. +// /// +// /// Attempt of usage the `t.update(&self)` method on an entity without `#[primary_key]` +// /// will raise a runtime error. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_update_method_operation() { +// // We first retrieve some entity from the database. Note that we must make +// // the retrieved instance mutable of clone it to a new mutable resource +// let mut updt_candidate: League = League::find_by_pk(&1) +// .await +// .expect("[1] - Failed the query to the database") +// .expect("[1] - No entity found for the primary key value passed in"); +// +// // The ext_id field value is extracted from the sql scripts under the +// // docker/sql folder. We are retrieving the first entity inserted at the +// // wake-up time of the database, and now checking some of its properties. +// assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); +// +// // Modify the value, and perform the update +// let updt_value: i64 = 593064_i64; +// updt_candidate.ext_id = updt_value; +// updt_candidate +// .update() +// .await +// .expect("Failed the update operation"); +// +// // Retrieve it again, and check if the value was really updated +// let updt_entity: League = League::find_by_pk(&1) +// .await +// .expect("[2] - Failed the query to the database") +// .expect("[2] - No entity found for the primary key value passed in"); +// +// assert_eq!(updt_entity.ext_id, updt_value); +// +// // We roll back the changes to the initial value to don't broke other tests +// // the next time that will run +// updt_candidate.ext_id = 100695891328981122_i64; +// updt_candidate +// .update() +// .await +// .expect("Failed to restore the initial value in the psql update operation"); +// } +// +// /// Same as the above test, but with the specified datasource. +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_update_with_mssql_method_operation() { +// // We first retrieve some entity from the database. Note that we must make +// // the retrieved instance mutable of clone it to a new mutable resource +// let mut updt_candidate: League = League::find_by_pk_with(&1, SQL_SERVER_DS) +// .await +// .expect("[1] - Failed the query to the database") +// .expect("[1] - No entity found for the primary key value passed in"); +// +// // The ext_id field value is extracted from the sql scripts under the +// // docker/sql folder. We are retrieving the first entity inserted at the +// // wake-up time of the database, and now checking some of its properties. +// assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); +// +// // Modify the value, and perform the update +// let updt_value: i64 = 59306442534_i64; +// updt_candidate.ext_id = updt_value; +// updt_candidate +// .update_with(SQL_SERVER_DS) +// .await +// .expect("Failed the update operation"); +// +// // Retrieve it again, and check if the value was really updated +// let updt_entity: League = League::find_by_pk_with(&1, SQL_SERVER_DS) +// .await +// .expect("[2] - Failed the query to the database") +// .expect("[2] - No entity found for the primary key value passed in"); +// +// assert_eq!(updt_entity.ext_id, updt_value); +// +// // We roll back the changes to the initial value to don't broke other tests +// // the next time that will run +// updt_candidate.ext_id = 100695891328981122_i64; +// updt_candidate +// .update_with(SQL_SERVER_DS) +// .await +// .expect("Failed to restablish the initial value update operation"); +// } +// +// /// Same as the above test, but with the specified datasource. +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_update_with_mysql_method_operation() { +// // We first retrieve some entity from the database. Note that we must make +// // the retrieved instance mutable of clone it to a new mutable resource +// +// let mut updt_candidate: League = League::find_by_pk_with(&1, MYSQL_DS) +// .await +// .expect("[1] - Failed the query to the database") +// .expect("[1] - No entity found for the primary key value passed in"); +// +// // The ext_id field value is extracted from the sql scripts under the +// // docker/sql folder. We are retrieving the first entity inserted at the +// // wake up time of the database, and now checking some of its properties. +// assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); +// +// // Modify the value, and perform the update +// let updt_value: i64 = 59306442534_i64; +// updt_candidate.ext_id = updt_value; +// updt_candidate +// .update_with(MYSQL_DS) +// .await +// .expect("Failed the update operation"); +// +// // Retrieve it again, and check if the value was really updated +// let updt_entity: League = League::find_by_pk_with(&1, MYSQL_DS) +// .await +// .expect("[2] - Failed the query to the database") +// .expect("[2] - No entity found for the primary key value passed in"); +// +// assert_eq!(updt_entity.ext_id, updt_value); +// +// // We rollback the changes to the initial value to don't broke other tests +// // the next time that will run +// updt_candidate.ext_id = 100695891328981122_i64; +// updt_candidate +// .update_with(MYSQL_DS) +// .await +// .expect("Failed to restablish the initial value update operation"); +// } From 5fb67f018b6fd0a26cb807382046d594cbeab867 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 15 Apr 2025 20:03:53 +0200 Subject: [PATCH 084/155] feat(wip): TLS issues with Tiberius since Rust 1.86 --- canyon_core/src/connection/db_connector.rs | 14 ++- canyon_core/src/connection/mod.rs | 15 ++- canyon_core/src/rows.rs | 109 ++++++++++-------- canyon_crud/src/crud.rs | 6 +- canyon_macros/src/query_operations/read.rs | 26 ++--- canyon_migrations/src/migrations/processor.rs | 9 +- tests/canyon.toml | 42 +++---- tests/crud/init_mssql.rs | 107 +++++++++-------- tests/crud/read_operations.rs | 41 +------ 9 files changed, 178 insertions(+), 191 deletions(-) diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index d59eafff..ddfa82c4 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -369,7 +369,6 @@ impl DatabaseConnection { mod connection_helpers { use super::*; - use tokio_postgres::NoTls; #[cfg(feature = "postgres")] pub async fn create_postgres_connection( @@ -378,7 +377,7 @@ mod connection_helpers { let (user, password) = auth::extract_postgres_auth(&datasource.auth)?; let url = connection_string(user, password, datasource); - let (client, connection) = tokio_postgres::connect(&url, NoTls).await?; + let (client, connection) = tokio_postgres::connect(&url, tokio_postgres::NoTls).await?; tokio::spawn(async move { if let Err(e) = connection.await { @@ -398,7 +397,6 @@ mod connection_helpers { datasource: &DatasourceConfig, ) -> Result> { use async_std::net::TcpStream; - let mut tiberius_config = tiberius::Config::new(); tiberius_config.host(&datasource.properties.host); @@ -408,10 +406,13 @@ mod connection_helpers { let auth_config = auth::extract_mssql_auth(&datasource.auth)?; tiberius_config.authentication(auth_config); tiberius_config.trust_cert(); // TODO: this should be specifically set via user input - + tiberius_config.encryption(tiberius::EncryptionLevel::NotSupported); // TODO: user input + // TODO: in MacOS 15, this is the actual workaround. We need to investigate further + // https://github.com/prisma/tiberius/issues/364 + let tcp = TcpStream::connect(tiberius_config.get_addr()).await?; tcp.set_nodelay(true)?; - + let client = tiberius::Client::connect(tiberius_config, tcp).await?; Ok(DatabaseConnection::SqlServer(SqlServerConnection { @@ -442,7 +443,8 @@ mod connection_helpers { #[cfg(feature = "mysql")] DatabaseType::MySQL => "mysql", #[cfg(feature = "mssql")] - DatabaseType::SqlServer => todo!("Connection string for MSSQL should never be reached"), + DatabaseType::SqlServer => "" + // # todo!("Connection string for MSSQL should never be reached"), }; format!( "{server}://{user}:{pswd}@{host}:{port}/{db}", diff --git a/canyon_core/src/connection/mod.rs b/canyon_core/src/connection/mod.rs index 9cae4f6b..352864b1 100644 --- a/canyon_core/src/connection/mod.rs +++ b/canyon_core/src/connection/mod.rs @@ -81,16 +81,15 @@ fn find_canyon_config_file() -> PathBuf { /// job done. pub async fn init_connections_cache() { for datasource in DATASOURCES.iter() { + let db_conn = DatabaseConnection::new(datasource).await; + + if let Err(e) = db_conn { + panic!("Error opening database connection for {}. Err: {}", datasource.name, e); + } + CACHED_DATABASE_CONN.lock().await.insert( &datasource.name, - DatabaseConnection::new(datasource) - .await - .unwrap_or_else(|_| { - panic!( - "Error pooling a new connection for the datasource: {:?}", - datasource.name - ) - }), + DatabaseConnection::new(datasource).await.unwrap(), ); } } diff --git a/canyon_core/src/rows.rs b/canyon_core/src/rows.rs index c449b86e..d37da35b 100644 --- a/canyon_core/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -10,7 +10,6 @@ use crate::row::Row; use std::error::Error; use cfg_if::cfg_if; -use mysql_common::prelude::FromRow; // Helper macro to conditionally add trait bounds // these are the hacky intermediate traits @@ -22,7 +21,9 @@ cfg_if! { // } else if #[cfg(feature = "mysql")] { // trait FromSql<'a, T> where T: mysql_async::types::FromSql<'a> { } // } - if #[cfg(feature = "postgres")] { + + // } else if #[cfg(all(feature = "postgres", feature = "mysql", feature = "mssql"))] { + if #[cfg(all(feature = "postgres", feature = "mysql", feature = "mssql"))] { pub trait FromSql<'a, T>: tokio_postgres::types::FromSql<'a> + tiberius::FromSql<'a> + mysql_async::prelude::FromValue {} @@ -40,6 +41,20 @@ cfg_if! { + tiberius::FromSqlOwned + mysql_async::prelude::FromValue {} + } else if #[cfg(feature = "postgres")] { + pub trait FromSql<'a, T>: tokio_postgres::types::FromSql<'a> {} + impl<'a, T> FromSql<'a, T> for T where T: + tokio_postgres::types::FromSql<'a> {} + + pub trait FromSqlOwnedValue: tokio_postgres::types::FromSqlOwned {} + impl FromSqlOwnedValue for T where T: + tokio_postgres::types::FromSqlOwned {} + } else if #[cfg(feature = "mssql")] { + pub trait FromSql<'a, T>: tiberius::FromSqlOwned {} + impl<'a, T> FromSql<'a, T> for T where T: tiberius::FromSqlOwned {} + + pub trait FromSqlOwnedValue: tiberius::FromSqlOwned {} + impl FromSqlOwnedValue for T where T: tiberius::FromSqlOwned {} } // TODO: missing combinations else } @@ -57,7 +72,7 @@ pub enum CanyonRows { #[cfg(feature = "mssql")] Tiberius(Vec), #[cfg(feature = "mysql")] - MySQL(Vec), + MySQL(Vec) } impl IntoResults for Result { @@ -136,50 +151,50 @@ impl CanyonRows { } } - pub fn get_column_at_row<'a, C: FromSql<'a, C>>( - &'a self, - column_name: &str, - index: usize, - ) -> Result> { - let row_extraction_failure = || { - format!( - "{:?} - Failure getting the row: {} at index: {}", - self, column_name, index - ) - }; - - match self { - #[cfg(feature = "postgres")] - Self::Postgres(v) => Ok(v - .get(index) - .ok_or_else(row_extraction_failure)? - .get::<&str, C>(column_name)), - #[cfg(feature = "mssql")] - Self::Tiberius(ref v) => v - .get(index) - .ok_or_else(row_extraction_failure)? - .get::(column_name) - .ok_or_else(|| { - format!( - "{:?} - Failure getting the row: {} at index: {}", - self, column_name, index - ) - .into() - }), - #[cfg(feature = "mysql")] - Self::MySQL(ref v) => v - .get(index) - .ok_or_else(row_extraction_failure)? - .get::(0) - .ok_or_else(|| { - format!( - "{:?} - Failure getting the row: {} at index: {}", - self, column_name, index - ) - .into() - }), - } - } + // pub fn get_column_at_row<'a, C: FromSql<'a, C>>( + // &'a self, + // column_name: &str, + // index: usize, + // ) -> Result> { + // let row_extraction_failure = || { + // format!( + // "{:?} - Failure getting the row: {} at index: {}", + // self, column_name, index + // ) + // }; + // + // match self { + // #[cfg(feature = "postgres")] + // Self::Postgres(v) => Ok(v + // .get(index) + // .ok_or_else(row_extraction_failure)? + // .get::<&str, C>(column_name)), + // #[cfg(feature = "mssql")] + // Self::Tiberius(ref v) => v + // .get(index) + // .ok_or_else(row_extraction_failure)? + // .get::(column_name) + // .ok_or_else(|| { + // format!( + // "{:?} - Failure getting the row: {} at index: {}", + // self, column_name, index + // ) + // .into() + // }), + // #[cfg(feature = "mysql")] + // Self::MySQL(ref v) => v + // .get(index) + // .ok_or_else(row_extraction_failure)? + // .get::(0) + // .ok_or_else(|| { + // format!( + // "{:?} - Failure getting the row: {} at index: {}", + // self, column_name, index + // ) + // .into() + // }), + // } + // } /// Returns the number of elements present on the wrapped collection pub fn len(&self) -> usize { diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 044266d8..34a9452a 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -52,13 +52,13 @@ pub trait CrudOperations: Send + Sync { // // fn select_query_with<'a, R: RowMapper>(database_type: DatabaseType) -> SelectQueryBuilder<'a, R>; - fn count() -> impl Future>> + Send; - + /*fn count() -> impl Future>> + Send; + fn count_with<'a, I>( input: I, ) -> impl Future>> + Send where - I: DbConnection + Send + 'a; + I: DbConnection + Send + 'a;*/ // fn find_by_pk<'a, R: RowMapper>( // value: &'a dyn QueryParameter<'a>, diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index b0626f64..4b176367 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -12,8 +12,7 @@ pub fn generate_read_operations_tokens( let ty = macro_data.ty; let fa_stmt = format!("SELECT * FROM {table_schema_data}"); - // TODO: bring the helper and convert the SELECT * into the - // SELECT col_name, col_name2...? + // TODO: bring the helper and convert the SELECT * into the SELECT col_name, col_name2...? // TODO: remember that this queries statements must be autogenerated by some automatic procedure let find_all = create_find_all_macro(ty, &fa_stmt); @@ -21,9 +20,9 @@ pub fn generate_read_operations_tokens( let find_all_unchecked = create_find_all_unchecked_macro(ty, &fa_stmt); let find_all_unchecked_with = create_find_all_unchecked_with_macro(ty, &fa_stmt); - let count_stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); - let count = create_count_macro(ty, &count_stmt); - let count_with = create_count_with_macro(ty, &count_stmt); + // let count_stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); + // let count = create_count_macro(ty, &count_stmt); + // let count_with = create_count_with_macro(ty, &count_stmt); let find_by_pk_complex_tokens = generate_find_by_pk_tokens(macro_data, table_schema_data); @@ -35,8 +34,8 @@ pub fn generate_read_operations_tokens( #find_all_unchecked #find_all_unchecked_with - #count - #count_with + // #count + // #count_with // #find_by_pk_complex_tokens @@ -318,7 +317,7 @@ mod macro_builder_read_ops_tests { &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), SELECT_ALL_STMT, ); - let find_all = find_all_builder.generate_tokens().to_string(); + let find_all = find_all_builder.to_string(); assert!(find_all.contains("async fn find_all")); assert!(find_all.contains(RES_RET_TY)); @@ -327,10 +326,9 @@ mod macro_builder_read_ops_tests { #[test] fn test_macro_builder_find_all_with() { let find_all_builder = create_find_all_with_macro( - &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), SELECT_ALL_STMT, ); - let find_all_with = find_all_builder.generate_tokens().to_string(); + let find_all_with = find_all_builder.to_string(); assert!(find_all_with.contains("async fn find_all_with")); assert!(find_all_with.contains(RES_RET_TY_LT)); @@ -344,7 +342,7 @@ mod macro_builder_read_ops_tests { &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), SELECT_ALL_STMT, ); - let find_all_unc = find_all_unc_builder.generate_tokens().to_string(); + let find_all_unc = find_all_unc_builder.to_string(); assert!(find_all_unc.contains("async fn find_all_unchecked")); assert!(find_all_unc.contains(RAW_RET_TY)); @@ -356,7 +354,7 @@ mod macro_builder_read_ops_tests { &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), SELECT_ALL_STMT, ); - let find_all_unc_with = find_all_unc_with_builder.generate_tokens().to_string(); + let find_all_unc_with = find_all_unc_with_builder.to_string(); assert!(find_all_unc_with.contains("async fn find_all_unchecked_with")); assert!(find_all_unc_with.contains(RAW_RET_TY)); @@ -370,7 +368,7 @@ mod macro_builder_read_ops_tests { &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), COUNT_STMT, ); - let count = count_builder.generate_tokens().to_string(); + let count = count_builder.to_string(); assert!(count.contains("async fn count")); assert!(count.contains("Result < i64")); @@ -382,7 +380,7 @@ mod macro_builder_read_ops_tests { &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), COUNT_STMT, ); - let count_with = count_with_builder.generate_tokens().to_string(); + let count_with = count_with_builder.to_string(); assert!(count_with.contains("async fn count_with")); assert!(count_with.contains("Result < i64")); diff --git a/canyon_migrations/src/migrations/processor.rs b/canyon_migrations/src/migrations/processor.rs index ff74b564..d6266204 100644 --- a/canyon_migrations/src/migrations/processor.rs +++ b/canyon_migrations/src/migrations/processor.rs @@ -30,6 +30,7 @@ pub struct MigrationsProcessor { drop_primary_key_operations: Vec, constraints_table_operations: Vec, constraints_column_operations: Vec, + #[cfg(feature = "postgres")] constraints_sequence_operations: Vec, } impl Transaction for MigrationsProcessor {} @@ -128,8 +129,12 @@ impl MigrationsProcessor { for operation in &self.constraints_column_operations { operation.generate_sql(datasource).await; // This should be moved again to runtime } - for operation in &self.constraints_sequence_operations { - operation.generate_sql(datasource).await; // This should be moved again to runtime + + #[cfg(feature = "postgres")] + { + for operation in &self.constraints_sequence_operations { + operation.generate_sql(datasource).await; // This should be moved again to runtime + } } // TODO Still pending to decouple de executions of cargo check to skip the process if this // code is not processed by cargo build or cargo run diff --git a/tests/canyon.toml b/tests/canyon.toml index 73c0b023..bd882f75 100644 --- a/tests/canyon.toml +++ b/tests/canyon.toml @@ -1,15 +1,15 @@ [canyon_sql] -[[canyon_sql.datasources]] -name = 'postgres_docker' - -[canyon_sql.datasources.auth] -postgresql = { basic = { username = 'postgres', password = 'postgres'}} - -[canyon_sql.datasources.properties] -host = 'localhost' -port = 5438 -db_name = 'postgres' +#[[canyon_sql.datasources]] +#name = 'postgres_docker' +# +#[canyon_sql.datasources.auth] +#postgresql = { basic = { username = 'postgres', password = 'postgres'}} +# +#[canyon_sql.datasources.properties] +#host = 'localhost' +#port = 5438 +#db_name = 'postgres' [[canyon_sql.datasources]] @@ -23,14 +23,14 @@ host = 'localhost' port = 1434 db_name = 'master' - -[[canyon_sql.datasources]] -name = 'mysql_docker' - -[canyon_sql.datasources.auth] -mysql = { basic = { username = 'root', password = 'root' } } - -[canyon_sql.datasources.properties] -host = 'localhost' -port = 3307 -db_name = 'public' \ No newline at end of file +# +#[[canyon_sql.datasources]] +#name = 'mysql_docker' +# +#[canyon_sql.datasources.auth] +#mysql = { basic = { username = 'root', password = 'root' } } +# +#[canyon_sql.datasources.properties] +#host = 'localhost' +#port = 3307 +#db_name = 'public' \ No newline at end of file diff --git a/tests/crud/init_mssql.rs b/tests/crud/init_mssql.rs index 8c76da9c..29153417 100644 --- a/tests/crud/init_mssql.rs +++ b/tests/crud/init_mssql.rs @@ -1,13 +1,14 @@ -// use crate::constants::SQL_SERVER_CREATE_TABLES; -// use crate::constants::SQL_SERVER_DS; -// use crate::constants::SQL_SERVER_FILL_TABLE_VALUES; -// use crate::tests_models::league::League; -// -// use canyon_sql::crud::CrudOperations; -// use canyon_sql::db_clients::tiberius::{Client, Config}; -// use canyon_sql::runtime::tokio::net::TcpStream; -// use canyon_sql::runtime::tokio_util::compat::TokioAsyncWriteCompatExt; -// +use tiberius::EncryptionLevel; +use crate::constants::SQL_SERVER_CREATE_TABLES; +use crate::constants::SQL_SERVER_DS; +use crate::constants::SQL_SERVER_FILL_TABLE_VALUES; +use crate::tests_models::league::League; + +use canyon_sql::crud::CrudOperations; +use canyon_sql::db_clients::tiberius::{Client, Config}; +use canyon_sql::runtime::tokio::net::TcpStream; +use canyon_sql::runtime::tokio_util::compat::TokioAsyncWriteCompatExt; + // /// In order to initialize data on `SqlServer`. we must manually insert it // /// when the docker starts. SqlServer official docker from Microsoft does // /// not allow you to run `.sql` files against the database (not at least, without) @@ -19,44 +20,48 @@ // /// This will be marked as `#[ignore]`, so we can force to run first the marked as // /// ignored, check the data available, perform the necessary init operations and // /// then *cargo test * the real integration tests -// #[canyon_sql::macros::canyon_tokio_test] -// #[ignore] -// fn initialize_sql_server_docker_instance() { -// static CONN_STR: &str = // TODO: change this for the DS when will be in the public API -// "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true"; -// -// canyon_sql::runtime::futures::executor::block_on(async { -// let config = Config::from_ado_string(CONN_STR).unwrap(); -// -// let tcp = TcpStream::connect(config.get_addr()).await.unwrap(); -// let tcp2 = TcpStream::connect(config.get_addr()).await.unwrap(); -// tcp.set_nodelay(true).ok(); -// -// let mut client = Client::connect(config.clone(), tcp.compat_write()) -// .await -// .unwrap(); -// -// // Create the tables -// let query_result = client.query(SQL_SERVER_CREATE_TABLES, &[]).await; -// assert!(query_result.is_ok()); -// -// let leagues_sql = League::find_all_with(SQL_SERVER_DS).await; -// println!("LSqlServer: {leagues_sql:?}"); -// assert!(leagues_sql.is_ok()); -// -// match leagues_sql { -// Ok(ref leagues) => { -// let leagues_len = leagues.len(); -// println!("Leagues already inserted on SQLSERVER: {:?}", &leagues_len); -// if leagues.len() < 10 { -// let mut client2 = Client::connect(config, tcp2.compat_write()) -// .await -// .expect("Can't connect to MSSQL"); -// let result = client2.query(SQL_SERVER_FILL_TABLE_VALUES, &[]).await; -// assert!(result.is_ok()); -// } -// } -// Err(e) => eprintln!("Error retrieving the leagues: {e}"), -// } -// }); -// } +#[canyon_sql::macros::canyon_tokio_test] +#[ignore] +fn initialize_sql_server_docker_instance() { + static CONN_STR: &str = // TODO: change this for the DS when will be in the public API + "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true;Encrypt=true"; + + canyon_sql::runtime::futures::executor::block_on(async { + let mut config = Config::from_ado_string(CONN_STR) + .expect("could not parse ado string"); + + config.encryption(EncryptionLevel::NotSupported); + let tcp = TcpStream::connect(config.get_addr()).await + .expect("could not connect to stream 1"); + let tcp2 = TcpStream::connect(config.get_addr()).await + .expect("could not connect to stream 2"); + tcp.set_nodelay(true).ok(); + + let mut client = Client::connect(config.clone(), tcp.compat_write()) + .await + .unwrap(); + + // Create the tables + let query_result = client.query(SQL_SERVER_CREATE_TABLES, &[]).await; + assert!(query_result.is_ok()); + + let leagues_sql = League::find_all_with::(SQL_SERVER_DS).await; + println!("LSqlServer: {leagues_sql:?}"); + assert!(leagues_sql.is_ok()); + + match leagues_sql { + Ok(ref leagues) => { + let leagues_len = leagues.len(); + println!("Leagues already inserted on SQLSERVER: {:?}", &leagues_len); + if leagues.len() < 10 { + let mut client2 = Client::connect(config, tcp2.compat_write()) + .await + .expect("Can't connect to MSSQL"); + let result = client2.query(SQL_SERVER_FILL_TABLE_VALUES, &[]).await; + assert!(result.is_ok()); + } + } + Err(e) => eprintln!("Error retrieving the leagues: {e}"), + } + }); +} diff --git a/tests/crud/read_operations.rs b/tests/crud/read_operations.rs index 8172b004..fbfffd4d 100644 --- a/tests/crud/read_operations.rs +++ b/tests/crud/read_operations.rs @@ -50,7 +50,7 @@ fn test_crud_find_all_with_mssql() { let find_all_result: Result, Box> = League::find_all_with(SQL_SERVER_DS).await; // Connection doesn't return an error - assert!(!find_all_result.is_err()); + assert!(!find_all_result.is_err(), "{:?}", find_all_result); assert!(!find_all_result.unwrap().is_empty()); } @@ -142,41 +142,4 @@ fn test_crud_find_all_unchecked_with() { // // "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" // // ); // // } - -/// Counts how many rows contains an entity on the target database. -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_count_operation() { - assert_eq!( - League::find_all::().await.unwrap().len() as i64, - League::count().await.unwrap() - ); -} - -/// Counts how many rows contains an entity on the target database using -/// the specified datasource mssql -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_count_with_operation_mssql() { - assert_eq!( - League::find_all_with::(SQL_SERVER_DS) - .await - .unwrap() - .len() as i64, - League::count_with(SQL_SERVER_DS).await.unwrap() - ); -} - -/// Counts how many rows contains an entity on the target database using -/// the specified datasource mysql -#[cfg(feature = "mysql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_count_with_operation_mysql() { - assert_eq!( - League::find_all_with::(MYSQL_DS) - .await - .unwrap() - .len() as i64, - League::count_with(MYSQL_DS).await.unwrap() - ); -} + \ No newline at end of file From a7c316080162587ce05ad7fb578199704bbb21e2 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 16 Apr 2025 17:42:13 +0200 Subject: [PATCH 085/155] feat(wip): proxy info via derive proc matro attr --- canyon_core/src/mapper.rs | 12 ++ canyon_crud/src/crud.rs | 33 ++-- canyon_macros/src/lib.rs | 2 +- canyon_macros/src/query_operations/mod.rs | 6 +- canyon_macros/src/query_operations/read.rs | 196 +++++++++++---------- canyon_macros/src/utils/macro_tokens.rs | 29 ++- tests/canyon.toml | 32 ++-- tests/crud/init_mssql.rs | 133 +++++++------- tests/crud/read_operations.rs | 58 ++++-- tests/tests_models/league.rs | 3 +- 10 files changed, 279 insertions(+), 225 deletions(-) diff --git a/canyon_core/src/mapper.rs b/canyon_core/src/mapper.rs index 1261a197..925e7522 100644 --- a/canyon_core/src/mapper.rs +++ b/canyon_core/src/mapper.rs @@ -12,6 +12,18 @@ pub trait RowMapper: Sized { fn deserialize_mysql(row: &mysql_async::Row) -> Self::Output; } +pub trait DefaultRowMapper { + type Mapper: RowMapper; +} + +// Blanket impl to make `Mapper = Self` for any `T: RowMapper` +impl DefaultRowMapper for T +where + T: RowMapper, +{ + type Mapper = T; +} + pub type CanyonError = Box<(dyn std::error::Error + Send + Sync)>; // TODO: convert this into a // real error pub trait IntoResults { diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 34a9452a..ef36d003 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -23,30 +23,29 @@ use std::future::Future; /// See it's definition and docs to see the implementations. /// Also, you can find the written macro-code that performs the auto-mapping /// in the *canyon_sql_root::canyon_macros* crates, on the root of this project. -pub trait CrudOperations: Send + Sync { - fn find_all() -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send +pub trait CrudOperations: Send + Sync where R: RowMapper, - Vec: FromIterator<::Output>; + Vec: FromIterator<::Output> +{ + fn find_all() -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send; - fn find_all_with<'a, R, I>( + fn find_all_with<'a, I>( input: I, ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where - R: RowMapper, - I: DbConnection + Send + 'a, - Vec: FromIterator<::Output>; - - fn find_all_unchecked() -> impl Future> + Send - where - R: RowMapper, - Vec: FromIterator<::Output>; + I: DbConnection + Send + 'a; - fn find_all_unchecked_with<'a, R, I>(input: I) -> impl Future> + Send - where - I: DbConnection + Send + 'a, - R: RowMapper, - Vec: FromIterator<::Output>; + // fn find_all_unchecked() -> impl Future> + Send + // where + // R: RowMapper, + // Vec: FromIterator<::Output>; + // + // fn find_all_unchecked_with<'a, I>(input: I) -> impl Future> + Send + // where + // I: DbConnection + Send + 'a, + // R: RowMapper, + // Vec: FromIterator<::Output>; // fn select_query<'a, R: RowMapper>() -> SelectQueryBuilder<'a, R>; // diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index c41dbab1..29aca677 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -114,7 +114,7 @@ pub fn canyon_entity(meta: CompilerTokenStream, input: CompilerTokenStream) -> C /// Allows the implementors to auto-derive the `CrudOperations` trait, which defines the methods /// that will perform the database communication and the implementation of the queries for every /// type, as defined in the `CrudOperations` + `Transaction` traits. -#[proc_macro_derive(CanyonCrud)] +#[proc_macro_derive(CanyonCrud, attributes(canyon_crud))] pub fn crud_operations(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast: DeriveInput = syn::parse(input).expect("Error parsing `Canyon Entity for generate the CRUD methods"); diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index 1906c4e3..cdf237b3 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -22,7 +22,11 @@ pub fn impl_crud_operations_trait_for_struct( table_schema_data: String, ) -> proc_macro::TokenStream { let mut crud_ops_tokens = TokenStream::new(); + let ty = macro_data.ty; + let mapper_ty = macro_data + .retrieve_mapping_target_type() + .unwrap_or_else(|| ty.clone()); let read_operations_tokens = generate_read_operations_tokens(macro_data, &table_schema_data); let insert_tokens = generate_insert_tokens(macro_data, &table_schema_data); @@ -40,7 +44,7 @@ pub fn impl_crud_operations_trait_for_struct( use canyon_sql::core::IntoResults; use canyon_sql::core::RowMapper; - impl canyon_sql::crud::CrudOperations for #ty { + impl canyon_sql::crud::CrudOperations<#mapper_ty> for #ty { #crud_operations_tokens } diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 4b176367..bc7b4f1e 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -11,14 +11,18 @@ pub fn generate_read_operations_tokens( use __details::{count_generators::*, find_all_generators::*}; let ty = macro_data.ty; + let mapper_ty = macro_data + .retrieve_mapping_target_type() + .unwrap_or_else(|| ty.clone()); + let fa_stmt = format!("SELECT * FROM {table_schema_data}"); // TODO: bring the helper and convert the SELECT * into the SELECT col_name, col_name2...? // TODO: remember that this queries statements must be autogenerated by some automatic procedure - let find_all = create_find_all_macro(ty, &fa_stmt); - let find_all_with = create_find_all_with_macro(&fa_stmt); - let find_all_unchecked = create_find_all_unchecked_macro(ty, &fa_stmt); - let find_all_unchecked_with = create_find_all_unchecked_with_macro(ty, &fa_stmt); + let find_all = create_find_all_macro(ty, &mapper_ty, &fa_stmt); + let find_all_with = create_find_all_with_macro(&fa_stmt, &mapper_ty); + // let find_all_unchecked = create_find_all_unchecked_macro(ty, &fa_stmt); + // let find_all_unchecked_with = create_find_all_unchecked_with_macro(ty, &fa_stmt); // let count_stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); // let count = create_count_macro(ty, &count_stmt); @@ -31,8 +35,8 @@ pub fn generate_read_operations_tokens( quote! { #find_all #find_all_with - #find_all_unchecked - #find_all_unchecked_with + // #find_all_unchecked + // #find_all_unchecked_with // #count // #count_with @@ -146,12 +150,11 @@ mod __details { use super::*; use proc_macro2::TokenStream; - pub fn create_find_all_macro(ty: &syn::Ident, stmt: &str) -> TokenStream { + pub fn create_find_all_macro(ty: &syn::Ident, mapper_ty: &syn::Ident, stmt: &str) -> TokenStream { quote! { - async fn find_all() - -> Result, Box<(dyn std::error::Error + Sync + Send)>> - where R: RowMapper, Vec: FromIterator<::Output> { - <#ty as canyon_sql::core::Transaction>::query::<&str, R>( + async fn find_all() + -> Result, Box<(dyn std::error::Error + Sync + Send)>> { + <#ty as canyon_sql::core::Transaction>::query::<&str, #mapper_ty>( #stmt, &[], "" @@ -160,48 +163,46 @@ mod __details { } } - pub fn create_find_all_with_macro(stmt: &str) -> TokenStream { + pub fn create_find_all_with_macro(stmt: &str, mapper_ty: &syn::Ident) -> TokenStream { quote! { - async fn find_all_with<'a, R, I>(input: I) - -> Result, Box<(dyn std::error::Error + Sync + Send)>> + async fn find_all_with<'a, I>(input: I) + -> Result, Box<(dyn std::error::Error + Sync + Send)>> where - R: RowMapper, - I: canyon_sql::core::DbConnection + Send + 'a, - Vec: FromIterator<::Output> - { - input.query::<&str, R>(#stmt, &[]).await - } - } - } - - pub fn create_find_all_unchecked_macro(ty: &syn::Ident, stmt: &str) -> TokenStream { - let expect_msg = format!("Failed to execute find all query for: {:?}", ty.to_string()); - quote! { - async fn find_all_unchecked() -> Vec - where R: RowMapper, - Vec: FromIterator<::Output> - { - <#ty as canyon_sql::core::Transaction>::query(#stmt, &[], "") - .await - .expect(#expect_msg) - } - } - } - - pub fn create_find_all_unchecked_with_macro(ty: &syn::Ident, stmt: &str) -> TokenStream { - let expect_msg = format!("Failed to execute find all query for: {:?}", ty.to_string()); - quote! { - async fn find_all_unchecked_with<'a, R, I>(input: I) -> Vec - where - R: RowMapper, - I: canyon_sql::core::DbConnection + Send + 'a, - Vec: FromIterator<::Output> + I: canyon_sql::core::DbConnection + Send + 'a { - input.query(#stmt, &[]).await - .expect(#expect_msg) + input.query::<&str, #mapper_ty>(#stmt, &[]).await } } } + // + // pub fn create_find_all_unchecked_macro(ty: &syn::Ident, stmt: &str) -> TokenStream { + // let expect_msg = format!("Failed to execute find all query for: {:?}", ty.to_string()); + // quote! { + // async fn find_all_unchecked() -> Vec + // where R: RowMapper, + // Vec: FromIterator<::Output> + // { + // <#ty as canyon_sql::core::Transaction>::query(#stmt, &[], "") + // .await + // .expect(#expect_msg) + // } + // } + // } + // + // pub fn create_find_all_unchecked_with_macro(ty: &syn::Ident, stmt: &str) -> TokenStream { + // let expect_msg = format!("Failed to execute find all query for: {:?}", ty.to_string()); + // quote! { + // async fn find_all_unchecked_with<'a, R, I>(input: I) -> Vec + // where + // R: RowMapper, + // I: canyon_sql::core::DbConnection + Send + 'a, + // Vec: FromIterator<::Output> + // { + // input.query(#stmt, &[]).await + // .expect(#expect_msg) + // } + // } + // } } pub mod count_generators { @@ -311,56 +312,57 @@ mod macro_builder_read_ops_tests { const COUNT_STMT: &str = "SELECT COUNT(*) FROM public.user"; const FIND_BY_PK_STMT: &str = "SELECT * FROM public.user WHERE id = $1"; - #[test] - fn test_macro_builder_find_all() { - let find_all_builder = create_find_all_macro( - &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - SELECT_ALL_STMT, - ); - let find_all = find_all_builder.to_string(); - - assert!(find_all.contains("async fn find_all")); - assert!(find_all.contains(RES_RET_TY)); - } - - #[test] - fn test_macro_builder_find_all_with() { - let find_all_builder = create_find_all_with_macro( - SELECT_ALL_STMT, - ); - let find_all_with = find_all_builder.to_string(); - - assert!(find_all_with.contains("async fn find_all_with")); - assert!(find_all_with.contains(RES_RET_TY_LT)); - assert!(find_all_with.contains(LT_CONSTRAINT)); - assert!(find_all_with.contains(WITH_WHERE_BOUNDS)); - } - - #[test] - fn test_macro_builder_find_all_unchecked() { - let find_all_unc_builder = create_find_all_unchecked_macro( - &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - SELECT_ALL_STMT, - ); - let find_all_unc = find_all_unc_builder.to_string(); - - assert!(find_all_unc.contains("async fn find_all_unchecked")); - assert!(find_all_unc.contains(RAW_RET_TY)); - } - - #[test] - fn test_macro_builder_find_all_unchecked_with() { - let find_all_unc_with_builder = create_find_all_unchecked_with_macro( - &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - SELECT_ALL_STMT, - ); - let find_all_unc_with = find_all_unc_with_builder.to_string(); - - assert!(find_all_unc_with.contains("async fn find_all_unchecked_with")); - assert!(find_all_unc_with.contains(RAW_RET_TY)); - assert!(find_all_unc_with.contains(LT_CONSTRAINT)); - assert!(find_all_unc_with.contains(INPUT_PARAM)); - } + // #[test] + // fn test_macro_builder_find_all() { + // let find_all_builder = create_find_all_macro( + // &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + // SELECT_ALL_STMT, + // ); + // let find_all = find_all_builder.to_string(); + // + // assert!(find_all.contains("async fn find_all")); + // assert!(find_all.contains(RES_RET_TY)); + // } + // + // #[test] + // fn test_macro_builder_find_all_with() { + // let find_all_builder = create_find_all_with_macro( + // SELECT_ALL_STMT, + // ); + // let find_all_with = find_all_builder.to_string(); + // + // assert!(find_all_with.contains("async fn find_all_with")); + // assert!(find_all_with.contains(RES_RET_TY_LT)); + // assert!(find_all_with.contains(LT_CONSTRAINT)); + // assert!(find_all_with.contains(WITH_WHERE_BOUNDS)); + // } + // + // #[test] + // fn test_macro_builder_find_all_unchecked() { + // let find_all_unc_builder = create_find_all_unchecked_macro( + // &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + // SELECT_ALL_STMT, + // ); + // let find_all_unc = find_all_unc_builder.to_string(); + // + // assert!(find_all_unc.contains("async fn find_all_unchecked")); + // assert!(find_all_unc.contains(RAW_RET_TY)); + // } + // + // #[test] + // fn test_macro_builder_find_all_unchecked_with() { + // let find_all_unc_with_builder = create_find_all_unchecked_with_macro( + // &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + // &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + // SELECT_ALL_STMT, + // ); + // let find_all_unc_with = find_all_unc_with_builder.to_string(); + // + // assert!(find_all_unc_with.contains("async fn find_all_unchecked_with")); + // assert!(find_all_unc_with.contains(RAW_RET_TY)); + // assert!(find_all_unc_with.contains(LT_CONSTRAINT)); + // assert!(find_all_unc_with.contains(INPUT_PARAM)); + // } #[test] fn test_macro_builder_count() { diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index 2473b7dc..10916d84 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -1,8 +1,8 @@ use std::convert::TryFrom; use canyon_entities::field_annotation::EntityFieldAnnotation; -use proc_macro2::Ident; -use syn::{Attribute, DeriveInput, Fields, GenericParam, Generics, Type, TypeParam, Visibility}; +use proc_macro2::{Ident, Span}; +use syn::{Attribute, DeriveInput, Fields, Generics, Lit, Meta, MetaNameValue, NestedMeta, Type, Visibility}; /// Provides a convenient way of store the data for the TokenStream /// received on a macro @@ -29,11 +29,26 @@ impl<'a> MacroTokens<'a> { } } - // pub fn retrieve_row_mapper_implementor(&self) -> Ident { - // let caller = self.ty; - // let row_mapper_type_parameter = self.generics.type_params() - // caller.clone() - // } + pub fn retrieve_mapping_target_type(&self) -> Option { + for attr in self.attrs { + if attr.path.is_ident("canyon_crud") { + let Ok(Meta::List(meta_list)) = attr.parse_meta() else { + continue; + }; + + for nested in meta_list.nested.iter() { + if let NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit, .. })) = nested { + if path.is_ident("maps_to") { + if let Lit::Str(ref lit_str) = lit { + return Some(Ident::new(&lit_str.value(), Span::call_site())); + } + } + } + } + } + } + None + } /// Gives a Vec of tuples that contains the visibility, the name and /// the type of every field on a Struct diff --git a/tests/canyon.toml b/tests/canyon.toml index bd882f75..25c78f7f 100644 --- a/tests/canyon.toml +++ b/tests/canyon.toml @@ -1,27 +1,27 @@ [canyon_sql] -#[[canyon_sql.datasources]] -#name = 'postgres_docker' -# -#[canyon_sql.datasources.auth] -#postgresql = { basic = { username = 'postgres', password = 'postgres'}} -# -#[canyon_sql.datasources.properties] -#host = 'localhost' -#port = 5438 -#db_name = 'postgres' - - [[canyon_sql.datasources]] -name = 'sqlserver_docker' +name = 'postgres_docker' [canyon_sql.datasources.auth] -sqlserver = { basic = { username = 'sa', password = 'SqlServer-10' } } +postgresql = { basic = { username = 'postgres', password = 'postgres'}} [canyon_sql.datasources.properties] host = 'localhost' -port = 1434 -db_name = 'master' +port = 5438 +db_name = 'postgres' + + +#[[canyon_sql.datasources]] +#name = 'sqlserver_docker' +# +#[canyon_sql.datasources.auth] +#sqlserver = { basic = { username = 'sa', password = 'SqlServer-10' } } +# +#[canyon_sql.datasources.properties] +#host = 'localhost' +#port = 1434 +#db_name = 'master' # #[[canyon_sql.datasources]] diff --git a/tests/crud/init_mssql.rs b/tests/crud/init_mssql.rs index 29153417..56099f85 100644 --- a/tests/crud/init_mssql.rs +++ b/tests/crud/init_mssql.rs @@ -1,67 +1,66 @@ -use tiberius::EncryptionLevel; -use crate::constants::SQL_SERVER_CREATE_TABLES; -use crate::constants::SQL_SERVER_DS; -use crate::constants::SQL_SERVER_FILL_TABLE_VALUES; -use crate::tests_models::league::League; - -use canyon_sql::crud::CrudOperations; -use canyon_sql::db_clients::tiberius::{Client, Config}; -use canyon_sql::runtime::tokio::net::TcpStream; -use canyon_sql::runtime::tokio_util::compat::TokioAsyncWriteCompatExt; - -// /// In order to initialize data on `SqlServer`. we must manually insert it -// /// when the docker starts. SqlServer official docker from Microsoft does -// /// not allow you to run `.sql` files against the database (not at least, without) -// /// using a workaround. So, we are going to query the `SqlServer` to check if already -// /// has some data (other processes, persistence or multi-threading envs), af if not, -// /// we are going to retrieve the inserted data on the `postgreSQL` at start-up and -// /// inserting into the `SqlServer` instance. -// /// -// /// This will be marked as `#[ignore]`, so we can force to run first the marked as -// /// ignored, check the data available, perform the necessary init operations and -// /// then *cargo test * the real integration tests -#[canyon_sql::macros::canyon_tokio_test] -#[ignore] -fn initialize_sql_server_docker_instance() { - static CONN_STR: &str = // TODO: change this for the DS when will be in the public API - "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true;Encrypt=true"; - - canyon_sql::runtime::futures::executor::block_on(async { - let mut config = Config::from_ado_string(CONN_STR) - .expect("could not parse ado string"); - - config.encryption(EncryptionLevel::NotSupported); - let tcp = TcpStream::connect(config.get_addr()).await - .expect("could not connect to stream 1"); - let tcp2 = TcpStream::connect(config.get_addr()).await - .expect("could not connect to stream 2"); - tcp.set_nodelay(true).ok(); - - let mut client = Client::connect(config.clone(), tcp.compat_write()) - .await - .unwrap(); - - // Create the tables - let query_result = client.query(SQL_SERVER_CREATE_TABLES, &[]).await; - assert!(query_result.is_ok()); - - let leagues_sql = League::find_all_with::(SQL_SERVER_DS).await; - println!("LSqlServer: {leagues_sql:?}"); - assert!(leagues_sql.is_ok()); - - match leagues_sql { - Ok(ref leagues) => { - let leagues_len = leagues.len(); - println!("Leagues already inserted on SQLSERVER: {:?}", &leagues_len); - if leagues.len() < 10 { - let mut client2 = Client::connect(config, tcp2.compat_write()) - .await - .expect("Can't connect to MSSQL"); - let result = client2.query(SQL_SERVER_FILL_TABLE_VALUES, &[]).await; - assert!(result.is_ok()); - } - } - Err(e) => eprintln!("Error retrieving the leagues: {e}"), - } - }); -} +// use crate::constants::SQL_SERVER_CREATE_TABLES; +// use crate::constants::SQL_SERVER_DS; +// use crate::constants::SQL_SERVER_FILL_TABLE_VALUES; +// use crate::tests_models::league::League; +// +// use canyon_sql::crud::CrudOperations; +// use canyon_sql::db_clients::tiberius::{Client, Config, EncryptionLevel}; +// use canyon_sql::runtime::tokio::net::TcpStream; +// use canyon_sql::runtime::tokio_util::compat::TokioAsyncWriteCompatExt; +// +// // /// In order to initialize data on `SqlServer`. we must manually insert it +// // /// when the docker starts. SqlServer official docker from Microsoft does +// // /// not allow you to run `.sql` files against the database (not at least, without) +// // /// using a workaround. So, we are going to query the `SqlServer` to check if already +// // /// has some data (other processes, persistence or multi-threading envs), af if not, +// // /// we are going to retrieve the inserted data on the `postgreSQL` at start-up and +// // /// inserting into the `SqlServer` instance. +// // /// +// // /// This will be marked as `#[ignore]`, so we can force to run first the marked as +// // /// ignored, check the data available, perform the necessary init operations and +// // /// then *cargo test * the real integration tests +// #[canyon_sql::macros::canyon_tokio_test] +// #[ignore] +// fn initialize_sql_server_docker_instance() { +// static CONN_STR: &str = // TODO: change this for the DS when will be in the public API +// "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true;Encrypt=true"; +// +// canyon_sql::runtime::futures::executor::block_on(async { +// let mut config = Config::from_ado_string(CONN_STR) +// .expect("could not parse ado string"); +// +// config.encryption(EncryptionLevel::NotSupported); +// let tcp = TcpStream::connect(config.get_addr()).await +// .expect("could not connect to stream 1"); +// let tcp2 = TcpStream::connect(config.get_addr()).await +// .expect("could not connect to stream 2"); +// tcp.set_nodelay(true).ok(); +// +// let mut client = Client::connect(config.clone(), tcp.compat_write()) +// .await +// .unwrap(); +// +// // Create the tables +// let query_result = client.query(SQL_SERVER_CREATE_TABLES, &[]).await; +// assert!(query_result.is_ok()); +// +// let leagues_sql = League::find_all_with::(SQL_SERVER_DS).await; +// println!("LSqlServer: {leagues_sql:?}"); +// assert!(leagues_sql.is_ok()); +// +// match leagues_sql { +// Ok(ref leagues) => { +// let leagues_len = leagues.len(); +// println!("Leagues already inserted on SQLSERVER: {:?}", &leagues_len); +// if leagues.len() < 10 { +// let mut client2 = Client::connect(config, tcp2.compat_write()) +// .await +// .expect("Can't connect to MSSQL"); +// let result = client2.query(SQL_SERVER_FILL_TABLE_VALUES, &[]).await; +// assert!(result.is_ok()); +// } +// } +// Err(e) => eprintln!("Error retrieving the leagues: {e}"), +// } +// }); +// } diff --git a/tests/crud/read_operations.rs b/tests/crud/read_operations.rs index fbfffd4d..ef453153 100644 --- a/tests/crud/read_operations.rs +++ b/tests/crud/read_operations.rs @@ -32,15 +32,6 @@ fn test_crud_find_all() { assert!(!find_all_players.unwrap().is_empty()); } -/// Same as the `find_all()`, but with the unchecked variant, which directly returns `Vec` not -/// `Result` wrapped -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_all_unchecked() { - let find_all_result: Vec = League::find_all_unchecked().await; - assert!(!find_all_result.is_empty()); -} - /// Tests the behaviour of a SELECT * FROM {table_name} within Canyon, through the /// `::find_all()` associated function derived with the `CanyonCrud` derive proc-macro /// and using the specified datasource @@ -65,14 +56,6 @@ fn test_crud_find_all_with_mysql() { assert!(!find_all_result.unwrap().is_empty()); } -/// Same as the `find_all_with()`, but with the unchecked variant and the specified dataosource, -/// returning directly `Vec` and not `Result, Err>` -#[cfg(feature = "mssql")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_all_unchecked_with() { - let find_all_result: Vec = League::find_all_unchecked_with(SQL_SERVER_DS).await; - assert!(!find_all_result.is_empty()); -} // // /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is // // /// defined with the #[primary_key] attribute over some field of the type. @@ -142,4 +125,43 @@ fn test_crud_find_all_unchecked_with() { // // "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" // // ); // // } - \ No newline at end of file + +// /// Counts how many rows contains an entity on the target database. +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_count_operation() { +// assert_eq!( +// League::find_all::().await.unwrap().len() as i64, +// League::count().await.unwrap() +// ); +// } +// +// /// Counts how many rows contains an entity on the target database using +// /// the specified datasource mssql +// #[cfg(feature = "mssql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_count_with_operation_mssql() { +// assert_eq!( +// League::find_all_with::(SQL_SERVER_DS) +// .await +// .unwrap() +// .len() as i64, +// League::count_with(SQL_SERVER_DS).await.unwrap() +// ); +// } +// +// /// Counts how many rows contains an entity on the target database using +// /// the specified datasource mysql +// #[cfg(feature = "mysql")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_count_with_operation_mysql() { +// assert_eq!( +// League::find_all_with::(MYSQL_DS) +// .await +// .unwrap() +// .len() as i64, +// League::count_with(MYSQL_DS).await.unwrap() +// ); +// } +// +// diff --git a/tests/tests_models/league.rs b/tests/tests_models/league.rs index 3f3037e7..b0bc0013 100644 --- a/tests/tests_models/league.rs +++ b/tests/tests_models/league.rs @@ -1,6 +1,7 @@ use canyon_sql::macros::*; #[derive(Debug, Fields, CanyonCrud, CanyonMapper, ForeignKeyable, Eq, PartialEq)] +#[canyon_crud(maps_to = Leaguasde)] // #[canyon_entity(table_name = "league", schema = "public")] #[canyon_entity(table_name = "league")] pub struct League { @@ -11,4 +12,4 @@ pub struct League { name: String, region: String, image_url: String, -} +} \ No newline at end of file From 2e2f1b07ae44ade98960310146eeadca45a8d750 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 20 Apr 2025 15:22:43 +0200 Subject: [PATCH 086/155] fix: parsing the inner contents of the new canyon_crud annotation --- canyon_entities/Cargo.toml | 2 +- canyon_macros/Cargo.toml | 2 +- canyon_macros/src/utils/macro_tokens.rs | 60 ++++++++++++++++++------- canyon_migrations/Cargo.toml | 3 -- tests/tests_models/league.rs | 2 +- 5 files changed, 48 insertions(+), 21 deletions(-) diff --git a/canyon_entities/Cargo.toml b/canyon_entities/Cargo.toml index 374e2e98..376bc4d3 100644 --- a/canyon_entities/Cargo.toml +++ b/canyon_entities/Cargo.toml @@ -14,4 +14,4 @@ regex = { workspace = true } partialdebug = { workspace = true } quote = { workspace = true } proc-macro2 = { workspace = true } -syn = { version = "1.0.86", features = ["full", "parsing"] } # TODO Pending to refactor and upgrade +syn = { version = "1.0.109", features = ["full", "parsing"] } # TODO Pending to refactor and upgrade diff --git a/canyon_macros/Cargo.toml b/canyon_macros/Cargo.toml index 9f89caf0..8acd9f0e 100755 --- a/canyon_macros/Cargo.toml +++ b/canyon_macros/Cargo.toml @@ -13,7 +13,7 @@ description.workspace = true proc-macro = true [dependencies] -syn = { version = "1.0.109", features = ["full"] } # TODO Pending to upgrade and refactor +syn = { version = "1.0.109", features = ["full", "parsing"] } # TODO Pending to upgrade and refactor quote = { workspace = true } proc-macro2 = { workspace = true } futures = { workspace = true } diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index 10916d84..6ead79f7 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -1,8 +1,9 @@ use std::convert::TryFrom; use canyon_entities::field_annotation::EntityFieldAnnotation; -use proc_macro2::{Ident, Span}; -use syn::{Attribute, DeriveInput, Fields, Generics, Lit, Meta, MetaNameValue, NestedMeta, Type, Visibility}; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::ToTokens; +use syn::{punctuated::Punctuated, token::Comma, Attribute, DeriveInput, Fields, Generics, Lit, Meta, MetaNameValue, NestedMeta, Token, Type, Visibility}; /// Provides a convenient way of store the data for the TokenStream /// received on a macro @@ -28,27 +29,56 @@ impl<'a> MacroTokens<'a> { }, } } +/** +syn::attr +pub type AttributeArgs = Vec - pub fn retrieve_mapping_target_type(&self) -> Option { - for attr in self.attrs { - if attr.path.is_ident("canyon_crud") { - let Ok(Meta::List(meta_list)) = attr.parse_meta() else { - continue; - }; - - for nested in meta_list.nested.iter() { - if let NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit, .. })) = nested { - if path.is_ident("maps_to") { - if let Lit::Str(ref lit_str) = lit { +Conventional argument type associated with an invocation of an attribute macro. +For example if we are developing an attribute macro that is intended to be invoked on function items as follows: +#[my_attribute(path = "/v1/refresh")] +pub fn refresh() { + /* ... */ +} + +The implementation of this macro would want to parse its attribute arguments as type AttributeArgs. +use proc_macro::TokenStream; +use syn::{parse_macro_input, AttributeArgs, ItemFn}; + +#[proc_macro_attribute] +pub fn my_attribute(args: TokenStream, input: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as AttributeArgs); + let input = parse_macro_input!(input as ItemFn); + + /* ... */ +} +*/ +pub fn retrieve_mapping_target_type(&self) -> Option { + for attr in self.attrs { + if attr.path.is_ident("canyon_crud") { + + let name_values: Result, syn::Error> = + attr.parse_args_with(Punctuated::parse_terminated); + + println!("Primo 1 'maps to' for: {:?}", self.ty); + match name_values { + Ok(values) => { + for nv in values { + if nv.path.is_ident("maps_to") { + if let Lit::Str(lit_str) = nv.lit { + println!("Parsea-ditto for: {:?}", self.ty); return Some(Ident::new(&lit_str.value(), Span::call_site())); } } } } - } + Err(e) => { + println!("Nope. Unable to parse attribute 'maps to' for: {:?}. Err: {:?}", self.ty, e); + } + }; } - None } + None +} /// Gives a Vec of tuples that contains the visibility, the name and /// the type of every field on a Struct diff --git a/canyon_migrations/Cargo.toml b/canyon_migrations/Cargo.toml index 18c79aa4..78af6b8f 100644 --- a/canyon_migrations/Cargo.toml +++ b/canyon_migrations/Cargo.toml @@ -23,9 +23,6 @@ mysql_common = { workspace = true, optional = true } regex = { workspace = true } partialdebug = { workspace = true } walkdir = { workspace = true } -proc-macro2 = { workspace = true } -quote = { workspace = true } -syn = { version = "1.0.86", features = ["full", "parsing"] } # TODO Pending to refactor and upgrade [features] postgres = ["tokio-postgres", "canyon_core/postgres", "canyon_crud/postgres"] diff --git a/tests/tests_models/league.rs b/tests/tests_models/league.rs index b0bc0013..16310016 100644 --- a/tests/tests_models/league.rs +++ b/tests/tests_models/league.rs @@ -1,7 +1,7 @@ use canyon_sql::macros::*; #[derive(Debug, Fields, CanyonCrud, CanyonMapper, ForeignKeyable, Eq, PartialEq)] -#[canyon_crud(maps_to = Leaguasde)] +#[canyon_crud(maps_to = "League")] // #[canyon_entity(table_name = "league", schema = "public")] #[canyon_entity(table_name = "league")] pub struct League { From f9384ef9315a952ea4b20a1a781575e70ec03726 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 20 Apr 2025 22:14:59 +0200 Subject: [PATCH 087/155] feat: canyon_crud(maps_to = ) annotation, to determine to which type Canyon Crud will map the results after the queries --- canyon_macros/src/canyon_mapper_macro.rs | 1 + canyon_macros/src/lib.rs | 2 +- canyon_macros/src/query_operations/mod.rs | 1 + canyon_macros/src/query_operations/read.rs | 1 + .../src/utils/canyon_crud_attribute.rs | 30 +++++++++ canyon_macros/src/utils/macro_tokens.rs | 61 ++++--------------- canyon_macros/src/utils/mod.rs | 1 + tests/canyon.toml | 4 +- tests/tests_models/league.rs | 6 +- 9 files changed, 52 insertions(+), 55 deletions(-) create mode 100644 canyon_macros/src/utils/canyon_crud_attribute.rs diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index 78595594..534d37bf 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -185,6 +185,7 @@ fn get_field_type_as_string(typ: &Type) -> String { } #[cfg(test)] +#[cfg(feature = "mssql")] mod mapper_macro_tests { use crate::canyon_mapper_macro::get_deserializing_type; diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index 29aca677..2ee8e599 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -68,9 +68,9 @@ pub fn main(_meta: CompilerTokenStream, input: CompilerTokenStream) -> CompilerT .into() } -#[proc_macro_attribute] /// Wraps the [`test`] proc macro in a convenient way to run tests within /// the tokio's current reactor +#[proc_macro_attribute] pub fn canyon_tokio_test( _meta: CompilerTokenStream, input: CompilerTokenStream, diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index cdf237b3..35a66299 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -26,6 +26,7 @@ pub fn impl_crud_operations_trait_for_struct( let ty = macro_data.ty; let mapper_ty = macro_data .retrieve_mapping_target_type() + .expect("Expected mapping macro data") .unwrap_or_else(|| ty.clone()); let read_operations_tokens = generate_read_operations_tokens(macro_data, &table_schema_data); diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index bc7b4f1e..d2b8b796 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -13,6 +13,7 @@ pub fn generate_read_operations_tokens( let ty = macro_data.ty; let mapper_ty = macro_data .retrieve_mapping_target_type() + .expect("Expected mapping target ")// TODO: return Err(...) .unwrap_or_else(|| ty.clone()); let fa_stmt = format!("SELECT * FROM {table_schema_data}"); diff --git a/canyon_macros/src/utils/canyon_crud_attribute.rs b/canyon_macros/src/utils/canyon_crud_attribute.rs new file mode 100644 index 00000000..78e68ffc --- /dev/null +++ b/canyon_macros/src/utils/canyon_crud_attribute.rs @@ -0,0 +1,30 @@ +use proc_macro2::Ident; +use syn::parse::{Parse, ParseStream}; +use syn::Token; + +// TODO: docs +pub(super) struct CanyonCrudAttribute { + pub maps_to: Option, +} + +impl Parse for CanyonCrudAttribute { + fn parse(input: ParseStream<'_>) -> syn::Result { + // Parse the argument name + let arg_name: Ident = input.parse()?; + if arg_name != "maps_to" { + // Same error as before when encountering an unsupported attribute + return Err(syn::Error::new_spanned( + arg_name, + "unsupported 'canyon_crud' attribute, expected `maps_to`", + )); + } + + // Parse (and discard the span of) the `=` token + let _: Token![=] = input.parse()?; + + // Parse the argument value + let name = input.parse()?; + + Ok(Self { maps_to: Some(name) }) + } +} \ No newline at end of file diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index 6ead79f7..8756bcc3 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -1,9 +1,12 @@ +use syn::parse_quote::ParseQuote; use std::convert::TryFrom; use canyon_entities::field_annotation::EntityFieldAnnotation; use proc_macro2::{Ident, Span, TokenStream}; use quote::ToTokens; -use syn::{punctuated::Punctuated, token::Comma, Attribute, DeriveInput, Fields, Generics, Lit, Meta, MetaNameValue, NestedMeta, Token, Type, Visibility}; +use syn::{punctuated::Punctuated, token::Comma, Attribute, DeriveInput, Expr, ExprPath, Fields, Generics, Lit, Meta, MetaNameValue, NestedMeta, Token, Type, Visibility}; +use syn::parse::{Parse, ParseStream, Parser}; +use crate::utils::canyon_crud_attribute::CanyonCrudAttribute; /// Provides a convenient way of store the data for the TokenStream /// received on a macro @@ -29,56 +32,16 @@ impl<'a> MacroTokens<'a> { }, } } -/** -syn::attr -pub type AttributeArgs = Vec - -Conventional argument type associated with an invocation of an attribute macro. -For example if we are developing an attribute macro that is intended to be invoked on function items as follows: -#[my_attribute(path = "/v1/refresh")] -pub fn refresh() { - /* ... */ -} - -The implementation of this macro would want to parse its attribute arguments as type AttributeArgs. -use proc_macro::TokenStream; -use syn::{parse_macro_input, AttributeArgs, ItemFn}; - -#[proc_macro_attribute] -pub fn my_attribute(args: TokenStream, input: TokenStream) -> TokenStream { - let args = parse_macro_input!(args as AttributeArgs); - let input = parse_macro_input!(input as ItemFn); - - /* ... */ -} -*/ -pub fn retrieve_mapping_target_type(&self) -> Option { - for attr in self.attrs { - if attr.path.is_ident("canyon_crud") { - - let name_values: Result, syn::Error> = - attr.parse_args_with(Punctuated::parse_terminated); - - println!("Primo 1 'maps to' for: {:?}", self.ty); - match name_values { - Ok(values) => { - for nv in values { - if nv.path.is_ident("maps_to") { - if let Lit::Str(lit_str) = nv.lit { - println!("Parsea-ditto for: {:?}", self.ty); - return Some(Ident::new(&lit_str.value(), Span::call_site())); - } - } - } - } - Err(e) => { - println!("Nope. Unable to parse attribute 'maps to' for: {:?}. Err: {:?}", self.ty, e); - } - }; + + pub fn retrieve_mapping_target_type(&self) -> Result, syn::Error> { + for attr in self.attrs { + if attr.path.is_ident("canyon_crud") { + let meta: CanyonCrudAttribute = attr.parse_args()?; + return Ok(meta.maps_to); + } } + Ok(None) } - None -} /// Gives a Vec of tuples that contains the visibility, the name and /// the type of every field on a Struct diff --git a/canyon_macros/src/utils/mod.rs b/canyon_macros/src/utils/mod.rs index be2269df..4e267ac5 100644 --- a/canyon_macros/src/utils/mod.rs +++ b/canyon_macros/src/utils/mod.rs @@ -1,3 +1,4 @@ pub mod function_parser; pub mod helpers; pub mod macro_tokens; +mod canyon_crud_attribute; diff --git a/tests/canyon.toml b/tests/canyon.toml index 25c78f7f..ce76af60 100644 --- a/tests/canyon.toml +++ b/tests/canyon.toml @@ -11,7 +11,7 @@ host = 'localhost' port = 5438 db_name = 'postgres' - +# #[[canyon_sql.datasources]] #name = 'sqlserver_docker' # @@ -22,7 +22,7 @@ db_name = 'postgres' #host = 'localhost' #port = 1434 #db_name = 'master' - +# # #[[canyon_sql.datasources]] #name = 'mysql_docker' diff --git a/tests/tests_models/league.rs b/tests/tests_models/league.rs index 16310016..1d4f9934 100644 --- a/tests/tests_models/league.rs +++ b/tests/tests_models/league.rs @@ -1,9 +1,9 @@ use canyon_sql::macros::*; #[derive(Debug, Fields, CanyonCrud, CanyonMapper, ForeignKeyable, Eq, PartialEq)] -#[canyon_crud(maps_to = "League")] -// #[canyon_entity(table_name = "league", schema = "public")] -#[canyon_entity(table_name = "league")] +#[canyon_crud(maps_to = League)] // canyon_crud mapping to Self is already the default behaviour +// just here for demonstration purposes +#[canyon_entity(table_name = "league", /* schema = "public"*/)] pub struct League { #[primary_key] id: i32, From 3c8c67812c1e7c5a5e09b5ceb43a1ac1c859d126 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 21 Apr 2025 14:55:46 +0200 Subject: [PATCH 088/155] clean: removed the x_unchecked operations from the public API --- canyon_crud/src/crud.rs | 11 ---- canyon_macros/src/query_operations/read.rs | 60 ---------------------- canyon_macros/src/utils/macro_tokens.rs | 11 ++-- 3 files changed, 4 insertions(+), 78 deletions(-) diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index ef36d003..59a5b085 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -36,17 +36,6 @@ pub trait CrudOperations: Send + Sync where I: DbConnection + Send + 'a; - // fn find_all_unchecked() -> impl Future> + Send - // where - // R: RowMapper, - // Vec: FromIterator<::Output>; - // - // fn find_all_unchecked_with<'a, I>(input: I) -> impl Future> + Send - // where - // I: DbConnection + Send + 'a, - // R: RowMapper, - // Vec: FromIterator<::Output>; - // fn select_query<'a, R: RowMapper>() -> SelectQueryBuilder<'a, R>; // // fn select_query_with<'a, R: RowMapper>(database_type: DatabaseType) -> SelectQueryBuilder<'a, R>; diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index d2b8b796..1467a709 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -22,8 +22,6 @@ pub fn generate_read_operations_tokens( let find_all = create_find_all_macro(ty, &mapper_ty, &fa_stmt); let find_all_with = create_find_all_with_macro(&fa_stmt, &mapper_ty); - // let find_all_unchecked = create_find_all_unchecked_macro(ty, &fa_stmt); - // let find_all_unchecked_with = create_find_all_unchecked_with_macro(ty, &fa_stmt); // let count_stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); // let count = create_count_macro(ty, &count_stmt); @@ -36,8 +34,6 @@ pub fn generate_read_operations_tokens( quote! { #find_all #find_all_with - // #find_all_unchecked - // #find_all_unchecked_with // #count // #count_with @@ -175,35 +171,6 @@ mod __details { } } } - // - // pub fn create_find_all_unchecked_macro(ty: &syn::Ident, stmt: &str) -> TokenStream { - // let expect_msg = format!("Failed to execute find all query for: {:?}", ty.to_string()); - // quote! { - // async fn find_all_unchecked() -> Vec - // where R: RowMapper, - // Vec: FromIterator<::Output> - // { - // <#ty as canyon_sql::core::Transaction>::query(#stmt, &[], "") - // .await - // .expect(#expect_msg) - // } - // } - // } - // - // pub fn create_find_all_unchecked_with_macro(ty: &syn::Ident, stmt: &str) -> TokenStream { - // let expect_msg = format!("Failed to execute find all query for: {:?}", ty.to_string()); - // quote! { - // async fn find_all_unchecked_with<'a, R, I>(input: I) -> Vec - // where - // R: RowMapper, - // I: canyon_sql::core::DbConnection + Send + 'a, - // Vec: FromIterator<::Output> - // { - // input.query(#stmt, &[]).await - // .expect(#expect_msg) - // } - // } - // } } pub mod count_generators { @@ -337,33 +304,6 @@ mod macro_builder_read_ops_tests { // assert!(find_all_with.contains(LT_CONSTRAINT)); // assert!(find_all_with.contains(WITH_WHERE_BOUNDS)); // } - // - // #[test] - // fn test_macro_builder_find_all_unchecked() { - // let find_all_unc_builder = create_find_all_unchecked_macro( - // &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - // SELECT_ALL_STMT, - // ); - // let find_all_unc = find_all_unc_builder.to_string(); - // - // assert!(find_all_unc.contains("async fn find_all_unchecked")); - // assert!(find_all_unc.contains(RAW_RET_TY)); - // } - // - // #[test] - // fn test_macro_builder_find_all_unchecked_with() { - // let find_all_unc_with_builder = create_find_all_unchecked_with_macro( - // &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - // &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - // SELECT_ALL_STMT, - // ); - // let find_all_unc_with = find_all_unc_with_builder.to_string(); - // - // assert!(find_all_unc_with.contains("async fn find_all_unchecked_with")); - // assert!(find_all_unc_with.contains(RAW_RET_TY)); - // assert!(find_all_unc_with.contains(LT_CONSTRAINT)); - // assert!(find_all_unc_with.contains(INPUT_PARAM)); - // } #[test] fn test_macro_builder_count() { diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index 8756bcc3..d085308a 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -1,12 +1,9 @@ -use syn::parse_quote::ParseQuote; use std::convert::TryFrom; -use canyon_entities::field_annotation::EntityFieldAnnotation; -use proc_macro2::{Ident, Span, TokenStream}; -use quote::ToTokens; -use syn::{punctuated::Punctuated, token::Comma, Attribute, DeriveInput, Expr, ExprPath, Fields, Generics, Lit, Meta, MetaNameValue, NestedMeta, Token, Type, Visibility}; -use syn::parse::{Parse, ParseStream, Parser}; use crate::utils::canyon_crud_attribute::CanyonCrudAttribute; +use canyon_entities::field_annotation::EntityFieldAnnotation; +use proc_macro2::Ident; +use syn::{Attribute, DeriveInput, Fields, Generics, Type, Visibility}; /// Provides a convenient way of store the data for the TokenStream /// received on a macro @@ -192,7 +189,7 @@ impl<'a> MacroTokens<'a> { }) } - /// Returns an String ready to be inserted on the VALUES Sql clause + /// Returns a String ready to be inserted on the VALUES Sql clause /// representing generic query parameters ($x). /// /// Already returns the correct number of placeholders, skipping one From dc71906ba24974197d13dff415a0412004014761 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 21 Apr 2025 20:03:57 +0200 Subject: [PATCH 089/155] feat(wip): The querybuilder only needs bounds on DbConnection, Transaction got out of the public API --- canyon_core/src/transaction.rs | 2 +- canyon_crud/src/crud.rs | 31 ++- .../src/query_elements/query_builder.rs | 20 +- canyon_macros/src/query_operations/read.rs | 215 +++++++++--------- canyon_macros/src/utils/macro_tokens.rs | 2 + tests/canyon.toml | 46 ++-- tests/crud/init_mssql.rs | 132 +++++------ tests/crud/querybuilder_operations.rs | 106 ++++----- tests/crud/read_operations.rs | 214 +++++++++-------- 9 files changed, 380 insertions(+), 388 deletions(-) diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index dad7d556..b40215fe 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -26,7 +26,7 @@ pub trait Transaction { ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send where S: AsRef + Display + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send, R: RowMapper, { async move { input.query_one::(stmt.as_ref(), params.as_ref()).await } diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 59a5b085..b099a561 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -36,29 +36,28 @@ pub trait CrudOperations: Send + Sync where I: DbConnection + Send + 'a; - // fn select_query<'a, R: RowMapper>() -> SelectQueryBuilder<'a, R>; - // - // fn select_query_with<'a, R: RowMapper>(database_type: DatabaseType) -> SelectQueryBuilder<'a, R>; + fn select_query<'a>() -> SelectQueryBuilder<'a, R>; + + fn select_query_with<'a>(database_type: DatabaseType) -> SelectQueryBuilder<'a, R>; - /*fn count() -> impl Future>> + Send; + fn count() -> impl Future>> + Send; fn count_with<'a, I>( input: I, ) -> impl Future>> + Send where - I: DbConnection + Send + 'a;*/ + I: DbConnection + Send + 'a; - // fn find_by_pk<'a, R: RowMapper>( - // value: &'a dyn QueryParameter<'a>, - // ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send; - // - // fn find_by_pk_with<'a, R, I>( - // value: &'a dyn QueryParameter<'a>, - // input: I, - // ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send - // where - // I: DbConnection + Send + 'a, - // R: RowMapper; + fn find_by_pk<'a>( + value: &'a dyn QueryParameter<'a>, + ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send; + + fn find_by_pk_with<'a, I>( + value: &'a dyn QueryParameter<'a>, + input: I, + ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send + where + I: DbConnection + Send + 'a; fn insert<'a>( &'a mut self, diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index 764eb563..95021075 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -1,13 +1,10 @@ use crate::{ bounds::{FieldIdentifier, FieldValueIdentifier}, - crud::CrudOperations, - query_elements::query::Query, Operator, }; use canyon_core::connection::database_type::DatabaseType; use canyon_core::connection::db_connector::DbConnection; use canyon_core::{mapper::RowMapper, query_parameters::QueryParameter, transaction::Transaction}; -use std::fmt::Debug; use std::marker::PhantomData; /// Contains the elements that makes part of the formal declaration @@ -146,7 +143,7 @@ impl<'a, R: RowMapper> QueryBuilder<'a, R> { /// Launches the generated query against the database targeted /// by the selected datasource /// /// TODO: this is not definitive => QueryBuilder -> Query -> Transaction -> RowMapper - pub async fn query( + pub async fn query( mut self, input: I, ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> @@ -155,7 +152,8 @@ impl<'a, R: RowMapper> QueryBuilder<'a, R> { { self.sql.push(';'); - T::query(&self.sql, &self.params, input).await + // T::query(&self.sql, &self.params, input).await + input.query(&self.sql, &self.params).await } pub fn r#where>(&mut self, r#where: Z, op: impl Operator) { @@ -268,14 +266,14 @@ impl<'a, R: RowMapper> SelectQueryBuilder<'a, R> { /// Launches the generated query to the database pointed by the /// selected datasource #[inline] - pub async fn query( + pub async fn query( self, input: I, ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> where Vec: FromIterator<::Output>, { - self._inner.query::(input).await + self._inner.query::(input).await } /// Adds a *LEFT JOIN* SQL statement to the underlying @@ -414,14 +412,14 @@ impl<'a, R: RowMapper> UpdateQueryBuilder<'a, R> { /// Launches the generated query to the database pointed by the /// selected datasource #[inline] - pub async fn query( + pub async fn query( self, input: I, ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> where Vec: FromIterator<::Output>, { - self._inner.query::(input).await + self._inner.query::(input).await } /// Creates an SQL `SET` clause to specify the columns that must be updated in the sentence @@ -540,14 +538,14 @@ impl<'a, R: RowMapper> DeleteQueryBuilder<'a, R> { /// Launches the generated query to the database pointed by the /// selected datasource #[inline] - pub async fn query( + pub async fn query( self, input: I, ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> where Vec: FromIterator<::Output>, { - self._inner.query::(input).await + self._inner.query::(input).await } } diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 1467a709..2c2ebf69 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -23,33 +23,31 @@ pub fn generate_read_operations_tokens( let find_all = create_find_all_macro(ty, &mapper_ty, &fa_stmt); let find_all_with = create_find_all_with_macro(&fa_stmt, &mapper_ty); - // let count_stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); - // let count = create_count_macro(ty, &count_stmt); - // let count_with = create_count_with_macro(ty, &count_stmt); + let count_stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); + let count = create_count_macro(ty, &count_stmt); + let count_with = create_count_with_macro(ty, &count_stmt); let find_by_pk_complex_tokens = generate_find_by_pk_tokens(macro_data, table_schema_data); - // let read_querybuilder_ops = generate_find_all_query_tokens(macro_data, table_schema_data); + let read_querybuilder_ops = generate_select_querybuilder_tokens(&mapper_ty, table_schema_data); quote! { #find_all #find_all_with - // #count - // #count_with + #count + #count_with - // #find_by_pk_complex_tokens + #find_by_pk_complex_tokens - // #read_querybuilder_ops + #read_querybuilder_ops } } -fn generate_find_all_query_tokens( - macro_data: &MacroTokens<'_>, +fn generate_select_querybuilder_tokens( + mapper_ty: &syn::Ident, table_schema_data: &String, ) -> TokenStream { - let ty = macro_data.ty; - quote! { /// Generates a [`canyon_sql::query::SelectQueryBuilder`] /// that allows you to customize the query by adding parameters and constrains dynamically. @@ -58,7 +56,7 @@ fn generate_find_all_query_tokens( /// entity but converted to the corresponding database convention, /// unless concrete values are set on the available parameters of the /// `canyon_macro(table_name = "table_name", schema = "schema")` - fn select_query<'a, R: RowMapper>() -> canyon_sql::query::SelectQueryBuilder<'a, #ty> { + fn select_query<'a>() -> canyon_sql::query::SelectQueryBuilder<'a, #mapper_ty> { canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, canyon_sql::connection::DatabaseType::default()) } @@ -73,8 +71,8 @@ fn generate_find_all_query_tokens( /// The query it's made against the database with the configured datasource /// described in the configuration file, and selected with the [`&str`] /// passed as parameter. - fn select_query_with<'a, R: RowMapper>(database_type: canyon_sql::connection::DatabaseType) - -> canyon_sql::query::SelectQueryBuilder<'a, #ty> + fn select_query_with<'a>(database_type: canyon_sql::connection::DatabaseType) + -> canyon_sql::query::SelectQueryBuilder<'a, #mapper_ty> { canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, database_type) } @@ -89,14 +87,18 @@ fn generate_find_by_pk_tokens( use __details::pk_generators::*; let ty = macro_data.ty; + let mapper_ty = macro_data + .retrieve_mapping_target_type() + .expect("Expected mapping target ") + .unwrap_or_else(|| ty.clone()); let pk = macro_data.get_primary_key_annotation().unwrap_or_default(); let stmt = format!("SELECT * FROM {table_schema_data} WHERE {pk} = $1"); // Disabled if there's no `primary_key` annotation if pk.is_empty() { return quote! { - async fn find_by_pk<'a, R: RowMapper>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) - -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + async fn find_by_pk<'a>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) + -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> { Err( std::io::Error::new( @@ -108,13 +110,11 @@ fn generate_find_by_pk_tokens( ) } - async fn find_by_pk_with<'a, R, I>( + async fn find_by_pk_with<'a, I>( value: &'a dyn canyon_sql::core::QueryParameter<'a>, input: I - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> - where - I: canyon_sql::core::DbConnection + Send + 'a, - R: RowMapper + ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + where I: canyon_sql::core::DbConnection + Send + 'a { Err( std::io::Error::new( @@ -128,8 +128,8 @@ fn generate_find_by_pk_tokens( }; } - let find_by_pk = create_find_by_pk_macro(ty, &stmt); - let find_by_pk_with = create_find_by_pk_with(ty, &stmt); + let find_by_pk = create_find_by_pk_macro(ty, &mapper_ty, &stmt); + let find_by_pk_with = create_find_by_pk_with(&mapper_ty, &stmt); quote! { #find_by_pk @@ -138,8 +138,6 @@ fn generate_find_by_pk_tokens( } mod __details { - use crate::query_operations::{doc_comments, macro_template::MacroOperationBuilder}; - use proc_macro2::Span; use quote::quote; use syn::Ident; @@ -236,37 +234,33 @@ mod __details { } pub mod pk_generators { + use proc_macro2::TokenStream; use super::*; - use crate::query_operations::macro_template::TransactionMethod; - - pub fn create_find_by_pk_macro(ty: &Ident, stmt: &str) -> MacroOperationBuilder { - MacroOperationBuilder::new() - .fn_name("find_by_pk") - .with_lifetime() - .type_is_row_mapper() - .user_type(ty) - .return_type(ty) - .add_doc_comment(doc_comments::FIND_BY_PK) - .add_doc_comment(doc_comments::DS_ADVERTISING) - .query_string(stmt) - .input_parameters(quote! { value: &'a dyn canyon_sql::core::QueryParameter<'a> }) - .forwarded_parameters(quote! { vec![value] }) - .with_transaction_method(TransactionMethod::QueryOne) + + pub fn create_find_by_pk_macro(ty: &Ident, mapper_ty: &syn::Ident, stmt: &str) -> TokenStream { + quote! { + async fn find_by_pk<'a>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) + -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + { + <#ty as canyon_sql::core::Transaction>::query_one::< + &str, + &[&'a (dyn QueryParameter<'a>)], + #mapper_ty + >(#stmt, &vec![value], "").await + } + } } - pub fn create_find_by_pk_with(ty: &Ident, stmt: &str) -> MacroOperationBuilder { - MacroOperationBuilder::new() - .fn_name("find_by_pk_with") - .type_is_row_mapper() - .with_input_param() - .user_type(ty) - .return_type(ty) - .add_doc_comment(doc_comments::FIND_BY_PK) - .add_doc_comment(doc_comments::DS_ADVERTISING) - .query_string(stmt) - .input_parameters(quote! { value: &'a dyn canyon_sql::core::QueryParameter<'a> }) - .forwarded_parameters(quote! { vec![value] }) - .with_transaction_method(TransactionMethod::QueryOne) + pub fn create_find_by_pk_with(mapper_ty: &syn::Ident, stmt: &str) -> TokenStream { + quote! { + async fn find_by_pk_with<'a, I>(value: &'a dyn canyon_sql::core::QueryParameter<'a>, input: I) + -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + where + I: canyon_sql::core::DbConnection + Send + 'a + { + input.query_one::<#mapper_ty>(#stmt, &vec![value]).await + } + } } } } @@ -276,9 +270,9 @@ mod macro_builder_read_ops_tests { use super::__details::{count_generators::*, find_all_generators::*, pk_generators::*}; use crate::query_operations::consts::*; - const SELECT_ALL_STMT: &str = "SELECT * FROM public.user"; // TODO: introduce the const_format crate - const COUNT_STMT: &str = "SELECT COUNT(*) FROM public.user"; - const FIND_BY_PK_STMT: &str = "SELECT * FROM public.user WHERE id = $1"; + // const SELECT_ALL_STMT: &str = "SELECT * FROM public.user"; // TODO: introduce the const_format crate + // const COUNT_STMT: &str = "SELECT COUNT(*) FROM public.user"; + // const FIND_BY_PK_STMT: &str = "SELECT * FROM public.user WHERE id = $1"; // #[test] // fn test_macro_builder_find_all() { @@ -304,57 +298,58 @@ mod macro_builder_read_ops_tests { // assert!(find_all_with.contains(LT_CONSTRAINT)); // assert!(find_all_with.contains(WITH_WHERE_BOUNDS)); // } - - #[test] - fn test_macro_builder_count() { - let count_builder = create_count_macro( - &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - COUNT_STMT, - ); - let count = count_builder.to_string(); - - assert!(count.contains("async fn count")); - assert!(count.contains("Result < i64")); - } - - #[test] - fn test_macro_builder_count_with() { - let count_with_builder = create_count_with_macro( - &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - COUNT_STMT, - ); - let count_with = count_with_builder.to_string(); - - assert!(count_with.contains("async fn count_with")); - assert!(count_with.contains("Result < i64")); - assert!(count_with.contains(LT_CONSTRAINT)); - assert!(count_with.contains(INPUT_PARAM)); - } - - #[test] - fn test_macro_builder_find_by_pk() { - let find_by_pk_builder = create_find_by_pk_macro( - &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - FIND_BY_PK_STMT, - ); - let find_by_pk = find_by_pk_builder.generate_tokens().to_string(); - - assert!(find_by_pk.contains("async fn find_by_pk")); - assert!(find_by_pk.contains(LT_CONSTRAINT)); - assert!(find_by_pk.contains(OPT_RET_TY_LT)); - } - - #[test] - fn test_macro_builder_find_by_pk_with() { - let find_by_pk_with_builder = create_find_by_pk_with( - &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - FIND_BY_PK_STMT, - ); - let find_by_pk_with = find_by_pk_with_builder.generate_tokens().to_string(); - - assert!(find_by_pk_with.contains("async fn find_by_pk_with")); - assert!(find_by_pk_with.contains(LT_CONSTRAINT)); - assert!(find_by_pk_with.contains(INPUT_PARAM)); - assert!(find_by_pk_with.contains(OPT_RET_TY_LT)); - } + // + // #[test] + // fn test_macro_builder_count() { + // let count_builder = create_count_macro( + // &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + // COUNT_STMT, + // ); + // let count = count_builder.to_string(); + // + // assert!(count.contains("async fn count")); + // assert!(count.contains("Result < i64")); + // } + // + // #[test] + // fn test_macro_builder_count_with() { + // let count_with_builder = create_count_with_macro( + // &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + // COUNT_STMT, + // ); + // let count_with = count_with_builder.to_string(); + // + // assert!(count_with.contains("async fn count_with")); + // assert!(count_with.contains("Result < i64")); + // assert!(count_with.contains(LT_CONSTRAINT)); + // assert!(count_with.contains(INPUT_PARAM)); + // } + // + // #[test] + // fn test_macro_builder_find_by_pk() { + // let find_by_pk_builder = create_find_by_pk_macro( + // &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + // &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + // FIND_BY_PK_STMT, + // ); + // let find_by_pk = find_by_pk_builder.generate_tokens().to_string(); + // + // assert!(find_by_pk.contains("async fn find_by_pk")); + // assert!(find_by_pk.contains(LT_CONSTRAINT)); + // assert!(find_by_pk.contains(OPT_RET_TY_LT)); + // } + // + // #[test] + // fn test_macro_builder_find_by_pk_with() { + // let find_by_pk_with_builder = create_find_by_pk_with( + // &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), + // FIND_BY_PK_STMT, + // ); + // let find_by_pk_with = find_by_pk_with_builder.generate_tokens().to_string(); + // + // assert!(find_by_pk_with.contains("async fn find_by_pk_with")); + // assert!(find_by_pk_with.contains(LT_CONSTRAINT)); + // assert!(find_by_pk_with.contains(INPUT_PARAM)); + // assert!(find_by_pk_with.contains(OPT_RET_TY_LT)); + // } } diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index d085308a..9e583464 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -30,6 +30,8 @@ impl<'a> MacroTokens<'a> { } } + // TODO: this must be refactored in order to avoid to make the operation everytime that + // this method is queried. The trick w'd be to have a map to relate the entries. pub fn retrieve_mapping_target_type(&self) -> Result, syn::Error> { for attr in self.attrs { if attr.path.is_ident("canyon_crud") { diff --git a/tests/canyon.toml b/tests/canyon.toml index ce76af60..73c0b023 100644 --- a/tests/canyon.toml +++ b/tests/canyon.toml @@ -11,26 +11,26 @@ host = 'localhost' port = 5438 db_name = 'postgres' -# -#[[canyon_sql.datasources]] -#name = 'sqlserver_docker' -# -#[canyon_sql.datasources.auth] -#sqlserver = { basic = { username = 'sa', password = 'SqlServer-10' } } -# -#[canyon_sql.datasources.properties] -#host = 'localhost' -#port = 1434 -#db_name = 'master' -# -# -#[[canyon_sql.datasources]] -#name = 'mysql_docker' -# -#[canyon_sql.datasources.auth] -#mysql = { basic = { username = 'root', password = 'root' } } -# -#[canyon_sql.datasources.properties] -#host = 'localhost' -#port = 3307 -#db_name = 'public' \ No newline at end of file + +[[canyon_sql.datasources]] +name = 'sqlserver_docker' + +[canyon_sql.datasources.auth] +sqlserver = { basic = { username = 'sa', password = 'SqlServer-10' } } + +[canyon_sql.datasources.properties] +host = 'localhost' +port = 1434 +db_name = 'master' + + +[[canyon_sql.datasources]] +name = 'mysql_docker' + +[canyon_sql.datasources.auth] +mysql = { basic = { username = 'root', password = 'root' } } + +[canyon_sql.datasources.properties] +host = 'localhost' +port = 3307 +db_name = 'public' \ No newline at end of file diff --git a/tests/crud/init_mssql.rs b/tests/crud/init_mssql.rs index 56099f85..c2effd52 100644 --- a/tests/crud/init_mssql.rs +++ b/tests/crud/init_mssql.rs @@ -1,66 +1,66 @@ -// use crate::constants::SQL_SERVER_CREATE_TABLES; -// use crate::constants::SQL_SERVER_DS; -// use crate::constants::SQL_SERVER_FILL_TABLE_VALUES; -// use crate::tests_models::league::League; -// -// use canyon_sql::crud::CrudOperations; -// use canyon_sql::db_clients::tiberius::{Client, Config, EncryptionLevel}; -// use canyon_sql::runtime::tokio::net::TcpStream; -// use canyon_sql::runtime::tokio_util::compat::TokioAsyncWriteCompatExt; -// -// // /// In order to initialize data on `SqlServer`. we must manually insert it -// // /// when the docker starts. SqlServer official docker from Microsoft does -// // /// not allow you to run `.sql` files against the database (not at least, without) -// // /// using a workaround. So, we are going to query the `SqlServer` to check if already -// // /// has some data (other processes, persistence or multi-threading envs), af if not, -// // /// we are going to retrieve the inserted data on the `postgreSQL` at start-up and -// // /// inserting into the `SqlServer` instance. -// // /// -// // /// This will be marked as `#[ignore]`, so we can force to run first the marked as -// // /// ignored, check the data available, perform the necessary init operations and -// // /// then *cargo test * the real integration tests -// #[canyon_sql::macros::canyon_tokio_test] -// #[ignore] -// fn initialize_sql_server_docker_instance() { -// static CONN_STR: &str = // TODO: change this for the DS when will be in the public API -// "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true;Encrypt=true"; -// -// canyon_sql::runtime::futures::executor::block_on(async { -// let mut config = Config::from_ado_string(CONN_STR) -// .expect("could not parse ado string"); -// -// config.encryption(EncryptionLevel::NotSupported); -// let tcp = TcpStream::connect(config.get_addr()).await -// .expect("could not connect to stream 1"); -// let tcp2 = TcpStream::connect(config.get_addr()).await -// .expect("could not connect to stream 2"); -// tcp.set_nodelay(true).ok(); -// -// let mut client = Client::connect(config.clone(), tcp.compat_write()) -// .await -// .unwrap(); -// -// // Create the tables -// let query_result = client.query(SQL_SERVER_CREATE_TABLES, &[]).await; -// assert!(query_result.is_ok()); -// -// let leagues_sql = League::find_all_with::(SQL_SERVER_DS).await; -// println!("LSqlServer: {leagues_sql:?}"); -// assert!(leagues_sql.is_ok()); -// -// match leagues_sql { -// Ok(ref leagues) => { -// let leagues_len = leagues.len(); -// println!("Leagues already inserted on SQLSERVER: {:?}", &leagues_len); -// if leagues.len() < 10 { -// let mut client2 = Client::connect(config, tcp2.compat_write()) -// .await -// .expect("Can't connect to MSSQL"); -// let result = client2.query(SQL_SERVER_FILL_TABLE_VALUES, &[]).await; -// assert!(result.is_ok()); -// } -// } -// Err(e) => eprintln!("Error retrieving the leagues: {e}"), -// } -// }); -// } +use crate::constants::SQL_SERVER_CREATE_TABLES; +use crate::constants::SQL_SERVER_DS; +use crate::constants::SQL_SERVER_FILL_TABLE_VALUES; +use crate::tests_models::league::League; + +use canyon_sql::crud::CrudOperations; +use canyon_sql::db_clients::tiberius::{Client, Config, EncryptionLevel}; +use canyon_sql::runtime::tokio::net::TcpStream; +use canyon_sql::runtime::tokio_util::compat::TokioAsyncWriteCompatExt; + +// /// In order to initialize data on `SqlServer`. we must manually insert it +// /// when the docker starts. SqlServer official docker from Microsoft does +// /// not allow you to run `.sql` files against the database (not at least, without) +// /// using a workaround. So, we are going to query the `SqlServer` to check if already +// /// has some data (other processes, persistence or multi-threading envs), af if not, +// /// we are going to retrieve the inserted data on the `postgreSQL` at start-up and +// /// inserting into the `SqlServer` instance. +// /// +// /// This will be marked as `#[ignore]`, so we can force to run first the marked as +// /// ignored, check the data available, perform the necessary init operations and +// /// then *cargo test * the real integration tests +#[canyon_sql::macros::canyon_tokio_test] +#[ignore] +fn initialize_sql_server_docker_instance() { + static CONN_STR: &str = // TODO: change this for the DS when will be in the public API + "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true;Encrypt=true"; + + canyon_sql::runtime::futures::executor::block_on(async { + let mut config = Config::from_ado_string(CONN_STR) + .expect("could not parse ado string"); + + config.encryption(EncryptionLevel::NotSupported); + let tcp = TcpStream::connect(config.get_addr()).await + .expect("could not connect to stream 1"); + let tcp2 = TcpStream::connect(config.get_addr()).await + .expect("could not connect to stream 2"); + tcp.set_nodelay(true).ok(); + + let mut client = Client::connect(config.clone(), tcp.compat_write()) + .await + .unwrap(); + + // Create the tables + let query_result = client.query(SQL_SERVER_CREATE_TABLES, &[]).await; + assert!(query_result.is_ok()); + + let leagues_sql = League::find_all_with(SQL_SERVER_DS).await; + println!("LSqlServer: {leagues_sql:?}"); + assert!(leagues_sql.is_ok()); + + match leagues_sql { + Ok(ref leagues) => { + let leagues_len = leagues.len(); + println!("Leagues already inserted on SQLSERVER: {:?}", &leagues_len); + if leagues.len() < 10 { + let mut client2 = Client::connect(config, tcp2.compat_write()) + .await + .expect("Can't connect to MSSQL"); + let result = client2.query(SQL_SERVER_FILL_TABLE_VALUES, &[]).await; + assert!(result.is_ok()); + } + } + Err(e) => eprintln!("Error retrieving the leagues: {e}"), + } + }); +} diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index 0b6e8925..884203fe 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -22,59 +22,59 @@ use crate::tests_models::tournament::*; /// with the parameters that modifies the base SQL to SELECT * FROM #[canyon_sql::macros::canyon_tokio_test] fn test_generated_sql_by_the_select_querybuilder() { - // let select_with_joins = League::select_query() - // .inner_join("tournament", "league.id", "tournament.league_id") - // .left_join("team", "tournament.id", "player.tournament_id") - // .r#where(LeagueFieldValue::id(&7), Comp::Gt) - // .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) - // .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); - // // .query() - // // .await; - // // NOTE: We don't have in the docker the generated relationships - // // with the joins, so for now, we are just going to check that the - // // generated SQL by the SelectQueryBuilder is the expected - // assert_eq!( - // select_with_joins.read_sql(), - // "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" - // ) + let select_with_joins = League::select_query() + .inner_join("tournament", "league.id", "tournament.league_id") + .left_join("team", "tournament.id", "player.tournament_id") + .r#where(LeagueFieldValue::id(&7), Comp::Gt) + .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) + .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); + // .query() + // .await; + // NOTE: We don't have in the docker the generated relationships + // with the joins, so for now, we are just going to check that the + // generated SQL by the SelectQueryBuilder is the expected + assert_eq!( + select_with_joins.read_sql(), + "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder() { + // Find all the leagues with ID less or equals that 7 + // and where it's region column value is equals to 'Korea' + let filtered_leagues_result: Result, _> = League::select_query() + .r#where(LeagueFieldValue::id(&50), Comp::LtEq) + .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) + .query(MYSQL_DS) + .await; + + let filtered_leagues: Vec = filtered_leagues_result.unwrap(); + assert!(!filtered_leagues.is_empty()); + + let league_idx_0 = filtered_leagues.first().unwrap(); + assert_eq!(league_idx_0.id, 34); + assert_eq!(league_idx_0.region, "KOREA"); +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_fulllike() { + // Find all the leagues with "LC" in their name + let filtered_leagues_result = + League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Full); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" + ) } -// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder() { -// // Find all the leagues with ID less or equals that 7 -// // and where it's region column value is equals to 'Korea' -// let filtered_leagues_result: Result, _> = League::select_query() -// .r#where(LeagueFieldValue::id(&50), Comp::LtEq) -// .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) -// .query::(MYSQL_DS) -// .await; -// -// let filtered_leagues: Vec = filtered_leagues_result.unwrap(); -// assert!(!filtered_leagues.is_empty()); -// -// let league_idx_0 = filtered_leagues.first().unwrap(); -// assert_eq!(league_idx_0.id, 34); -// assert_eq!(league_idx_0.region, "KOREA"); -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_fulllike() { -// // Find all the leagues with "LC" in their name -// let filtered_leagues_result = -// League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" -// ) -// } -// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mssql")] @@ -83,13 +83,13 @@ fn test_generated_sql_by_the_select_querybuilder() { // // Find all the leagues with "LC" in their name // let filtered_leagues_result = // League::select_query_with(SQL_SERVER_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" // ) // } -// + // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mysql")] diff --git a/tests/crud/read_operations.rs b/tests/crud/read_operations.rs index ef453153..9f21e282 100644 --- a/tests/crud/read_operations.rs +++ b/tests/crud/read_operations.rs @@ -57,111 +57,109 @@ fn test_crud_find_all_with_mysql() { } -// // /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -// // /// defined with the #[primary_key] attribute over some field of the type. -// // /// -// // /// Uses the *default datasource*. -// // #[cfg(feature = "postgres")] -// // #[canyon_sql::macros::canyon_tokio_test] -// // fn test_crud_find_by_pk() { -// // let find_by_pk_result: Result, Box> = -// // League::find_by_pk(&1).await; -// // assert!(find_by_pk_result.as_ref().unwrap().is_some()); -// // -// // let some_league = find_by_pk_result.unwrap().unwrap(); -// // assert_eq!(some_league.id, 1); -// // assert_eq!(some_league.ext_id, 100695891328981122_i64); -// // assert_eq!(some_league.slug, "european-masters"); -// // assert_eq!(some_league.name, "European Masters"); -// // assert_eq!(some_league.region, "EUROPE"); -// // assert_eq!( -// // some_league.image_url, -// // "http://static.lolesports.com/leagues/EM_Bug_Outline1.png" -// // ); -// // } -// // -// // /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -// // /// defined with the #[primary_key] attribute over some field of the type. -// // /// -// // /// Uses the *specified datasource mssql* in the second parameter of the function call. -// // #[cfg(feature = "mssql")] -// // #[canyon_sql::macros::canyon_tokio_test] -// // fn test_crud_find_by_pk_with_mssql() { -// // let find_by_pk_result: Result, Box> = -// // League::find_by_pk_with(&27, SQL_SERVER_DS).await; -// // assert!(find_by_pk_result.as_ref().unwrap().is_some()); -// // -// // let some_league = find_by_pk_result.unwrap().unwrap(); -// // assert_eq!(some_league.id, 27); -// // assert_eq!(some_league.ext_id, 107898214974993351_i64); -// // assert_eq!(some_league.slug, "college_championship"); -// // assert_eq!(some_league.name, "College Championship"); -// // assert_eq!(some_league.region, "NORTH AMERICA"); -// // assert_eq!( -// // some_league.image_url, -// // "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" -// // ); -// // } -// // -// // /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is -// // /// defined with the #[primary_key] attribute over some field of the type. -// // /// -// // /// Uses the *specified datasource mysql* in the second parameter of the function call. -// // #[cfg(feature = "mysql")] -// // #[canyon_sql::macros::canyon_tokio_test] -// // fn test_crud_find_by_pk_with_mysql() { -// // let find_by_pk_result: Result, Box> = -// // League::find_by_pk_with(&27, MYSQL_DS).await; -// // assert!(find_by_pk_result.as_ref().unwrap().is_some()); -// // -// // let some_league = find_by_pk_result.unwrap().unwrap(); -// // assert_eq!(some_league.id, 27); -// // assert_eq!(some_league.ext_id, 107898214974993351_i64); -// // assert_eq!(some_league.slug, "college_championship"); -// // assert_eq!(some_league.name, "College Championship"); -// // assert_eq!(some_league.region, "NORTH AMERICA"); -// // assert_eq!( -// // some_league.image_url, -// // "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" -// // ); -// // } - -// /// Counts how many rows contains an entity on the target database. -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_count_operation() { -// assert_eq!( -// League::find_all::().await.unwrap().len() as i64, -// League::count().await.unwrap() -// ); -// } -// -// /// Counts how many rows contains an entity on the target database using -// /// the specified datasource mssql -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_count_with_operation_mssql() { -// assert_eq!( -// League::find_all_with::(SQL_SERVER_DS) -// .await -// .unwrap() -// .len() as i64, -// League::count_with(SQL_SERVER_DS).await.unwrap() -// ); -// } -// -// /// Counts how many rows contains an entity on the target database using -// /// the specified datasource mysql -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_count_with_operation_mysql() { -// assert_eq!( -// League::find_all_with::(MYSQL_DS) -// .await -// .unwrap() -// .len() as i64, -// League::count_with(MYSQL_DS).await.unwrap() -// ); -// } -// -// +/// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +/// defined with the #[primary_key] attribute over some field of the type. +/// +/// Uses the *default datasource*. +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_by_pk() { + let find_by_pk_result: Result, Box> = + League::find_by_pk(&1).await; + assert!(find_by_pk_result.as_ref().unwrap().is_some()); + + let some_league = find_by_pk_result.unwrap().unwrap(); + assert_eq!(some_league.id, 1); + assert_eq!(some_league.ext_id, 100695891328981122_i64); + assert_eq!(some_league.slug, "european-masters"); + assert_eq!(some_league.name, "European Masters"); + assert_eq!(some_league.region, "EUROPE"); + assert_eq!( + some_league.image_url, + "http://static.lolesports.com/leagues/EM_Bug_Outline1.png" + ); +} + +/// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +/// defined with the #[primary_key] attribute over some field of the type. +/// +/// Uses the *specified datasource mssql* in the second parameter of the function call. +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_by_pk_with_mssql() { + let find_by_pk_result: Result, Box> = + League::find_by_pk_with(&27, SQL_SERVER_DS).await; + assert!(find_by_pk_result.as_ref().unwrap().is_some()); + + let some_league = find_by_pk_result.unwrap().unwrap(); + assert_eq!(some_league.id, 27); + assert_eq!(some_league.ext_id, 107898214974993351_i64); + assert_eq!(some_league.slug, "college_championship"); + assert_eq!(some_league.name, "College Championship"); + assert_eq!(some_league.region, "NORTH AMERICA"); + assert_eq!( + some_league.image_url, + "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" + ); +} + +/// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is +/// defined with the #[primary_key] attribute over some field of the type. +/// +/// Uses the *specified datasource mysql* in the second parameter of the function call. +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_by_pk_with_mysql() { + let find_by_pk_result: Result, Box> = + League::find_by_pk_with(&27, MYSQL_DS).await; + assert!(find_by_pk_result.as_ref().unwrap().is_some()); + + let some_league = find_by_pk_result.unwrap().unwrap(); + assert_eq!(some_league.id, 27); + assert_eq!(some_league.ext_id, 107898214974993351_i64); + assert_eq!(some_league.slug, "college_championship"); + assert_eq!(some_league.name, "College Championship"); + assert_eq!(some_league.region, "NORTH AMERICA"); + assert_eq!( + some_league.image_url, + "http://static.lolesports.com/leagues/1646396098648_CollegeChampionshiplogo.png" + ); +} + +/// Counts how many rows contains an entity on the target database. +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_count_operation() { + assert_eq!( + League::find_all().await.unwrap().len() as i64, + League::count().await.unwrap() + ); +} + +/// Counts how many rows contains an entity on the target database using +/// the specified datasource mssql +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_count_with_operation_mssql() { + assert_eq!( + League::find_all_with(SQL_SERVER_DS) + .await + .unwrap() + .len() as i64, + League::count_with(SQL_SERVER_DS).await.unwrap() + ); +} + +/// Counts how many rows contains an entity on the target database using +/// the specified datasource mysql +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_count_with_operation_mysql() { + assert_eq!( + League::find_all_with(MYSQL_DS) + .await + .unwrap() + .len() as i64, + League::count_with(MYSQL_DS).await.unwrap() + ); +} From 33c8046adf7f79889c60b8246dd9f48850e57468 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 30 Apr 2025 16:04:25 +0200 Subject: [PATCH 090/155] feat(wip): refactored the read ops file structure. Re-enabled the tests without the MacroOpsBuilder --- canyon_core/src/connection/database_type.rs | 4 +- canyon_core/src/connection/datasources.rs | 6 +- .../src/connection/db_clients/mssql.rs | 10 +- .../src/connection/db_clients/mysql.rs | 24 +- .../src/connection/db_clients/postgresql.rs | 16 +- canyon_core/src/connection/db_connector.rs | 59 ++- canyon_core/src/connection/mod.rs | 13 +- canyon_core/src/query_parameters.rs | 2 +- canyon_core/src/rows.rs | 2 +- canyon_core/src/transaction.rs | 10 +- canyon_crud/src/bounds.rs | 3 +- canyon_crud/src/crud.rs | 40 +- .../src/query_elements/query_builder.rs | 8 +- canyon_macros/src/canyon_mapper_macro.rs | 11 +- canyon_macros/src/query_operations/consts.rs | 19 +- canyon_macros/src/query_operations/delete.rs | 5 +- .../src/query_operations/foreign_key.rs | 12 +- .../src/query_operations/macro_template.rs | 10 +- canyon_macros/src/query_operations/mod.rs | 2 +- canyon_macros/src/query_operations/read.rs | 358 ++++++++++-------- .../src/utils/canyon_crud_attribute.rs | 6 +- canyon_macros/src/utils/macro_tokens.rs | 2 +- canyon_macros/src/utils/mod.rs | 2 +- canyon_migrations/src/migrations/handler.rs | 15 +- canyon_migrations/src/migrations/memory.rs | 11 +- canyon_migrations/src/migrations/processor.rs | 12 +- tests/crud/init_mssql.rs | 9 +- tests/crud/querybuilder_operations.rs | 158 ++++---- tests/crud/read_operations.rs | 11 +- tests/tests_models/league.rs | 5 +- 30 files changed, 450 insertions(+), 395 deletions(-) diff --git a/canyon_core/src/connection/database_type.rs b/canyon_core/src/connection/database_type.rs index cabc5f31..8ac8a481 100644 --- a/canyon_core/src/connection/database_type.rs +++ b/canyon_core/src/connection/database_type.rs @@ -1,6 +1,6 @@ -use serde::Deserialize; -use crate::connection::DEFAULT_DATASOURCE; use super::datasources::Auth; +use crate::connection::DEFAULT_DATASOURCE; +use serde::Deserialize; /// Holds the current supported databases by Canyon-SQL #[derive(Deserialize, Debug, Eq, PartialEq, Clone, Copy)] diff --git a/canyon_core/src/connection/datasources.rs b/canyon_core/src/connection/datasources.rs index 560789a0..13de6e14 100644 --- a/canyon_core/src/connection/datasources.rs +++ b/canyon_core/src/connection/datasources.rs @@ -114,11 +114,13 @@ impl DatasourceConfig { pub fn get_db_type(&self) -> DatabaseType { self.auth.get_db_type() } - + pub fn has_migrations_enabled(&self) -> bool { if let Some(migrations) = self.properties.migrations { migrations.has_migrations_enabled() - } else { false } + } else { + false + } } } diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/db_clients/mssql.rs index 5bdf0bea..3fbedfe7 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/db_clients/mssql.rs @@ -21,7 +21,7 @@ impl DbConnection for SqlServerConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send { + ) -> impl Future>> + Send { sqlserver_query_launcher::query_rows(stmt, params, self) } @@ -29,7 +29,7 @@ impl DbConnection for SqlServerConnection { &self, stmt: S, params: &[&'a (dyn QueryParameter<'a>)], - ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where S: AsRef + Display + Send, R: RowMapper, @@ -65,7 +65,7 @@ impl DbConnection for SqlServerConnection { sqlserver_query_launcher::execute(stmt, params, self) } - fn get_database_type(&self) -> Result> { + fn get_database_type(&self) -> Result> { Ok(DatabaseType::SqlServer) } } @@ -82,7 +82,7 @@ pub(crate) mod sqlserver_query_launcher { stmt: S, params: &[&'a dyn QueryParameter<'a>], conn: &SqlServerConnection, - ) -> Result, Box<(dyn Error + Sync + Send)>> + ) -> Result, Box<(dyn Error + Send + Sync)>> where S: AsRef + Display + Send, R: RowMapper, @@ -103,7 +103,7 @@ pub(crate) mod sqlserver_query_launcher { stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &SqlServerConnection, - ) -> Result> { + ) -> Result> { let result = execute_query(stmt, params, conn) .await? .into_results() diff --git a/canyon_core/src/connection/db_clients/mysql.rs b/canyon_core/src/connection/db_clients/mysql.rs index dc0b247e..3de45a08 100644 --- a/canyon_core/src/connection/db_clients/mysql.rs +++ b/canyon_core/src/connection/db_clients/mysql.rs @@ -23,7 +23,7 @@ impl DbConnection for MysqlConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send { + ) -> impl Future>> + Send { mysql_query_launcher::query_rows(stmt, params, self) } @@ -31,7 +31,7 @@ impl DbConnection for MysqlConnection { &self, stmt: S, params: &[&'a (dyn QueryParameter<'a>)], - ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where S: AsRef + Display + Send, R: RowMapper, @@ -44,7 +44,7 @@ impl DbConnection for MysqlConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where R: RowMapper, { @@ -55,7 +55,7 @@ impl DbConnection for MysqlConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send { + ) -> impl Future>> + Send { mysql_query_launcher::query_one_for(stmt, params, self) } @@ -63,11 +63,11 @@ impl DbConnection for MysqlConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send { + ) -> impl Future>> + Send { mysql_query_launcher::execute(stmt, params, self) } - fn get_database_type(&self) -> Result> { + fn get_database_type(&self) -> Result> { Ok(DatabaseType::MySQL) } } @@ -92,7 +92,7 @@ pub(crate) mod mysql_query_launcher { stmt: S, params: &[&'_ dyn QueryParameter<'_>], conn: &MysqlConnection, - ) -> Result, Box<(dyn Error + Sync + Send)>> + ) -> Result, Box<(dyn Error + Send + Sync)>> where S: AsRef + Display + Send, R: RowMapper, @@ -110,7 +110,7 @@ pub(crate) mod mysql_query_launcher { stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &MysqlConnection, - ) -> Result> { + ) -> Result> { Ok(CanyonRows::MySQL(execute_query(stmt, params, conn).await?)) } @@ -119,7 +119,7 @@ pub(crate) mod mysql_query_launcher { stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &MysqlConnection, - ) -> Result, Box<(dyn Error + Sync + Send)>> + ) -> Result, Box<(dyn Error + Send + Sync)>> where R: RowMapper, { @@ -136,7 +136,7 @@ pub(crate) mod mysql_query_launcher { stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &MysqlConnection, - ) -> Result> { + ) -> Result> { Ok(execute_query(stmt, params, conn) .await? .first() @@ -151,7 +151,7 @@ pub(crate) mod mysql_query_launcher { stmt: S, params: &[&'_ dyn QueryParameter<'_>], conn: &MysqlConnection, - ) -> Result, Box<(dyn Error + Sync + Send)>> + ) -> Result, Box<(dyn Error + Send + Sync)>> where S: AsRef + Display + Send, { @@ -181,7 +181,7 @@ pub(crate) mod mysql_query_launcher { stmt: S, params: &[&'_ dyn QueryParameter<'_>], conn: &MysqlConnection, - ) -> Result> + ) -> Result> where S: AsRef + Display + Send, { diff --git a/canyon_core/src/connection/db_clients/postgresql.rs b/canyon_core/src/connection/db_clients/postgresql.rs index d47bacd7..8f5f61da 100644 --- a/canyon_core/src/connection/db_clients/postgresql.rs +++ b/canyon_core/src/connection/db_clients/postgresql.rs @@ -21,7 +21,7 @@ impl DbConnection for PostgreSqlConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send { + ) -> impl Future>> + Send { postgres_query_launcher::query_rows(stmt, params, self) } @@ -29,7 +29,7 @@ impl DbConnection for PostgreSqlConnection { &self, stmt: S, params: &[&'a (dyn QueryParameter<'a>)], - ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where S: AsRef + Display + Send, R: RowMapper, @@ -65,7 +65,7 @@ impl DbConnection for PostgreSqlConnection { postgres_query_launcher::execute(stmt, params, self) } - fn get_database_type(&self) -> Result> { + fn get_database_type(&self) -> Result> { Ok(DatabaseType::PostgreSql) } } @@ -82,7 +82,7 @@ pub(crate) mod postgres_query_launcher { stmt: S, params: &[&'_ (dyn QueryParameter<'_>)], conn: &PostgreSqlConnection, - ) -> Result, Box<(dyn Error + Sync + Send)>> + ) -> Result, Box<(dyn Error + Send + Sync)>> where S: AsRef + Display + Send, R: RowMapper, @@ -102,7 +102,7 @@ pub(crate) mod postgres_query_launcher { stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &PostgreSqlConnection, - ) -> Result> { + ) -> Result> { let m_params: Vec<_> = params .iter() .map(|param| param.as_postgres_param()) @@ -118,7 +118,7 @@ pub(crate) mod postgres_query_launcher { stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &PostgreSqlConnection, - ) -> Result, Box<(dyn Error + Sync + Send)>> + ) -> Result, Box<(dyn Error + Send + Sync)>> where R: RowMapper, { @@ -142,7 +142,7 @@ pub(crate) mod postgres_query_launcher { stmt: &str, params: &[&'a dyn QueryParameter<'a>], conn: &PostgreSqlConnection, - ) -> Result> { + ) -> Result> { let m_params: Vec<_> = params .iter() .map(|param| param.as_postgres_param()) @@ -156,7 +156,7 @@ pub(crate) mod postgres_query_launcher { stmt: S, params: &[&'_ (dyn QueryParameter<'_>)], conn: &PostgreSqlConnection, - ) -> Result> + ) -> Result> where S: AsRef + Display + Send, { diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index ddfa82c4..2cdb3039 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -22,13 +22,13 @@ pub trait DbConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send; + ) -> impl Future>> + Send; fn query<'a, S, R>( &self, stmt: S, params: &[&'a (dyn QueryParameter<'a>)], - ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where S: AsRef + Display + Send, R: RowMapper, @@ -38,7 +38,7 @@ pub trait DbConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where R: RowMapper; @@ -52,7 +52,7 @@ pub trait DbConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send; + ) -> impl Future>> + Send; /// Executes the given SQL statement against the target database, being any implementor of self, /// returning only a numerical positive integer number reflecting the number of affected rows @@ -60,9 +60,9 @@ pub trait DbConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send; + ) -> impl Future>> + Send; - fn get_database_type(&self) -> Result>; + fn get_database_type(&self) -> Result>; } /// This impl of [` DbConnection` ] for [`&str`] allows the client to use the exposed input types @@ -74,7 +74,7 @@ impl DbConnection for &str { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { + ) -> Result> { let conn = get_database_connection_by_ds(Some(self)).await?; conn.query_rows(stmt, params).await } @@ -83,7 +83,7 @@ impl DbConnection for &str { &self, stmt: S, params: &[&'a (dyn QueryParameter<'a>)], - ) -> Result, Box<(dyn Error + Sync + Send)>> + ) -> Result, Box<(dyn Error + Send + Sync)>> where S: AsRef + Display + Send, R: RowMapper, @@ -97,7 +97,7 @@ impl DbConnection for &str { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result, Box<(dyn Error + Sync + Send)>> + ) -> Result, Box<(dyn Error + Send + Sync)>> where R: RowMapper, { @@ -110,7 +110,7 @@ impl DbConnection for &str { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { + ) -> Result> { let sane_ds_name = if !self.is_empty() { Some(*self) } else { None }; let conn = get_database_connection_by_ds(sane_ds_name).await?; conn.query_one_for(stmt, params).await @@ -120,13 +120,13 @@ impl DbConnection for &str { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { + ) -> Result> { let sane_ds_name = if !self.is_empty() { Some(*self) } else { None }; let conn = get_database_connection_by_ds(sane_ds_name).await?; conn.execute(stmt, params).await } - fn get_database_type(&self) -> Result> { + fn get_database_type(&self) -> Result> { Ok(find_datasource_by_name_or_try_default(Some(*self))?.get_db_type()) } } @@ -150,7 +150,7 @@ impl DbConnection for DatabaseConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { + ) -> Result> { db_conn_launch_impl(self, stmt, params).await } @@ -158,7 +158,7 @@ impl DbConnection for DatabaseConnection { &self, stmt: S, params: &[&'a (dyn QueryParameter<'a>)], - ) -> Result, Box<(dyn Error + Sync + Send)>> + ) -> Result, Box<(dyn Error + Send + Sync)>> where S: AsRef + Display + Send, R: RowMapper, @@ -180,7 +180,7 @@ impl DbConnection for DatabaseConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result, Box<(dyn Error + Sync + Send)>> + ) -> Result, Box<(dyn Error + Send + Sync)>> where R: RowMapper, { @@ -191,7 +191,7 @@ impl DbConnection for DatabaseConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { + ) -> Result> { match self { #[cfg(feature = "postgres")] DatabaseConnection::Postgres(client) => client.query_one_for(stmt, params).await, @@ -207,7 +207,7 @@ impl DbConnection for DatabaseConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { + ) -> Result> { match self { #[cfg(feature = "postgres")] DatabaseConnection::Postgres(client) => client.execute(stmt, params).await, @@ -220,7 +220,7 @@ impl DbConnection for DatabaseConnection { } } - fn get_database_type(&self) -> Result> { + fn get_database_type(&self) -> Result> { Ok(self.get_db_type()) } } @@ -230,14 +230,14 @@ impl DbConnection for &mut DatabaseConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { + ) -> Result> { db_conn_launch_impl(self, stmt, params).await } async fn query<'a, S, R>( &self, stmt: S, params: &[&'a (dyn QueryParameter<'a>)], - ) -> Result, Box<(dyn Error + Sync + Send)>> + ) -> Result, Box<(dyn Error + Send + Sync)>> where S: AsRef + Display + Send, R: RowMapper, @@ -259,7 +259,7 @@ impl DbConnection for &mut DatabaseConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result, Box<(dyn Error + Sync + Send)>> + ) -> Result, Box<(dyn Error + Send + Sync)>> where R: RowMapper, { @@ -270,7 +270,7 @@ impl DbConnection for &mut DatabaseConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { + ) -> Result> { match self { #[cfg(feature = "postgres")] DatabaseConnection::Postgres(client) => client.query_one_for(stmt, params).await, @@ -287,7 +287,7 @@ impl DbConnection for &mut DatabaseConnection { &self, stmt: &str, params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { + ) -> Result> { match self { #[cfg(feature = "postgres")] DatabaseConnection::Postgres(client) => client.execute(stmt, params).await, @@ -300,7 +300,7 @@ impl DbConnection for &mut DatabaseConnection { } } - fn get_database_type(&self) -> Result> { + fn get_database_type(&self) -> Result> { Ok(self.get_db_type()) } } @@ -407,12 +407,12 @@ mod connection_helpers { tiberius_config.authentication(auth_config); tiberius_config.trust_cert(); // TODO: this should be specifically set via user input tiberius_config.encryption(tiberius::EncryptionLevel::NotSupported); // TODO: user input - // TODO: in MacOS 15, this is the actual workaround. We need to investigate further - // https://github.com/prisma/tiberius/issues/364 - + // TODO: in MacOS 15, this is the actual workaround. We need to investigate further + // https://github.com/prisma/tiberius/issues/364 + let tcp = TcpStream::connect(tiberius_config.get_addr()).await?; tcp.set_nodelay(true)?; - + let client = tiberius::Client::connect(tiberius_config, tcp).await?; Ok(DatabaseConnection::SqlServer(SqlServerConnection { @@ -443,8 +443,7 @@ mod connection_helpers { #[cfg(feature = "mysql")] DatabaseType::MySQL => "mysql", #[cfg(feature = "mssql")] - DatabaseType::SqlServer => "" - // # todo!("Connection string for MSSQL should never be reached"), + DatabaseType::SqlServer => "", // # todo!("Connection string for MSSQL should never be reached"), }; format!( "{server}://{user}:{pswd}@{host}:{port}/{db}", diff --git a/canyon_core/src/connection/mod.rs b/canyon_core/src/connection/mod.rs index 352864b1..e46b9fae 100644 --- a/canyon_core/src/connection/mod.rs +++ b/canyon_core/src/connection/mod.rs @@ -42,7 +42,7 @@ lazy_static! { pub static ref DATASOURCES: Vec = CONFIG_FILE.canyon_sql.datasources.clone(); - + pub static ref DEFAULT_DATASOURCE: &'static DatasourceConfig = DATASOURCES.first() .expect("No datasource configured"); @@ -82,11 +82,14 @@ fn find_canyon_config_file() -> PathBuf { pub async fn init_connections_cache() { for datasource in DATASOURCES.iter() { let db_conn = DatabaseConnection::new(datasource).await; - + if let Err(e) = db_conn { - panic!("Error opening database connection for {}. Err: {}", datasource.name, e); + panic!( + "Error opening database connection for {}. Err: {}", + datasource.name, e + ); } - + CACHED_DATABASE_CONN.lock().await.insert( &datasource.name, DatabaseConnection::new(datasource).await.unwrap(), @@ -116,4 +119,4 @@ pub fn find_datasource_by_name_or_try_default( |ds_name| DATASOURCES.iter().find(|ds| ds.name.eq(ds_name)), ) .ok_or_else(|| DatasourceNotFound::from(datasource_name)) -} \ No newline at end of file +} diff --git a/canyon_core/src/query_parameters.rs b/canyon_core/src/query_parameters.rs index cbdf2487..d3d14ff9 100644 --- a/canyon_core/src/query_parameters.rs +++ b/canyon_core/src/query_parameters.rs @@ -10,7 +10,7 @@ use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Utc}; /// Defines a trait for represent type bounds against the allowed /// data types supported by Canyon to be used as query parameters. -pub trait QueryParameter<'a>: std::fmt::Debug + Sync + Send { +pub trait QueryParameter<'a>: std::fmt::Debug + Send + Sync { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync); #[cfg(feature = "mssql")] diff --git a/canyon_core/src/rows.rs b/canyon_core/src/rows.rs index d37da35b..768a4d23 100644 --- a/canyon_core/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -72,7 +72,7 @@ pub enum CanyonRows { #[cfg(feature = "mssql")] Tiberius(Vec), #[cfg(feature = "mysql")] - MySQL(Vec) + MySQL(Vec), } impl IntoResults for Result { diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index b40215fe..3a37b5bd 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -10,7 +10,7 @@ pub trait Transaction { stmt: S, params: &[&'a (dyn QueryParameter<'a>)], input: impl DbConnection + Send, - ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> where S: AsRef + Display + Send, R: RowMapper, @@ -23,7 +23,7 @@ pub trait Transaction { stmt: S, params: Z, input: impl DbConnection + Send + 'a, - ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where S: AsRef + Display + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send, @@ -36,7 +36,7 @@ pub trait Transaction { stmt: S, params: Z, input: impl DbConnection + Send + 'a, - ) -> impl Future>> + Send + ) -> impl Future>> + Send where S: AsRef + Display + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send + 'a, @@ -51,7 +51,7 @@ pub trait Transaction { stmt: S, params: Z, input: impl DbConnection + Send + 'a, - ) -> impl Future>> + Send + ) -> impl Future>> + Send where S: AsRef + Display + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send + 'a, @@ -63,7 +63,7 @@ pub trait Transaction { stmt: S, params: Z, input: impl DbConnection + Send + 'a, - ) -> impl Future>> + Send + ) -> impl Future>> + Send where S: AsRef + Display + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send + 'a, diff --git a/canyon_crud/src/bounds.rs b/canyon_crud/src/bounds.rs index 84e36eab..59c2e49d 100644 --- a/canyon_crud/src/bounds.rs +++ b/canyon_crud/src/bounds.rs @@ -52,8 +52,7 @@ pub trait FieldIdentifier /// IntVariant(i32) /// } /// ``` -pub trait FieldValueIdentifier<'a> -{ +pub trait FieldValueIdentifier<'a> { fn value(self) -> (&'static str, &'a dyn QueryParameter<'a>); } diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index b099a561..116bd5ff 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -24,69 +24,69 @@ use std::future::Future; /// Also, you can find the written macro-code that performs the auto-mapping /// in the *canyon_sql_root::canyon_macros* crates, on the root of this project. pub trait CrudOperations: Send + Sync - where - R: RowMapper, - Vec: FromIterator<::Output> +where + R: RowMapper, + Vec: FromIterator<::Output>, { - fn find_all() -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send; + fn find_all() -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send; fn find_all_with<'a, I>( input: I, - ) -> impl Future, Box<(dyn Error + Sync + Send)>>> + Send + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where I: DbConnection + Send + 'a; fn select_query<'a>() -> SelectQueryBuilder<'a, R>; - + fn select_query_with<'a>(database_type: DatabaseType) -> SelectQueryBuilder<'a, R>; - fn count() -> impl Future>> + Send; - + fn count() -> impl Future>> + Send; + fn count_with<'a, I>( input: I, - ) -> impl Future>> + Send + ) -> impl Future>> + Send where I: DbConnection + Send + 'a; fn find_by_pk<'a>( value: &'a dyn QueryParameter<'a>, - ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send; - + ) -> impl Future, Box<(dyn Error + Send + Sync + 'a)>>> + Send; + fn find_by_pk_with<'a, I>( value: &'a dyn QueryParameter<'a>, input: I, - ) -> impl Future, Box<(dyn Error + Sync + Send + 'a)>>> + Send + ) -> impl Future, Box<(dyn Error + Send + Sync + 'a)>>> + Send where I: DbConnection + Send + 'a; fn insert<'a>( &'a mut self, - ) -> impl Future>> + Send; + ) -> impl Future>> + Send; fn insert_with<'a, I>( &mut self, input: I, - ) -> impl Future>> + Send + ) -> impl Future>> + Send where I: DbConnection + Send + 'a; // fn multi_insert<'a, T>( // instances: &'a mut [&'a mut T], - // ) -> impl Future>> + Send; + // ) -> impl Future>> + Send; // // fn multi_insert_with<'a, T, I>( // instances: &'a mut [&'a mut T], // input: I, - // ) -> impl Future>> + Send + // ) -> impl Future>> + Send // where // I: DbConnection + Send + 'a; - fn update(&self) -> impl Future>> + Send; + fn update(&self) -> impl Future>> + Send; fn update_with<'a, I>( &self, input: I, - ) -> impl Future>> + Send + ) -> impl Future>> + Send where I: DbConnection + Send + 'a; @@ -96,12 +96,12 @@ pub trait CrudOperations: Send + Sync // where // I: DbConnection + Send + 'a; - fn delete(&self) -> impl Future>> + Send; + fn delete(&self) -> impl Future>> + Send; fn delete_with<'a, I>( &self, input: I, - ) -> impl Future>> + Send + ) -> impl Future>> + Send where I: DbConnection + Send + 'a; diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index 95021075..72d6d347 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -146,7 +146,7 @@ impl<'a, R: RowMapper> QueryBuilder<'a, R> { pub async fn query( mut self, input: I, - ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> where Vec: FromIterator<::Output>, { @@ -269,7 +269,7 @@ impl<'a, R: RowMapper> SelectQueryBuilder<'a, R> { pub async fn query( self, input: I, - ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> where Vec: FromIterator<::Output>, { @@ -415,7 +415,7 @@ impl<'a, R: RowMapper> UpdateQueryBuilder<'a, R> { pub async fn query( self, input: I, - ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> where Vec: FromIterator<::Output>, { @@ -541,7 +541,7 @@ impl<'a, R: RowMapper> DeleteQueryBuilder<'a, R> { pub async fn query( self, input: I, - ) -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> where Vec: FromIterator<::Output>, { diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index 534d37bf..7ed2b1a8 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -78,7 +78,8 @@ fn create_postgres_fields_mapping( } #[cfg(feature = "mysql")] -#[allow(clippy::type_complexity)]fn create_mysql_fields_mapping( +#[allow(clippy::type_complexity)] +fn create_mysql_fields_mapping( fields: &[(Visibility, Ident, Type)], ) -> Map, fn(&'_ (Visibility, Ident, Type)) -> TokenStream> { fields.iter().map(|(_vis, ident, _ty)| { @@ -156,8 +157,12 @@ fn get_deserializing_type(target_type: &str) -> TokenStream { quote! { #tt } } }) - .unwrap_or_else(|| panic!("Unable to process type: {} on the given struct for SqlServer", - target_type)) + .unwrap_or_else(|| { + panic!( + "Unable to process type: {} on the given struct for SqlServer", + target_type + ) + }) } #[cfg(feature = "mssql")] diff --git a/canyon_macros/src/query_operations/consts.rs b/canyon_macros/src/query_operations/consts.rs index 7038b9e9..f9db6f19 100644 --- a/canyon_macros/src/query_operations/consts.rs +++ b/canyon_macros/src/query_operations/consts.rs @@ -8,6 +8,7 @@ use syn::{Ident, Type}; thread_local! { pub static USER_MOCK_TY: RefCell = RefCell::new(Ident::new("User", Span::call_site())); + pub static USER_MOCK_MAPPER_TY: RefCell = RefCell::new(Ident::new("User", Span::call_site())); pub static VOID_RET_TY: RefCell = RefCell::new({ let ret_ty: Type = syn::parse_str("()").expect("Failed to parse unit type"); quote! { #ret_ty } @@ -19,18 +20,26 @@ thread_local! { pub const RAW_RET_TY: &str = "Vec < User >"; pub const RES_RET_TY: &str = - "Result < Vec < User > , Box < (dyn std :: error :: Error + Send + Sync) > >"; + "Result < Vec < User > , Box < (dyn std :: error :: Error + Send + Sync) >>"; pub const RES_VOID_RET_TY: &str = - "Result < () , Box < (dyn std :: error :: Error + Send + Sync) > >"; + "Result < () , Box < (dyn std :: error :: Error + Send + Sync) >>"; pub const RES_RET_TY_LT: &str = - "Result < Vec < User > , Box < (dyn std :: error :: Error + Send + Sync + 'a) > >"; + "Result < Vec < User > , Box < (dyn std :: error :: Error + Send + Sync + 'a) >>"; pub const RES_VOID_RET_TY_LT: &str = - "Result < () , Box < (dyn std :: error :: Error + Send + Sync + 'a) > >"; + "Result < () , Box < (dyn std :: error :: Error + Send + Sync + 'a) >>"; pub const OPT_RET_TY_LT: &str = - "Result < Option < User > , Box < (dyn std :: error :: Error + Send + Sync + 'a) > >"; + "Result < Option < User > , Box < (dyn std :: error :: Error + Send + Sync + 'a) >>"; +pub const I64_RET_TY: &str = "Result < i64 , Box < (dyn std :: error :: Error + Send + Sync) >>"; +pub const I64_RET_TY_LT: &str = + "Result < i64 , Box < (dyn std :: error :: Error + Send + Sync + 'a) >>"; pub const MAPS_TO: &str = "into_results :: < User > ()"; pub const LT_CONSTRAINT: &str = "< 'a "; pub const INPUT_PARAM: &str = "input : I"; +pub const VALUE_PARAM: &str = "& 'a dyn canyon_sql :: core :: QueryParameter < 'a >"; pub const WITH_WHERE_BOUNDS: &str = "where I : canyon_sql :: core :: DbConnection + Send + 'a "; + +pub const FIND_BY_PK_ERR_NO_PK: &str = "You can't use the 'find_by_pk' associated function on a \ + CanyonEntity that does not have a #[primary_key] annotation. \ + If you need to perform an specific search, use the Querybuilder instead."; diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index f94ccb60..57116e86 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -53,7 +53,7 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri // let delete_with_querybuilder = generate_delete_query_tokens(ty, table_schema_data); // delete_ops_tokens.extend(delete_with_querybuilder); - + delete_ops_tokens } @@ -170,9 +170,6 @@ mod __details { mod delete_tests { use super::__details::*; use crate::query_operations::consts::*; - - - const DELETE_MOCK_STMT: &str = "DELETE FROM public.user WHERE user.id = 1"; diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index 2c5af716..fa9984af 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -81,11 +81,11 @@ fn generate_find_by_foreign_key_tokens( ); let quoted_method_signature: TokenStream = quote! { async fn #method_name_ident<'a>(&self) -> - Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> }; let quoted_with_method_signature: TokenStream = quote! { async fn #method_name_ident_with<'a, I>(&self, input: I) -> - Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> where I: canyon_sql::core::DbConnection + Send + 'a }; @@ -154,15 +154,15 @@ fn generate_find_by_reverse_foreign_key_tokens( ); let quoted_method_signature: TokenStream = quote! { async fn #method_name_ident<'a, R, F>(value: &F) - -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> where R: RowMapper, - F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send + F: canyon_sql::crud::bounds::ForeignKeyable + Send + Sync }; let quoted_with_method_signature: TokenStream = quote! { async fn #method_name_ident_with<'a, R, F, I> (value: &F, input: I) - -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> where R: RowMapper, - F: canyon_sql::crud::bounds::ForeignKeyable + Sync + Send, + F: canyon_sql::crud::bounds::ForeignKeyable + Send + Sync, I: canyon_sql::core::DbConnection + Send + 'a }; diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs index 14c88b37..cd777196 100644 --- a/canyon_macros/src/query_operations/macro_template.rs +++ b/canyon_macros/src/query_operations/macro_template.rs @@ -8,7 +8,7 @@ pub enum TransactionMethod { QueryOne, QueryOneFor, QueryRows, - Execute + Execute, } impl ToTokens for TransactionMethod { @@ -115,7 +115,7 @@ impl MacroOperationBuilder { return quote! {}; } - let mut generics = quote!{ < }; + let mut generics = quote! { < }; if self.lifetime { generics.extend(quote! { 'a, }); @@ -126,7 +126,7 @@ impl MacroOperationBuilder { if self.input_param.is_some() { generics.extend(quote! { I }); } - generics.extend(quote!{ > }); + generics.extend(quote! { > }); generics } @@ -193,12 +193,12 @@ impl MacroOperationBuilder { pub fn type_is_row_mapper(mut self) -> Self { self.type_is_row_mapper = true; let ret_ty = self.get_organic_ret_ty(); - self.where_clause_bounds.push(quote!{ + self.where_clause_bounds.push(quote! { R: RowMapper }); self } - + fn get_organic_ret_ty(&self) -> TokenStream { if let Some(return_ty_ts) = &self.return_type_ts { let rt_ts = return_ty_ts; diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index 35a66299..8c3c0bc0 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -22,7 +22,7 @@ pub fn impl_crud_operations_trait_for_struct( table_schema_data: String, ) -> proc_macro::TokenStream { let mut crud_ops_tokens = TokenStream::new(); - + let ty = macro_data.ty; let mapper_ty = macro_data .retrieve_mapping_target_type() diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 2c2ebf69..7defb113 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -1,51 +1,54 @@ -use proc_macro2::TokenStream; -use quote::quote; - +use crate::query_operations::consts; use crate::utils::macro_tokens::MacroTokens; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; -// The API for export to the real macro implementation the generated macros for the READ operations +/// Facade function that acts as the unique API for export to the real macro implementation +/// of all the generated macros for the READ operations pub fn generate_read_operations_tokens( macro_data: &MacroTokens<'_>, table_schema_data: &String, ) -> TokenStream { - use __details::{count_generators::*, find_all_generators::*}; - let ty = macro_data.ty; let mapper_ty = macro_data .retrieve_mapping_target_type() - .expect("Expected mapping target ")// TODO: return Err(...) + .expect("Expected mapping target ") // TODO: return Err(...) .unwrap_or_else(|| ty.clone()); + let find_all_tokens = generate_find_all_operations_tokens(ty, &mapper_ty, table_schema_data); + let count_tokens = generate_count_operations_tokens(ty, table_schema_data); + let find_by_pk_tokens = generate_find_by_pk_operations_tokens(macro_data, table_schema_data); + let read_querybuilder_ops = generate_select_querybuilder_tokens(&mapper_ty, table_schema_data); + + quote! { + #find_all_tokens + #read_querybuilder_ops + #count_tokens + #find_by_pk_tokens + } +} + +fn generate_find_all_operations_tokens( + ty: &Ident, + mapper_ty: &Ident, + table_schema_data: &String, +) -> TokenStream { let fa_stmt = format!("SELECT * FROM {table_schema_data}"); // TODO: bring the helper and convert the SELECT * into the SELECT col_name, col_name2...? // TODO: remember that this queries statements must be autogenerated by some automatic procedure - let find_all = create_find_all_macro(ty, &mapper_ty, &fa_stmt); - let find_all_with = create_find_all_with_macro(&fa_stmt, &mapper_ty); - - let count_stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); - let count = create_count_macro(ty, &count_stmt); - let count_with = create_count_with_macro(ty, &count_stmt); - - let find_by_pk_complex_tokens = generate_find_by_pk_tokens(macro_data, table_schema_data); - - let read_querybuilder_ops = generate_select_querybuilder_tokens(&mapper_ty, table_schema_data); + let find_all = __details::find_all_generators::create_find_all_macro(ty, mapper_ty, &fa_stmt); + let find_all_with = + __details::find_all_generators::create_find_all_with_macro(mapper_ty, &fa_stmt); quote! { #find_all #find_all_with - - #count - #count_with - - #find_by_pk_complex_tokens - - #read_querybuilder_ops } } fn generate_select_querybuilder_tokens( - mapper_ty: &syn::Ident, + mapper_ty: &Ident, table_schema_data: &String, ) -> TokenStream { quote! { @@ -79,57 +82,53 @@ fn generate_select_querybuilder_tokens( } } -/// Generates the TokenStream for build the __find_by_pk() CRUD operation -fn generate_find_by_pk_tokens( +fn generate_count_operations_tokens(ty: &Ident, table_schema_data: &String) -> TokenStream { + let count_stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); + let count = __details::count_generators::create_count_macro(ty, &count_stmt); + let count_with = __details::count_generators::create_count_with_macro(ty, &count_stmt); + + quote! { + #count + #count_with + } +} + +fn generate_find_by_pk_operations_tokens( macro_data: &MacroTokens<'_>, table_schema_data: &String, ) -> TokenStream { - use __details::pk_generators::*; - let ty = macro_data.ty; - let mapper_ty = macro_data + let mapper_ty = macro_data .retrieve_mapping_target_type() .expect("Expected mapping target ") .unwrap_or_else(|| ty.clone()); - let pk = macro_data.get_primary_key_annotation().unwrap_or_default(); - let stmt = format!("SELECT * FROM {table_schema_data} WHERE {pk} = $1"); - - // Disabled if there's no `primary_key` annotation - if pk.is_empty() { - return quote! { - async fn find_by_pk<'a>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) - -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> - { - Err( + let pk = macro_data.get_primary_key_annotation(); + let no_pk_runtime_err = if pk.is_some() { + None + } else { + let err_msg = consts::FIND_BY_PK_ERR_NO_PK; + Some(quote! { + Err( std::io::Error::new( std::io::ErrorKind::Unsupported, - "You can't use the 'find_by_pk' associated function on a \ - CanyonEntity that does not have a #[primary_key] annotation. \ - If you need to perform an specific search, use the Querybuilder instead." + #err_msg, ).into_inner().unwrap() ) - } - - async fn find_by_pk_with<'a, I>( - value: &'a dyn canyon_sql::core::QueryParameter<'a>, - input: I - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> - where I: canyon_sql::core::DbConnection + Send + 'a - { - Err( - std::io::Error::new( - std::io::ErrorKind::Unsupported, - "You can't use the 'find_by_pk_with' associated function on a \ - CanyonEntity that does not have a #[primary_key] annotation. \ - If you need to perform an specific search, use the Querybuilder instead." - ).into_inner().unwrap() - ) - } - }; - } - - let find_by_pk = create_find_by_pk_macro(ty, &mapper_ty, &stmt); - let find_by_pk_with = create_find_by_pk_with(&mapper_ty, &stmt); + }) + }; + let stmt = format!( + "SELECT * FROM {table_schema_data} WHERE {} = $1", + pk.unwrap_or_default() + ); + + let find_by_pk = __details::find_by_pk_generators::create_find_by_pk_macro( + ty, + &mapper_ty, + &stmt, + &no_pk_runtime_err, + ); + let find_by_pk_with = + __details::find_by_pk_generators::create_find_by_pk_with(ty, &stmt, &no_pk_runtime_err); quote! { #find_by_pk @@ -145,10 +144,10 @@ mod __details { use super::*; use proc_macro2::TokenStream; - pub fn create_find_all_macro(ty: &syn::Ident, mapper_ty: &syn::Ident, stmt: &str) -> TokenStream { + pub fn create_find_all_macro(ty: &Ident, mapper_ty: &Ident, stmt: &str) -> TokenStream { quote! { async fn find_all() - -> Result, Box<(dyn std::error::Error + Sync + Send)>> { + -> Result, Box<(dyn std::error::Error + Send + Sync)>> { <#ty as canyon_sql::core::Transaction>::query::<&str, #mapper_ty>( #stmt, &[], @@ -158,10 +157,10 @@ mod __details { } } - pub fn create_find_all_with_macro(stmt: &str, mapper_ty: &syn::Ident) -> TokenStream { + pub fn create_find_all_with_macro(mapper_ty: &Ident, stmt: &str) -> TokenStream { quote! { async fn find_all_with<'a, I>(input: I) - -> Result, Box<(dyn std::error::Error + Sync + Send)>> + -> Result, Box<(dyn std::error::Error + Send + Sync)>> where I: canyon_sql::core::DbConnection + Send + 'a { @@ -206,7 +205,7 @@ mod __details { let result_handling = generate_count_manual_result_handling(ty); quote! { - async fn count() -> Result> { + async fn count() -> Result> { let res = <#ty as canyon_sql::core::Transaction>::query_rows(#stmt, &[], "") .await?; match res { @@ -220,7 +219,7 @@ mod __details { let result_handling = generate_count_manual_result_handling(ty); quote! { - async fn count_with<'a, I>(input: I) -> Result> + async fn count_with<'a, I>(input: I) -> Result> where I: canyon_sql::core::DbConnection + Send + 'a { let res = input.query_rows(#stmt, &[]).await?; @@ -233,32 +232,57 @@ mod __details { } } - pub mod pk_generators { - use proc_macro2::TokenStream; + pub mod find_by_pk_generators { use super::*; + use proc_macro2::TokenStream; - pub fn create_find_by_pk_macro(ty: &Ident, mapper_ty: &syn::Ident, stmt: &str) -> TokenStream { - quote! { - async fn find_by_pk<'a>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) - -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> - { + pub fn create_find_by_pk_macro( + ty: &Ident, + mapper_ty: &syn::Ident, + stmt: &str, + pk_runtime_error: &Option, + ) -> TokenStream { + let body = if pk_runtime_error.is_none() { + quote! { <#ty as canyon_sql::core::Transaction>::query_one::< &str, &[&'a (dyn QueryParameter<'a>)], #mapper_ty >(#stmt, &vec![value], "").await } + } else { + quote! { #pk_runtime_error } + }; + + quote! { + async fn find_by_pk<'a>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) + -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + { + #body + } } } - pub fn create_find_by_pk_with(mapper_ty: &syn::Ident, stmt: &str) -> TokenStream { + pub fn create_find_by_pk_with( + mapper_ty: &syn::Ident, + stmt: &str, + pk_runtime_error: &Option, + ) -> TokenStream { + let body = if pk_runtime_error.is_none() { + quote! { + input.query_one::<#mapper_ty>(#stmt, &vec![value]).await + } + } else { + quote! { #pk_runtime_error } + }; + quote! { async fn find_by_pk_with<'a, I>(value: &'a dyn canyon_sql::core::QueryParameter<'a>, input: I) - -> Result, Box<(dyn std::error::Error + Sync + Send + 'a)>> + -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> where I: canyon_sql::core::DbConnection + Send + 'a { - input.query_one::<#mapper_ty>(#stmt, &vec![value]).await + #body } } } @@ -267,89 +291,91 @@ mod __details { #[cfg(test)] mod macro_builder_read_ops_tests { - use super::__details::{count_generators::*, find_all_generators::*, pk_generators::*}; - use crate::query_operations::consts::*; - - // const SELECT_ALL_STMT: &str = "SELECT * FROM public.user"; // TODO: introduce the const_format crate - // const COUNT_STMT: &str = "SELECT COUNT(*) FROM public.user"; - // const FIND_BY_PK_STMT: &str = "SELECT * FROM public.user WHERE id = $1"; - - // #[test] - // fn test_macro_builder_find_all() { - // let find_all_builder = create_find_all_macro( - // &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - // SELECT_ALL_STMT, - // ); - // let find_all = find_all_builder.to_string(); - // - // assert!(find_all.contains("async fn find_all")); - // assert!(find_all.contains(RES_RET_TY)); - // } - // - // #[test] - // fn test_macro_builder_find_all_with() { - // let find_all_builder = create_find_all_with_macro( - // SELECT_ALL_STMT, - // ); - // let find_all_with = find_all_builder.to_string(); - // - // assert!(find_all_with.contains("async fn find_all_with")); - // assert!(find_all_with.contains(RES_RET_TY_LT)); - // assert!(find_all_with.contains(LT_CONSTRAINT)); - // assert!(find_all_with.contains(WITH_WHERE_BOUNDS)); - // } - // - // #[test] - // fn test_macro_builder_count() { - // let count_builder = create_count_macro( - // &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - // COUNT_STMT, - // ); - // let count = count_builder.to_string(); - // - // assert!(count.contains("async fn count")); - // assert!(count.contains("Result < i64")); - // } - // - // #[test] - // fn test_macro_builder_count_with() { - // let count_with_builder = create_count_with_macro( - // &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - // COUNT_STMT, - // ); - // let count_with = count_with_builder.to_string(); - // - // assert!(count_with.contains("async fn count_with")); - // assert!(count_with.contains("Result < i64")); - // assert!(count_with.contains(LT_CONSTRAINT)); - // assert!(count_with.contains(INPUT_PARAM)); - // } - // - // #[test] - // fn test_macro_builder_find_by_pk() { - // let find_by_pk_builder = create_find_by_pk_macro( - // &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - // &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - // FIND_BY_PK_STMT, - // ); - // let find_by_pk = find_by_pk_builder.generate_tokens().to_string(); - // - // assert!(find_by_pk.contains("async fn find_by_pk")); - // assert!(find_by_pk.contains(LT_CONSTRAINT)); - // assert!(find_by_pk.contains(OPT_RET_TY_LT)); - // } - // - // #[test] - // fn test_macro_builder_find_by_pk_with() { - // let find_by_pk_with_builder = create_find_by_pk_with( - // &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - // FIND_BY_PK_STMT, - // ); - // let find_by_pk_with = find_by_pk_with_builder.generate_tokens().to_string(); - // - // assert!(find_by_pk_with.contains("async fn find_by_pk_with")); - // assert!(find_by_pk_with.contains(LT_CONSTRAINT)); - // assert!(find_by_pk_with.contains(INPUT_PARAM)); - // assert!(find_by_pk_with.contains(OPT_RET_TY_LT)); - // } + use super::__details::{count_generators::*, find_all_generators::*}; + + use crate::query_operations::consts; + use crate::query_operations::read::__details::find_by_pk_generators::{ + create_find_by_pk_macro, create_find_by_pk_with, + }; + use proc_macro2::Ident; + + const SELECT_ALL_STMT: &str = "SELECT * FROM public.user"; // TODO: introduce the const_format crate + const COUNT_STMT: &str = "SELECT COUNT(*) FROM public.user"; + const FIND_BY_PK_STMT: &str = "SELECT * FROM public.user WHERE id = $1"; + + #[test] + fn test_create_find_all_macro() { + let ty = syn::parse_str::("User").unwrap(); + let mapper_ty = syn::parse_str::("User").unwrap(); + let tokens = create_find_all_macro(&ty, &mapper_ty, SELECT_ALL_STMT); + let generated = tokens.to_string(); + + assert!(generated.contains("async fn find_all")); + assert!(generated.contains(consts::RES_RET_TY)); + assert!(generated.contains(SELECT_ALL_STMT)); + } + + #[test] + fn test_create_find_all_with_macro() { + let mapper_ty = syn::parse_str::("User").unwrap(); + let tokens = create_find_all_with_macro(&mapper_ty, SELECT_ALL_STMT); + let generated = tokens.to_string(); + + assert!(generated.contains("async fn find_all_with")); + assert!(generated.contains(consts::RES_RET_TY)); + assert!(generated.contains(consts::LT_CONSTRAINT)); + assert!(generated.contains(consts::INPUT_PARAM)); + assert!(generated.contains(SELECT_ALL_STMT)); + } + + #[test] + fn test_create_count_macro() { + let ty = syn::parse_str::("User").unwrap(); + let mapper_ty = syn::parse_str::("User").unwrap(); + let tokens = create_count_macro(&ty, COUNT_STMT); + let generated = tokens.to_string(); + + assert!(generated.contains("async fn count")); + assert!(generated.contains(consts::I64_RET_TY)); + assert!(generated.contains(COUNT_STMT)); + } + + #[test] + fn test_create_count_with_macro() { + let ty = syn::parse_str::("User").unwrap(); + let tokens = create_count_with_macro(&ty, COUNT_STMT); + let generated = tokens.to_string(); + + assert!(generated.contains("async fn count_with")); + assert!(generated.contains(consts::I64_RET_TY_LT)); + assert!(generated.contains(COUNT_STMT)); + assert!(generated.contains(consts::LT_CONSTRAINT)); + assert!(generated.contains(consts::INPUT_PARAM)); + } + + #[test] + fn test_create_find_by_pk_macro() { + let ty = syn::parse_str::("User").unwrap(); + let mapper_ty = syn::parse_str::("User").unwrap(); + let pk_runtime_error = None; + let tokens = create_find_by_pk_macro(&ty, &mapper_ty, FIND_BY_PK_STMT, &pk_runtime_error); + let generated = tokens.to_string(); + + assert!(generated.contains("async fn find_by_pk")); + assert!(generated.contains(consts::OPT_RET_TY_LT)); + assert!(generated.contains(FIND_BY_PK_STMT)); + } + + #[test] + fn test_create_find_by_pk_with_macro() { + let mapper_ty = syn::parse_str::("User").unwrap(); + let pk_runtime_error = None; + let tokens = create_find_by_pk_with(&mapper_ty, FIND_BY_PK_STMT, &pk_runtime_error); + let generated = tokens.to_string(); + + assert!(generated.contains("async fn find_by_pk_with")); + assert!(generated.contains(consts::OPT_RET_TY_LT)); + assert!(generated.contains(consts::LT_CONSTRAINT)); + assert!(generated.contains(FIND_BY_PK_STMT)); + } } diff --git a/canyon_macros/src/utils/canyon_crud_attribute.rs b/canyon_macros/src/utils/canyon_crud_attribute.rs index 78e68ffc..1ce08c34 100644 --- a/canyon_macros/src/utils/canyon_crud_attribute.rs +++ b/canyon_macros/src/utils/canyon_crud_attribute.rs @@ -25,6 +25,8 @@ impl Parse for CanyonCrudAttribute { // Parse the argument value let name = input.parse()?; - Ok(Self { maps_to: Some(name) }) + Ok(Self { + maps_to: Some(name), + }) } -} \ No newline at end of file +} diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index 9e583464..aea10788 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -29,7 +29,7 @@ impl<'a> MacroTokens<'a> { }, } } - + // TODO: this must be refactored in order to avoid to make the operation everytime that // this method is queried. The trick w'd be to have a map to relate the entries. pub fn retrieve_mapping_target_type(&self) -> Result, syn::Error> { diff --git a/canyon_macros/src/utils/mod.rs b/canyon_macros/src/utils/mod.rs index 4e267ac5..883f2c0a 100644 --- a/canyon_macros/src/utils/mod.rs +++ b/canyon_macros/src/utils/mod.rs @@ -1,4 +1,4 @@ +mod canyon_crud_attribute; pub mod function_parser; pub mod helpers; pub mod macro_tokens; -mod canyon_crud_attribute; diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index 59f827c0..292a795f 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -38,16 +38,23 @@ impl Migrations { ); let mut migrations_processor = MigrationsProcessor::default(); - let mut db_conn = canyon_core::connection::get_database_connection_by_ds(Some(&datasource.name)) - .await - .unwrap_or_else(|_| panic!("Unable to get a database connection on the migrations processor for: {:?}", datasource.name)); + let mut db_conn = + canyon_core::connection::get_database_connection_by_ds(Some(&datasource.name)) + .await + .unwrap_or_else(|_| { + panic!( + "Unable to get a database connection on the migrations processor for: {:?}", + datasource.name + ) + }); let canyon_entities = CANYON_REGISTER_ENTITIES.lock().unwrap().to_vec(); let canyon_memory = CanyonMemory::remember(datasource, &canyon_entities).await; // Tracked entities that must be migrated whenever Canyon starts let schema_status = - Self::fetch_database(&datasource.name, &mut db_conn, datasource.get_db_type()).await; + Self::fetch_database(&datasource.name, &mut db_conn, datasource.get_db_type()) + .await; let database_tables_schema_info = Self::map_rows(schema_status, datasource.get_db_type()); diff --git a/canyon_migrations/src/migrations/memory.rs b/canyon_migrations/src/migrations/memory.rs index 21e3e318..efadbff0 100644 --- a/canyon_migrations/src/migrations/memory.rs +++ b/canyon_migrations/src/migrations/memory.rs @@ -66,9 +66,14 @@ impl CanyonMemory { ) -> Self { let datasource_name = &datasource.name; let mut db_conn = - canyon_core::connection::get_database_connection_by_ds(Some(datasource_name)).await - .unwrap_or_else(|_| panic!("Unable to get a database connection on the migrations processor for: {:?}", datasource_name)); - + canyon_core::connection::get_database_connection_by_ds(Some(datasource_name)) + .await + .unwrap_or_else(|_| { + panic!( + "Unable to get a database connection on the migrations processor for: {:?}", + datasource_name + ) + }); // Creates the memory table if not exists Self::create_memory(&datasource.name, &mut db_conn, &datasource.get_db_type()).await; diff --git a/canyon_migrations/src/migrations/processor.rs b/canyon_migrations/src/migrations/processor.rs index d6266204..1094c7fd 100644 --- a/canyon_migrations/src/migrations/processor.rs +++ b/canyon_migrations/src/migrations/processor.rs @@ -585,9 +585,15 @@ impl MigrationsProcessor { for query_to_execute in datasource.1 { let datasource_name = datasource.0; - let db_conn = canyon_core::connection::get_database_connection_by_ds(Some(datasource_name)) - .await - .unwrap_or_else(|_| panic!("Unable to get a database connection on the migrations processor for: {:?}", datasource_name)); + let db_conn = + canyon_core::connection::get_database_connection_by_ds(Some(datasource_name)) + .await + .unwrap_or_else(|_| { + panic!( + "Unable to get a database connection on the migrations processor for: {:?}", + datasource_name + ) + }); let res = Self::query_rows(query_to_execute, [], db_conn).await; diff --git a/tests/crud/init_mssql.rs b/tests/crud/init_mssql.rs index c2effd52..3534f457 100644 --- a/tests/crud/init_mssql.rs +++ b/tests/crud/init_mssql.rs @@ -26,13 +26,14 @@ fn initialize_sql_server_docker_instance() { "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true;Encrypt=true"; canyon_sql::runtime::futures::executor::block_on(async { - let mut config = Config::from_ado_string(CONN_STR) - .expect("could not parse ado string"); + let mut config = Config::from_ado_string(CONN_STR).expect("could not parse ado string"); config.encryption(EncryptionLevel::NotSupported); - let tcp = TcpStream::connect(config.get_addr()).await + let tcp = TcpStream::connect(config.get_addr()) + .await .expect("could not connect to stream 1"); - let tcp2 = TcpStream::connect(config.get_addr()).await + let tcp2 = TcpStream::connect(config.get_addr()) + .await .expect("could not connect to stream 2"); tcp.set_nodelay(true).ok(); diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index 884203fe..cf526116 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -1,80 +1,80 @@ -#[cfg(feature = "mysql")] -use crate::constants::MYSQL_DS; -#[cfg(feature = "mssql")] -use crate::constants::SQL_SERVER_DS; - -/// Tests for the QueryBuilder available operations within Canyon. -/// -/// QueryBuilder are the way of obtain more flexibility that with -/// the default generated queries, essentially for build the queries -/// with the SQL filters -/// -use canyon_sql::{ - crud::CrudOperations, - query::{operators::Comp, operators::Like, ops::QueryBuilder}, -}; - -use crate::tests_models::league::*; -use crate::tests_models::player::*; -use crate::tests_models::tournament::*; - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[canyon_sql::macros::canyon_tokio_test] -fn test_generated_sql_by_the_select_querybuilder() { - let select_with_joins = League::select_query() - .inner_join("tournament", "league.id", "tournament.league_id") - .left_join("team", "tournament.id", "player.tournament_id") - .r#where(LeagueFieldValue::id(&7), Comp::Gt) - .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) - .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); - // .query() - // .await; - // NOTE: We don't have in the docker the generated relationships - // with the joins, so for now, we are just going to check that the - // generated SQL by the SelectQueryBuilder is the expected - assert_eq!( - select_with_joins.read_sql(), - "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" - ) -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder() { - // Find all the leagues with ID less or equals that 7 - // and where it's region column value is equals to 'Korea' - let filtered_leagues_result: Result, _> = League::select_query() - .r#where(LeagueFieldValue::id(&50), Comp::LtEq) - .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) - .query(MYSQL_DS) - .await; - - let filtered_leagues: Vec = filtered_leagues_result.unwrap(); - assert!(!filtered_leagues.is_empty()); - - let league_idx_0 = filtered_leagues.first().unwrap(); - assert_eq!(league_idx_0.id, 34); - assert_eq!(league_idx_0.region, "KOREA"); -} - -/// Builds a new SQL statement for retrieves entities of the `T` type, filtered -/// with the parameters that modifies the base SQL to SELECT * FROM -#[cfg(feature = "postgres")] -#[canyon_sql::macros::canyon_tokio_test] -fn test_crud_find_with_querybuilder_and_fulllike() { - // Find all the leagues with "LC" in their name - let filtered_leagues_result = - League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Full); - - assert_eq!( - filtered_leagues_result.read_sql(), - "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" - ) -} - +// #[cfg(feature = "mysql")] +// use crate::constants::MYSQL_DS; +// #[cfg(feature = "mssql")] +// use crate::constants::SQL_SERVER_DS; +// +// /// Tests for the QueryBuilder available operations within Canyon. +// /// +// /// QueryBuilder are the way of obtain more flexibility that with +// /// the default generated queries, essentially for build the queries +// /// with the SQL filters +// /// +// use canyon_sql::{ +// crud::CrudOperations, +// query::{operators::Comp, operators::Like, ops::QueryBuilder}, +// }; +// +// use crate::tests_models::league::*; +// use crate::tests_models::player::*; +// use crate::tests_models::tournament::*; +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_generated_sql_by_the_select_querybuilder() { +// let select_with_joins = League::select_query() +// .inner_join("tournament", "league.id", "tournament.league_id") +// .left_join("team", "tournament.id", "player.tournament_id") +// .r#where(LeagueFieldValue::id(&7), Comp::Gt) +// .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) +// .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); +// // .query() +// // .await; +// // NOTE: We don't have in the docker the generated relationships +// // with the joins, so for now, we are just going to check that the +// // generated SQL by the SelectQueryBuilder is the expected +// assert_eq!( +// select_with_joins.read_sql(), +// "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" +// ) +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder() { +// // Find all the leagues with ID less or equals that 7 +// // and where it's region column value is equals to 'Korea' +// let filtered_leagues_result: Result, _> = League::select_query() +// .r#where(LeagueFieldValue::id(&50), Comp::LtEq) +// .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) +// .query(MYSQL_DS) +// .await; +// +// let filtered_leagues: Vec = filtered_leagues_result.unwrap(); +// assert!(!filtered_leagues.is_empty()); +// +// let league_idx_0 = filtered_leagues.first().unwrap(); +// assert_eq!(league_idx_0.id, 34); +// assert_eq!(league_idx_0.region, "KOREA"); +// } +// +// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered +// /// with the parameters that modifies the base SQL to SELECT * FROM +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_find_with_querybuilder_and_fulllike() { +// // Find all the leagues with "LC" in their name +// let filtered_leagues_result = +// League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Full); +// +// assert_eq!( +// filtered_leagues_result.read_sql(), +// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" +// ) +// } +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mssql")] @@ -83,13 +83,13 @@ fn test_crud_find_with_querybuilder_and_fulllike() { // // Find all the leagues with "LC" in their name // let filtered_leagues_result = // League::select_query_with(SQL_SERVER_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// +// // assert_eq!( // filtered_leagues_result.read_sql(), // "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" // ) // } - +// // /// Builds a new SQL statement for retrieves entities of the `T` type, filtered // /// with the parameters that modifies the base SQL to SELECT * FROM // #[cfg(feature = "mysql")] diff --git a/tests/crud/read_operations.rs b/tests/crud/read_operations.rs index 9f21e282..7f34f637 100644 --- a/tests/crud/read_operations.rs +++ b/tests/crud/read_operations.rs @@ -56,7 +56,6 @@ fn test_crud_find_all_with_mysql() { assert!(!find_all_result.unwrap().is_empty()); } - /// Tests the behaviour of a SELECT * FROM {table_name} WHERE = , where the pk is /// defined with the #[primary_key] attribute over some field of the type. /// @@ -142,10 +141,7 @@ fn test_crud_count_operation() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_count_with_operation_mssql() { assert_eq!( - League::find_all_with(SQL_SERVER_DS) - .await - .unwrap() - .len() as i64, + League::find_all_with(SQL_SERVER_DS).await.unwrap().len() as i64, League::count_with(SQL_SERVER_DS).await.unwrap() ); } @@ -156,10 +152,7 @@ fn test_crud_count_with_operation_mssql() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_count_with_operation_mysql() { assert_eq!( - League::find_all_with(MYSQL_DS) - .await - .unwrap() - .len() as i64, + League::find_all_with(MYSQL_DS).await.unwrap().len() as i64, League::count_with(MYSQL_DS).await.unwrap() ); } diff --git a/tests/tests_models/league.rs b/tests/tests_models/league.rs index 1d4f9934..b6476bb5 100644 --- a/tests/tests_models/league.rs +++ b/tests/tests_models/league.rs @@ -1,7 +1,8 @@ use canyon_sql::macros::*; #[derive(Debug, Fields, CanyonCrud, CanyonMapper, ForeignKeyable, Eq, PartialEq)] -#[canyon_crud(maps_to = League)] // canyon_crud mapping to Self is already the default behaviour +#[canyon_crud(maps_to = League)] +// canyon_crud mapping to Self is already the default behaviour // just here for demonstration purposes #[canyon_entity(table_name = "league", /* schema = "public"*/)] pub struct League { @@ -12,4 +13,4 @@ pub struct League { name: String, region: String, image_url: String, -} \ No newline at end of file +} From da7ff8bdb1bf71055be803e64c880d799bccc0df Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 30 Apr 2025 17:27:47 +0200 Subject: [PATCH 091/155] feat(wip): making the querybuilder able to receive str and &str as DS params --- canyon_core/src/connection/db_connector.rs | 66 +++ canyon_crud/src/crud.rs | 9 +- .../src/query_elements/query_builder.rs | 124 +++-- canyon_macros/src/query_operations/read.rs | 18 +- tests/crud/querybuilder_operations.rs | 459 +++++++++--------- 5 files changed, 380 insertions(+), 296 deletions(-) diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index 2cdb3039..a7c5b0f3 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -65,6 +65,72 @@ pub trait DbConnection { fn get_database_type(&self) -> Result>; } +/// This impl of [` DbConnection` ] for [`&str`] allows the client to use the exposed input types +/// on the public API that works with a generic parameter to refer to a database connection +/// directly with an [`&str`] that must match one of the datasources defined +/// within the user config file +impl DbConnection for str { + async fn query_rows<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + let conn = get_database_connection_by_ds(Some(self)).await?; + conn.query_rows(stmt, params).await + } + + async fn query<'a, S, R>( + &self, + stmt: S, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> Result, Box<(dyn Error + Send + Sync)>> + where + S: AsRef + Display + Send, + R: RowMapper, + Vec: FromIterator<::Output>, + { + let conn = get_database_connection_by_ds(Some(self)).await?; + conn.query(stmt, params).await + } + + async fn query_one<'a, R>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result, Box<(dyn Error + Send + Sync)>> + where + R: RowMapper, + { + let sane_ds_name = if !self.is_empty() { Some(self) } else { None }; + let conn = get_database_connection_by_ds(sane_ds_name).await?; + conn.query_one::(stmt, params).await + } + + async fn query_one_for<'a, T: FromSqlOwnedValue>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + let sane_ds_name = if !self.is_empty() { Some(self) } else { None }; + let conn = get_database_connection_by_ds(sane_ds_name).await?; + conn.query_one_for(stmt, params).await + } + + async fn execute<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + let sane_ds_name = if !self.is_empty() { Some(self) } else { None }; + let conn = get_database_connection_by_ds(sane_ds_name).await?; + conn.execute(stmt, params).await + } + + fn get_database_type(&self) -> Result> { + Ok(find_datasource_by_name_or_try_default(Some(self))?.get_db_type()) + } +} + /// This impl of [` DbConnection` ] for [`&str`] allows the client to use the exposed input types /// on the public API that works with a generic parameter to refer to a database connection /// directly with an [`&str`] that must match one of the datasources defined diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 116bd5ff..2e6aa417 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -36,9 +36,14 @@ where where I: DbConnection + Send + 'a; - fn select_query<'a>() -> SelectQueryBuilder<'a, R>; + fn select_query<'a>( + ) -> Result, Box<(dyn Error + Send + Sync + 'a)>>; - fn select_query_with<'a>(database_type: DatabaseType) -> SelectQueryBuilder<'a, R>; + fn select_query_with<'a, I>( + input: &'a I, + ) -> Result, Box<(dyn Error + Send + Sync + 'a)>> + where + I: DbConnection + Send + 'a + ?Sized; fn count() -> impl Future>> + Send; diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index 72d6d347..a36846ba 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -4,17 +4,14 @@ use crate::{ }; use canyon_core::connection::database_type::DatabaseType; use canyon_core::connection::db_connector::DbConnection; -use canyon_core::{mapper::RowMapper, query_parameters::QueryParameter, transaction::Transaction}; +use canyon_core::{mapper::RowMapper, query_parameters::QueryParameter}; +use std::error::Error; use std::marker::PhantomData; /// Contains the elements that makes part of the formal declaration /// of the behaviour of the Canyon-SQL QueryBuilder pub mod ops { - use canyon_core::{ - mapper::RowMapper, query_parameters::QueryParameter, transaction::Transaction, - }; - - use crate::crud::CrudOperations; + use canyon_core::query_parameters::QueryParameter; pub use super::*; @@ -120,40 +117,36 @@ pub mod ops { } /// Type for construct more complex queries than the classical CRUD ones. -pub struct QueryBuilder<'a, R: RowMapper> { +pub struct QueryBuilder<'a, I: DbConnection + ?Sized, R: RowMapper> { // query: Query<'a>, sql: String, params: Vec<&'a dyn QueryParameter<'a>>, database_type: DatabaseType, + input: &'a I, pd: PhantomData, } -unsafe impl<'a, R: RowMapper> Sync for QueryBuilder<'a, R> {} +unsafe impl<'a, I: DbConnection + ?Sized, R: RowMapper> Sync for QueryBuilder<'a, I, R> {} -impl<'a, R: RowMapper> QueryBuilder<'a, R> { - pub fn new(sql: String, database_type: DatabaseType) -> Self { - Self { +impl<'a, I: DbConnection + ?Sized, R: RowMapper> QueryBuilder<'a, I, R> { + pub fn new(sql: String, input: &'a I) -> Result> { + Ok(Self { sql, params: vec![], - database_type, + database_type: input.get_database_type()?, + input, pd: Default::default(), - } + }) } /// Launches the generated query against the database targeted /// by the selected datasource - /// /// TODO: this is not definitive => QueryBuilder -> Query -> Transaction -> RowMapper - pub async fn query( - mut self, - input: I, - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + pub async fn query(mut self) -> Result, Box<(dyn Error + Send + Sync + 'a)>> where Vec: FromIterator<::Output>, { self.sql.push(';'); - - // T::query(&self.sql, &self.params, input).await - input.query(&self.sql, &self.params).await + self.input.query(&self.sql, &self.params).await } pub fn r#where>(&mut self, r#where: Z, op: impl Operator) { @@ -251,29 +244,28 @@ impl<'a, R: RowMapper> QueryBuilder<'a, R> { } } -pub struct SelectQueryBuilder<'a, R: RowMapper> { - _inner: QueryBuilder<'a, R>, +pub struct SelectQueryBuilder<'a, I: DbConnection + ?Sized, R: RowMapper> { + _inner: QueryBuilder<'a, I, R>, } -impl<'a, R: RowMapper> SelectQueryBuilder<'a, R> { +impl<'a, I: DbConnection + ?Sized, R: RowMapper> SelectQueryBuilder<'a, I, R> { /// Generates a new public instance of the [`SelectQueryBuilder`] - pub fn new(table_schema_data: &str, database_type: DatabaseType) -> Self { - Self { - _inner: QueryBuilder::new(format!("SELECT * FROM {table_schema_data}"), database_type), - } + pub fn new( + table_schema_data: &str, + input: &'a I, + ) -> Result> { + Ok(Self { + _inner: QueryBuilder::new(format!("SELECT * FROM {table_schema_data}"), input)?, + }) } - /// Launches the generated query to the database pointed by the - /// selected datasource + /// Launches the generated query to the database pointed by the selected datasource #[inline] - pub async fn query( - self, - input: I, - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + pub async fn query(self) -> Result, Box<(dyn Error + Send + Sync + 'a)>> where Vec: FromIterator<::Output>, { - self._inner.query::(input).await + self._inner.query().await } /// Adds a *LEFT JOIN* SQL statement to the underlying @@ -337,7 +329,9 @@ impl<'a, R: RowMapper> SelectQueryBuilder<'a, R> { } } -impl<'a, R: RowMapper> ops::QueryBuilder<'a> for SelectQueryBuilder<'a, R> { +impl<'a, I: DbConnection + ?Sized, R: RowMapper> ops::QueryBuilder<'a> + for SelectQueryBuilder<'a, I, R> +{ #[inline] fn read_sql(&'a self) -> &'a str { self._inner.sql.as_str() @@ -397,29 +391,28 @@ impl<'a, R: RowMapper> ops::QueryBuilder<'a> for SelectQueryBuilder<'a, R> { /// /// * `set` - To construct a new `SET` clause to determine the columns to /// update with the provided values -pub struct UpdateQueryBuilder<'a, R: RowMapper> { - _inner: QueryBuilder<'a, R>, +pub struct UpdateQueryBuilder<'a, I: DbConnection, R: RowMapper> { + _inner: QueryBuilder<'a, I, R>, } -impl<'a, R: RowMapper> UpdateQueryBuilder<'a, R> { +impl<'a, I: DbConnection, R: RowMapper> UpdateQueryBuilder<'a, I, R> { /// Generates a new public instance of the [`UpdateQueryBuilder`] - pub fn new(table_schema_data: &str, database_type: DatabaseType) -> Self { - Self { - _inner: QueryBuilder::new(format!("UPDATE {table_schema_data}"), database_type), - } + pub fn new( + table_schema_data: &str, + input: &'a I, + ) -> Result> { + Ok(Self { + _inner: QueryBuilder::new(format!("UPDATE {table_schema_data}"), input)?, + }) } - /// Launches the generated query to the database pointed by the - /// selected datasource + /// Launches the generated query to the database pointed by the selected datasource #[inline] - pub async fn query( - self, - input: I, - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + pub async fn query(self, input: I) -> Result, Box<(dyn Error + Send + Sync + 'a)>> where Vec: FromIterator<::Output>, { - self._inner.query::(input).await + self._inner.query().await } /// Creates an SQL `SET` clause to specify the columns that must be updated in the sentence @@ -462,7 +455,7 @@ impl<'a, R: RowMapper> UpdateQueryBuilder<'a, R> { } } -impl<'a, R: RowMapper> ops::QueryBuilder<'a> for UpdateQueryBuilder<'a, R> { +impl<'a, I: DbConnection, R: RowMapper> ops::QueryBuilder<'a> for UpdateQueryBuilder<'a, I, R> { #[inline] fn read_sql(&'a self) -> &'a str { self._inner.sql.as_str() @@ -523,33 +516,32 @@ impl<'a, R: RowMapper> ops::QueryBuilder<'a> for UpdateQueryBuilder<'a, R> { /// /// * `set` - To construct a new `SET` clause to determine the columns to /// update with the provided values -pub struct DeleteQueryBuilder<'a, R: RowMapper> { - _inner: QueryBuilder<'a, R>, +pub struct DeleteQueryBuilder<'a, I: DbConnection, R: RowMapper> { + _inner: QueryBuilder<'a, I, R>, } -impl<'a, R: RowMapper> DeleteQueryBuilder<'a, R> { +impl<'a, I: DbConnection, R: RowMapper> DeleteQueryBuilder<'a, I, R> { /// Generates a new public instance of the [`DeleteQueryBuilder`] - pub fn new(table_schema_data: &str, database_type: DatabaseType) -> Self { - Self { - _inner: QueryBuilder::new(format!("DELETE FROM {table_schema_data}"), database_type), - } + pub fn new( + table_schema_data: &str, + input: &'a I, + ) -> Result> { + Ok(Self { + _inner: QueryBuilder::new(format!("DELETE FROM {table_schema_data}"), input)?, + }) } - /// Launches the generated query to the database pointed by the - /// selected datasource + /// Launches the generated query to the database pointed by the selected datasource #[inline] - pub async fn query( - self, - input: I, - ) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + pub async fn query(self, input: I) -> Result, Box<(dyn Error + Send + Sync + 'a)>> where Vec: FromIterator<::Output>, { - self._inner.query::(input).await + self._inner.query().await } } -impl<'a, R: RowMapper> ops::QueryBuilder<'a> for DeleteQueryBuilder<'a, R> { +impl<'a, I: DbConnection, R: RowMapper> ops::QueryBuilder<'a> for DeleteQueryBuilder<'a, I, R> { #[inline] fn read_sql(&'a self) -> &'a str { self._inner.sql.as_str() diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 7defb113..fe4c7aac 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -59,8 +59,13 @@ fn generate_select_querybuilder_tokens( /// entity but converted to the corresponding database convention, /// unless concrete values are set on the available parameters of the /// `canyon_macro(table_name = "table_name", schema = "schema")` - fn select_query<'a>() -> canyon_sql::query::SelectQueryBuilder<'a, #mapper_ty> { - canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, canyon_sql::connection::DatabaseType::default()) + fn select_query<'a>() + -> Result< + canyon_sql::query::SelectQueryBuilder<'a, str, #mapper_ty>, + Box<(dyn std::error::Error + Send + Sync + 'a)> + > + { + canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, &"") } /// Generates a [`canyon_sql::query::SelectQueryBuilder`] @@ -74,10 +79,13 @@ fn generate_select_querybuilder_tokens( /// The query it's made against the database with the configured datasource /// described in the configuration file, and selected with the [`&str`] /// passed as parameter. - fn select_query_with<'a>(database_type: canyon_sql::connection::DatabaseType) - -> canyon_sql::query::SelectQueryBuilder<'a, #mapper_ty> + fn select_query_with<'a, I>(input: &'a I) + -> Result< + canyon_sql::query::SelectQueryBuilder<'a, I, #mapper_ty>, + Box<(dyn std::error::Error + Send + Sync + 'a)> + > where I: canyon_sql::core::DbConnection + Send + 'a + ?Sized { - canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, database_type) + canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, input) } } } diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index cf526116..0e7d03ed 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -1,226 +1,239 @@ -// #[cfg(feature = "mysql")] -// use crate::constants::MYSQL_DS; -// #[cfg(feature = "mssql")] -// use crate::constants::SQL_SERVER_DS; -// -// /// Tests for the QueryBuilder available operations within Canyon. -// /// -// /// QueryBuilder are the way of obtain more flexibility that with -// /// the default generated queries, essentially for build the queries -// /// with the SQL filters -// /// -// use canyon_sql::{ -// crud::CrudOperations, -// query::{operators::Comp, operators::Like, ops::QueryBuilder}, -// }; -// -// use crate::tests_models::league::*; -// use crate::tests_models::player::*; -// use crate::tests_models::tournament::*; -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_generated_sql_by_the_select_querybuilder() { -// let select_with_joins = League::select_query() -// .inner_join("tournament", "league.id", "tournament.league_id") -// .left_join("team", "tournament.id", "player.tournament_id") -// .r#where(LeagueFieldValue::id(&7), Comp::Gt) -// .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) -// .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); -// // .query() -// // .await; -// // NOTE: We don't have in the docker the generated relationships -// // with the joins, so for now, we are just going to check that the -// // generated SQL by the SelectQueryBuilder is the expected -// assert_eq!( -// select_with_joins.read_sql(), -// "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" -// ) -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder() { -// // Find all the leagues with ID less or equals that 7 -// // and where it's region column value is equals to 'Korea' -// let filtered_leagues_result: Result, _> = League::select_query() -// .r#where(LeagueFieldValue::id(&50), Comp::LtEq) -// .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) -// .query(MYSQL_DS) -// .await; -// -// let filtered_leagues: Vec = filtered_leagues_result.unwrap(); -// assert!(!filtered_leagues.is_empty()); -// -// let league_idx_0 = filtered_leagues.first().unwrap(); -// assert_eq!(league_idx_0.id, 34); -// assert_eq!(league_idx_0.region, "KOREA"); -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_fulllike() { -// // Find all the leagues with "LC" in their name -// let filtered_leagues_result = -// League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" -// ) -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_fulllike_with_mssql() { -// // Find all the leagues with "LC" in their name -// let filtered_leagues_result = -// League::select_query_with(SQL_SERVER_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" -// ) -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_fulllike_with_mysql() { -// // Find all the leagues with "LC" in their name -// let filtered_leagues_result = -// League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"LC"), Like::Full); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR) ,'%')" -// ) -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_leftlike() { -// // Find all the leagues whose name ends with "CK" -// let filtered_leagues_result = -// League::select_query().r#where(LeagueFieldValue::name(&"CK"), Like::Left); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" -// ) -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_leftlike_with_mssql() { -// // Find all the leagues whose name ends with "CK" -// let filtered_leagues_result = -// League::select_query().r#where(LeagueFieldValue::name(&"CK"), Like::Left); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" -// ) -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_leftlike_with_mysql() { -// // Find all the leagues whose name ends with "CK" -// let filtered_leagues_result = -// League::select_query_with(MYSQL_DS).r#where(LeagueFieldValue::name(&"CK"), Like::Left); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR))" -// ) -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_rightlike() { -// // Find all the leagues whose name starts with "LC" -// let filtered_leagues_result = -// League::select_query().r#where(LeagueFieldValue::name(&"LC"), Like::Right); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" -// ) -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_rightlike_with_mssql() { -// // Find all the leagues whose name starts with "LC" -// let filtered_leagues_result = League::select_query_with(SQL_SERVER_DS) -// .r#where(LeagueFieldValue::name(&"LC"), Like::Right); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" -// ) -// } -// -// /// Builds a new SQL statement for retrieves entities of the `T` type, filtered -// /// with the parameters that modifies the base SQL to SELECT * FROM -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_and_rightlike_with_mysql() { -// // Find all the leagues whose name starts with "LC" -// let filtered_leagues_result = -// League::select_query_with(DatabaseType::MySQL).r#where(LeagueFieldValue::name(&"LC"), Like::Right); -// -// assert_eq!( -// filtered_leagues_result.read_sql(), -// "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS CHAR) ,'%')" -// ) -// } -// -// /// Same than the above but with the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_with_mssql() { -// // Find all the players where its ID column value is greater than 50 -// let filtered_find_players = Player::select_query_with(DatabaseType::SqlServer) -// .r#where(PlayerFieldValue::id(&50), Comp::Gt) -// .query::(SQL_SERVER_DS) -// .await; -// -// assert!(!filtered_find_players.unwrap().is_empty()); -// } -// -// /// Same than the above but with the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_find_with_querybuilder_with_mysql() { -// // Find all the players where its ID column value is greater than 50 -// let filtered_find_players = Player::select_query_with(DatabaseType::MySQL) -// .r#where(PlayerFieldValue::id(&50), Comp::Gt) -// .query::(MYSQL_DS) -// .await; -// -// assert!(!filtered_find_players.unwrap().is_empty()); -// } -// +#[cfg(feature = "mysql")] +use crate::constants::MYSQL_DS; +#[cfg(feature = "mssql")] +use crate::constants::SQL_SERVER_DS; + +/// Tests for the QueryBuilder available operations within Canyon. +/// +/// QueryBuilder are the way of obtain more flexibility that with +/// the default generated queries, essentially for build the queries +/// with the SQL filters +/// +use canyon_sql::{ + crud::CrudOperations, + query::{operators::Comp, operators::Like, ops::QueryBuilder}, +}; + +use crate::tests_models::league::*; +use crate::tests_models::player::*; +use crate::tests_models::tournament::*; + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[canyon_sql::macros::canyon_tokio_test] +fn test_generated_sql_by_the_select_querybuilder() { + let select_with_joins = League::select_query() + .unwrap() + .inner_join("tournament", "league.id", "tournament.league_id") + .left_join("team", "tournament.id", "player.tournament_id") + .r#where(LeagueFieldValue::id(&7), Comp::Gt) + .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) + .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); + // .query() + // .await; + // NOTE: We don't have in the docker the generated relationships + // with the joins, so for now, we are just going to check that the + // generated SQL by the SelectQueryBuilder is the expected + assert_eq!( + select_with_joins.read_sql(), + "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder() { + // Find all the leagues with ID less or equals that 7 + // and where it's region column value is equals to 'Korea' + let filtered_leagues_result: Result, _> = League::select_query() + .unwrap() + .r#where(LeagueFieldValue::id(&50), Comp::LtEq) + .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) + .query() + .await; + + let filtered_leagues: Vec = filtered_leagues_result.unwrap(); + assert!(!filtered_leagues.is_empty()); + + let league_idx_0 = filtered_leagues.first().unwrap(); + assert_eq!(league_idx_0.id, 34); + assert_eq!(league_idx_0.region, "KOREA"); +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_fulllike() { + // Find all the leagues with "LC" in their name + let filtered_leagues_result = League::select_query() + .unwrap() + .r#where(LeagueFieldValue::name(&"LC"), Like::Full); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_fulllike_with_mssql() { + // Find all the leagues with "LC" in their name + let filtered_leagues_result = League::select_query_with(SQL_SERVER_DS) + .unwrap() + .r#where(LeagueFieldValue::name(&"LC"), Like::Full); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_fulllike_with_mysql() { + // Find all the leagues with "LC" in their name + let filtered_leagues_result = League::select_query_with(MYSQL_DS) + .unwrap() + .r#where(LeagueFieldValue::name(&"LC"), Like::Full); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_leftlike() { + // Find all the leagues whose name ends with "CK" + let filtered_leagues_result = League::select_query() + .unwrap() + .r#where(LeagueFieldValue::name(&"CK"), Like::Left); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_leftlike_with_mssql() { + // Find all the leagues whose name ends with "CK" + let filtered_leagues_result = League::select_query() + .unwrap() + .r#where(LeagueFieldValue::name(&"CK"), Like::Left); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS VARCHAR))" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_leftlike_with_mysql() { + // Find all the leagues whose name ends with "CK" + let filtered_leagues_result = League::select_query_with(MYSQL_DS) + .unwrap() + .r#where(LeagueFieldValue::name(&"CK"), Like::Left); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT('%', CAST($1 AS CHAR))" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_rightlike() { + // Find all the leagues whose name starts with "LC" + let filtered_leagues_result = League::select_query() + .unwrap() + .r#where(LeagueFieldValue::name(&"LC"), Like::Right); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_rightlike_with_mssql() { + // Find all the leagues whose name starts with "LC" + let filtered_leagues_result = League::select_query_with(SQL_SERVER_DS) + .unwrap() + .r#where(LeagueFieldValue::name(&"LC"), Like::Right); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS VARCHAR) ,'%')" + ) +} + +/// Builds a new SQL statement for retrieves entities of the `T` type, filtered +/// with the parameters that modifies the base SQL to SELECT * FROM +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_and_rightlike_with_mysql() { + // Find all the leagues whose name starts with "LC" + let filtered_leagues_result = League::select_query_with(MYSQL_DS) + .unwrap() + .r#where(LeagueFieldValue::name(&"LC"), Like::Right); + + assert_eq!( + filtered_leagues_result.read_sql(), + "SELECT * FROM league WHERE name LIKE CONCAT(CAST($1 AS CHAR) ,'%')" + ) +} + +/// Same than the above but with the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_with_mssql() { + // Find all the players where its ID column value is greater than 50 + let filtered_find_players = Player::select_query_with(SQL_SERVER_DS) + .unwrap() + .r#where(PlayerFieldValue::id(&50), Comp::Gt) + .query() + .await; + + assert!(!filtered_find_players.unwrap().is_empty()); +} + +/// Same than the above but with the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_find_with_querybuilder_with_mysql() { + // Find all the players where its ID column value is greater than 50 + let filtered_find_players = Player::select_query_with(MYSQL_DS) + .unwrap() + .r#where(PlayerFieldValue::id(&50), Comp::Gt) + .query() + .await; + + assert!(!filtered_find_players.unwrap().is_empty()); +} + // /// Updates the values of the range on entries defined by the constraint parameters // /// in the database entity // #[cfg(feature = "postgres")] From 79cac3e20f96eb48b06dfded7347d1ff01af9f8f Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 1 May 2025 10:34:42 +0200 Subject: [PATCH 092/155] =?UTF-8?q?feat(wip)!:=20marcho=20tomar=20caf?= =?UTF-8?q?=C3=A9,=20que=20fai=20bo=20d=C3=ADa!=20:)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- canyon_core/src/connection/mod.rs | 7 + canyon_crud/src/crud.rs | 1 + canyon_macros/src/query_operations/update.rs | 94 +++--- canyon_macros/src/utils/macro_tokens.rs | 5 + tests/crud/insert_operations.rs | 268 ++++++++--------- tests/crud/update_operations.rs | 284 +++++++++---------- 6 files changed, 339 insertions(+), 320 deletions(-) diff --git a/canyon_core/src/connection/mod.rs b/canyon_core/src/connection/mod.rs index e46b9fae..5b96e420 100644 --- a/canyon_core/src/connection/mod.rs +++ b/canyon_core/src/connection/mod.rs @@ -31,6 +31,13 @@ use lazy_static::lazy_static; use tokio::sync::Mutex; use walkdir::WalkDir; +// TODO's: DatabaseConnection and DataSource can implement default, so there's no need to use str and &str +// as defaults anymore, since the can load as the default the first one defined in the config file, or have more +// complex workflows that are deferred to initialization time +// NOTE: There's some way to read the cfg at compile time, an if there's no datasource defined, for the ops that +// handle the db connection (the _with ones) to just look for a datasource -> runtime in the cfg file? +// TODO: Crud Operations should be split into two different derives, splitting the automagic from the _with ones +// TODO: T lazy_static! { pub static ref CANYON_TOKIO_RUNTIME: tokio::runtime::Runtime = tokio::runtime::Runtime::new() // TODO Make the config with the builder diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 2e6aa417..40a37370 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -75,6 +75,7 @@ where where I: DbConnection + Send + 'a; + // TODO: the horripilant multi_insert MUST be replaced with a batch insert // fn multi_insert<'a, T>( // instances: &'a mut [&'a mut T], // ) -> impl Future>> + Send; diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index 082551dd..33ef4a94 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -1,6 +1,6 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; - +use crate::query_operations::doc_comments; use crate::query_operations::update::__details::*; use crate::utils::macro_tokens::MacroTokens; @@ -23,50 +23,54 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let update_values = fields.iter().map(|ident| { quote! { &self.#ident } }); - let update_values_cloned = update_values.clone(); + + let update_signature = quote! { + /// Updates a database record that matches the current instance of a T type, returning a + /// result indicating a possible failure querying the database. + async fn update(&self) -> Result> + }; + let update_with_signature = quote! { + async fn update_with<'a, I>(&self, input: I) + -> Result> + where I: canyon_sql::core::DbConnection + Send + 'a + }; if let Some(primary_key) = macro_data.get_primary_key_annotation() { let pk_ident = Ident::new(&primary_key, Span::call_site()); + let stmt = quote!{format!( + "UPDATE {} SET {} WHERE {} = ${:?}", + #table_schema_data, #str_columns_values, #primary_key, &self.#pk_ident + )}; + let update_values = quote! { + &[#(#update_values),*] + }; update_ops_tokens.extend(quote! { - /// Updates a database record that matches - /// the current instance of a T type, returning a result - /// indicating a possible failure querying the database. - async fn update(&self) -> Result> { - let stmt = format!( - "UPDATE {} SET {} WHERE {} = ${:?}", - #table_schema_data, #str_columns_values, #primary_key, &self.#pk_ident - ); - let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = &[#(#update_values),*]; - - <#ty as canyon_sql::core::Transaction>::execute(stmt, update_values, "").await + #update_signature { + let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = #update_values; + <#ty as canyon_sql::core::Transaction>::execute(#stmt, update_values, "").await } - /// Updates a database record that matches - /// the current instance of a T type, returning a result - /// indicating a possible failure querying the database with the - /// specified datasource - async fn update_with<'a, I>(&self, input: I) - -> Result> - where I: canyon_sql::core::DbConnection + Send + 'a - { - let stmt = format!( - "UPDATE {} SET {} WHERE {} = ${:?}", - #table_schema_data, #str_columns_values, #primary_key, &self.#pk_ident - ); - let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = &[#(#update_values_cloned),*]; - - <#ty as canyon_sql::core::Transaction>::execute(stmt, update_values, input).await + #update_with_signature { + let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = #update_values; + <#ty as canyon_sql::core::Transaction>::execute(#stmt, update_values, input).await } }); } else { // If there's no primary key, update method over self won't be available. // Use instead the update associated function of the querybuilder - let update_err_tokens = create_update_err_macro(ty); - let update_err_with_tokens = create_update_err_with_macro(ty); + let err_msg = doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE; // TODO: not on doc comments pls + let no_pk_err = quote! { + Err( + std::io::Error::new( + std::io::ErrorKind::Unsupported, + #err_msg + ).into_inner().unwrap() + ) + }; // TODO: waiting for creating our custom error types update_ops_tokens.extend(quote! { - #update_err_tokens - #update_err_with_tokens + #update_signature { #no_pk_err } + #update_with_signature{ #no_pk_err } }); } @@ -78,7 +82,7 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri /// Generates the TokenStream for the __update() CRUD operation /// being the query generated with the [`QueryBuilder`] -fn generate_update_query_tokens(ty: &Ident, table_schema_data: &String) -> TokenStream { +fn generate_update_querybuilder_tokens(ty: &Ident, table_schema_data: &String) -> TokenStream { quote! { /// Generates a [`canyon_sql::query::UpdateQueryBuilder`] /// that allows you to customize the query by adding parameters and constrains dynamically. @@ -112,17 +116,19 @@ fn generate_update_query_tokens(ty: &Ident, table_schema_data: &String) -> Token mod __details { use crate::query_operations::doc_comments; use crate::query_operations::macro_template::MacroOperationBuilder; - use proc_macro2::{Ident, Span}; - - pub fn create_update_err_macro(ty: &syn::Ident) -> MacroOperationBuilder { - MacroOperationBuilder::new() - .fn_name("update") - .with_self_as_ref() - .user_type(ty) - .return_type(&Ident::new("u64", Span::call_site())) - .raw_return() - .add_doc_comment(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) - .with_direct_error_return(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) + use proc_macro2::{Ident, Span, TokenStream}; + use quote::quote; + + pub fn create_update_err_macro(ty: &syn::Ident) -> TokenStream { + let err_msg = doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE; // TODO: not on doc comments pls + quote! { + Err( + std::io::Error::new( + std::io::ErrorKind::Unsupported, + #err_msg + ) + ).into_inner().unwrap() + } // TODO: waiting for creating our custom error types } pub fn create_update_err_with_macro(ty: &syn::Ident) -> MacroOperationBuilder { diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index aea10788..68c408fc 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -16,6 +16,11 @@ pub struct MacroTokens<'a> { pub fields: &'a Fields, } +// TODO: this struct, as is, is not really useful. There's tons of methods that must be called and +// process data everytime a Crud Operation needs them. W'd be much more efficient to have a struct +// that holds most of the data already processed, for example, the pk annotations, +// the fk operations, the mapping target type... + impl<'a> MacroTokens<'a> { pub fn new(ast: &'a DeriveInput) -> Self { Self { diff --git a/tests/crud/insert_operations.rs b/tests/crud/insert_operations.rs index 4b135fff..1f0e1078 100644 --- a/tests/crud/insert_operations.rs +++ b/tests/crud/insert_operations.rs @@ -1,126 +1,126 @@ -// //! Integration tests for the CRUD operations available in `Canyon` that -// //! generates and executes *INSERT* statements -// use canyon_sql::crud::CrudOperations; -// -// #[cfg(feature = "mysql")] -// use crate::constants::MYSQL_DS; -// #[cfg(feature = "mssql")] -// use crate::constants::SQL_SERVER_DS; -// -// use crate::tests_models::league::*; -// -// /// Inserts a new record on the database, given an entity that is -// /// annotated with `#[canyon_entity]` macro over a *T* type. -// /// -// /// For insert a new record on a database, the *insert* operation needs -// /// some special requirements: -// /// > - We need a mutable instance of `T`. If the operation completes -// /// successfully, the insert operation will automatically set the autogenerated -// /// value for the `primary_key` annotated field in it. -// /// -// /// > - It's considered a good practice to initialize that concrete field with -// /// the `Default` trait, because the value on the primary key field will be -// /// ignored at the execution time of the insert, and updated with the autogenerated -// /// value by the database. -// /// -// /// By default, the `#[primary_key]` annotation means autogenerated and autoincremental. -// /// You can configure not autoincremental via macro annotation parameters (please, -// /// refer to the docs [here]() for more info.) -// /// -// /// If the type hasn't a `#[primary_key]` annotation, or the annotation contains -// /// an argument specifying not autoincremental behaviour, all the fields will be -// /// inserted on the database and no returning value will be placed in any field. -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_insert_operation() { -// let mut new_league: League = League { -// id: Default::default(), -// ext_id: 7892635306594_i64, -// slug: "some-new-league".to_string(), -// name: "Some New League".to_string(), -// region: "Bahía de cochinos".to_string(), -// image_url: "https://nobodyspectsandimage.io".to_string(), -// }; -// -// // We insert the instance on the database, on the `League` entity -// new_league.insert().await.expect("Failed insert operation"); -// -// // Now, in the `id` field of the instance, we have the autogenerated -// // value for the primary key field, which is id. So, we can query the -// // database again with the find by primary key operation to check if -// // the value was really inserted -// let inserted_league = League::find_by_pk(&new_league.id) -// .await -// .expect("Failed the query to the database") -// .expect("No entity found for the primary key value passed in"); -// -// assert_eq!(new_league.id, inserted_league.id); -// } -// -// /// Same as the insert operation above, but targeting the database defined in -// /// the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_insert_with_mssql_operation() { -// let mut new_league: League = League { -// id: Default::default(), -// ext_id: 7892635306594_i64, -// slug: "some-new-league".to_string(), -// name: "Some New League".to_string(), -// region: "Bahía de cochinos".to_string(), -// image_url: "https://nobodyspectsandimage.io".to_string(), -// }; -// -// // We insert the instance on the database, on the `League` entity -// new_league -// .insert_with(SQL_SERVER_DS) -// .await -// .expect("Failed insert datasource operation"); -// -// // Now, in the `id` field of the instance, we have the autogenerated -// // value for the primary key field, which is id. So, we can query the -// // database again with the find by primary key operation to check if -// // the value was really inserted -// let inserted_league = League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) -// .await -// .expect("Failed the query to the database") -// .expect("No entity found for the primary key value passed in"); -// -// assert_eq!(new_league.id, inserted_league.id); -// } -// -// /// Same as the insert operation above, but targeting the database defined in -// /// the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_insert_with_mysql_operation() { -// let mut new_league: League = League { -// id: Default::default(), -// ext_id: 7892635306594_i64, -// slug: "some-new-league".to_string(), -// name: "Some New League".to_string(), -// region: "Bahía de cochinos".to_string(), -// image_url: "https://nobodyspectsandimage.io".to_string(), -// }; -// -// // We insert the instance on the database, on the `League` entity -// new_league -// .insert_with(MYSQL_DS) -// .await -// .expect("Failed insert datasource operation"); -// -// // Now, in the `id` field of the instance, we have the autogenerated -// // value for the primary key field, which is id. So, we can query the -// // database again with the find by primary key operation to check if -// // the value was really inserted -// let inserted_league = League::find_by_pk_with(&new_league.id, MYSQL_DS) -// .await -// .expect("Failed the query to the database") -// .expect("No entity found for the primary key value passed in"); -// -// assert_eq!(new_league.id, inserted_league.id); -// } -// +//! Integration tests for the CRUD operations available in `Canyon` that +//! generates and executes *INSERT* statements +use canyon_sql::crud::CrudOperations; + +#[cfg(feature = "mysql")] +use crate::constants::MYSQL_DS; +#[cfg(feature = "mssql")] +use crate::constants::SQL_SERVER_DS; + +use crate::tests_models::league::*; + +/// Inserts a new record on the database, given an entity that is +/// annotated with `#[canyon_entity]` macro over a *T* type. +/// +/// For insert a new record on a database, the *insert* operation needs +/// some special requirements: +/// > - We need a mutable instance of `T`. If the operation completes +/// successfully, the insert operation will automatically set the autogenerated +/// value for the `primary_key` annotated field in it. +/// +/// > - It's considered a good practice to initialize that concrete field with +/// the `Default` trait, because the value on the primary key field will be +/// ignored at the execution time of the insert, and updated with the autogenerated +/// value by the database. +/// +/// By default, the `#[primary_key]` annotation means autogenerated and autoincremental. +/// You can configure not autoincremental via macro annotation parameters (please, +/// refer to the docs [here]() for more info.) +/// +/// If the type hasn't a `#[primary_key]` annotation, or the annotation contains +/// an argument specifying not autoincremental behaviour, all the fields will be +/// inserted on the database and no returning value will be placed in any field. +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_insert_operation() { + let mut new_league: League = League { + id: Default::default(), + ext_id: 7892635306594_i64, + slug: "some-new-league".to_string(), + name: "Some New League".to_string(), + region: "Bahía de cochinos".to_string(), + image_url: "https://nobodyspectsandimage.io".to_string(), + }; + + // We insert the instance on the database, on the `League` entity + new_league.insert().await.expect("Failed insert operation"); + + // Now, in the `id` field of the instance, we have the autogenerated + // value for the primary key field, which is id. So, we can query the + // database again with the find by primary key operation to check if + // the value was really inserted + let inserted_league = League::find_by_pk(&new_league.id) + .await + .expect("Failed the query to the database") + .expect("No entity found for the primary key value passed in"); + + assert_eq!(new_league.id, inserted_league.id); +} + +/// Same as the insert operation above, but targeting the database defined in +/// the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_insert_with_mssql_operation() { + let mut new_league: League = League { + id: Default::default(), + ext_id: 7892635306594_i64, + slug: "some-new-league".to_string(), + name: "Some New League".to_string(), + region: "Bahía de cochinos".to_string(), + image_url: "https://nobodyspectsandimage.io".to_string(), + }; + + // We insert the instance on the database, on the `League` entity + new_league + .insert_with(SQL_SERVER_DS) + .await + .expect("Failed insert datasource operation"); + + // Now, in the `id` field of the instance, we have the autogenerated + // value for the primary key field, which is id. So, we can query the + // database again with the find by primary key operation to check if + // the value was really inserted + let inserted_league = League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) + .await + .expect("Failed the query to the database") + .expect("No entity found for the primary key value passed in"); + + assert_eq!(new_league.id, inserted_league.id); +} + +/// Same as the insert operation above, but targeting the database defined in +/// the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_insert_with_mysql_operation() { + let mut new_league: League = League { + id: Default::default(), + ext_id: 7892635306594_i64, + slug: "some-new-league".to_string(), + name: "Some New League".to_string(), + region: "Bahía de cochinos".to_string(), + image_url: "https://nobodyspectsandimage.io".to_string(), + }; + + // We insert the instance on the database, on the `League` entity + new_league + .insert_with(MYSQL_DS) + .await + .expect("Failed insert datasource operation"); + + // Now, in the `id` field of the instance, we have the autogenerated + // value for the primary key field, which is id. So, we can query the + // database again with the find by primary key operation to check if + // the value was really inserted + let inserted_league = League::find_by_pk_with(&new_league.id, MYSQL_DS) + .await + .expect("Failed the query to the database") + .expect("No entity found for the primary key value passed in"); + + assert_eq!(new_league.id, inserted_league.id); +} +// // /// The multi insert operation is a shorthand for insert multiple instances of *T* // /// in the database at once. // /// @@ -158,7 +158,7 @@ // region: "The dark side of the moon".to_string(), // image_url: "https://interplanetary-league.io".to_string(), // }; -// +// // // Insert the instance as database entities // new_league_mi // .insert() @@ -172,7 +172,7 @@ // .insert() // .await // .expect("Failed insert datasource operation"); -// +// // // Recover the inserted data by primary key // let inserted_league = League::find_by_pk(&new_league_mi.id) // .await @@ -186,12 +186,12 @@ // .await // .expect("[3] - Failed the query to the database") // .expect("[3] - No entity found for the primary key value passed in"); -// +// // assert_eq!(new_league_mi.id, inserted_league.id); // assert_eq!(new_league_mi_2.id, inserted_league_2.id); // assert_eq!(new_league_mi_3.id, inserted_league_3.id); // } -// +// // /// Same as the multi insert above, but with the specified datasource // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] @@ -220,7 +220,7 @@ // region: "The dark side of the moon".to_string(), // image_url: "https://interplanetary-league.io".to_string(), // }; -// +// // // Insert the instance as database entities // new_league_mi // .insert_with(SQL_SERVER_DS) @@ -234,7 +234,7 @@ // .insert_with(SQL_SERVER_DS) // .await // .expect("Failed insert datasource operation"); -// +// // // Recover the inserted data by primary key // let inserted_league = League::find_by_pk_with(&new_league_mi.id, SQL_SERVER_DS) // .await @@ -248,12 +248,12 @@ // .await // .expect("[3] - Failed the query to the database") // .expect("[3] - No entity found for the primary key value passed in"); -// +// // assert_eq!(new_league_mi.id, inserted_league.id); // assert_eq!(new_league_mi_2.id, inserted_league_2.id); // assert_eq!(new_league_mi_3.id, inserted_league_3.id); // } -// +// // /// Same as the multi insert above, but with the specified datasource // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] @@ -282,7 +282,7 @@ // region: "The dark side of the moon".to_string(), // image_url: "https://interplanetary-league.io".to_string(), // }; -// +// // // Insert the instance as database entities // new_league_mi // .insert_with(MYSQL_DS) @@ -296,7 +296,7 @@ // .insert_with(MYSQL_DS) // .await // .expect("Failed insert datasource operation"); -// +// // // Recover the inserted data by primary key // let inserted_league = League::find_by_pk_with(&new_league_mi.id, MYSQL_DS) // .await @@ -310,7 +310,7 @@ // .await // .expect("[3] - Failed the query to the database") // .expect("[3] - No entity found for the primary key value passed in"); -// +// // assert_eq!(new_league_mi.id, inserted_league.id); // assert_eq!(new_league_mi_2.id, inserted_league_2.id); // assert_eq!(new_league_mi_3.id, inserted_league_3.id); diff --git a/tests/crud/update_operations.rs b/tests/crud/update_operations.rs index 2c126458..2f291884 100644 --- a/tests/crud/update_operations.rs +++ b/tests/crud/update_operations.rs @@ -1,142 +1,142 @@ -// use crate::tests_models::league::*; -// // Integration tests for the CRUD operations available in `Canyon` that -// /// generates and executes *UPDATE* statements -// use canyon_sql::crud::CrudOperations; -// -// #[cfg(feature = "mysql")] -// use crate::constants::MYSQL_DS; -// #[cfg(feature = "mssql")] -// use crate::constants::SQL_SERVER_DS; -// -// /// Update operation is a *CRUD* method defined for some entity `T`, that works by appliying -// /// some change to a Rust's entity instance, and persisting them into the database. -// /// -// /// The `t.update(&self)` operation is only enabled for types that -// /// has, at least, one of it's fields annotated with a `#[primary_key]` -// /// operation, because we use that concrete field to construct the clause that targets -// /// that entity. -// /// -// /// Attempt of usage the `t.update(&self)` method on an entity without `#[primary_key]` -// /// will raise a runtime error. -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_method_operation() { -// // We first retrieve some entity from the database. Note that we must make -// // the retrieved instance mutable of clone it to a new mutable resource -// let mut updt_candidate: League = League::find_by_pk(&1) -// .await -// .expect("[1] - Failed the query to the database") -// .expect("[1] - No entity found for the primary key value passed in"); -// -// // The ext_id field value is extracted from the sql scripts under the -// // docker/sql folder. We are retrieving the first entity inserted at the -// // wake-up time of the database, and now checking some of its properties. -// assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); -// -// // Modify the value, and perform the update -// let updt_value: i64 = 593064_i64; -// updt_candidate.ext_id = updt_value; -// updt_candidate -// .update() -// .await -// .expect("Failed the update operation"); -// -// // Retrieve it again, and check if the value was really updated -// let updt_entity: League = League::find_by_pk(&1) -// .await -// .expect("[2] - Failed the query to the database") -// .expect("[2] - No entity found for the primary key value passed in"); -// -// assert_eq!(updt_entity.ext_id, updt_value); -// -// // We roll back the changes to the initial value to don't broke other tests -// // the next time that will run -// updt_candidate.ext_id = 100695891328981122_i64; -// updt_candidate -// .update() -// .await -// .expect("Failed to restore the initial value in the psql update operation"); -// } -// -// /// Same as the above test, but with the specified datasource. -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_with_mssql_method_operation() { -// // We first retrieve some entity from the database. Note that we must make -// // the retrieved instance mutable of clone it to a new mutable resource -// let mut updt_candidate: League = League::find_by_pk_with(&1, SQL_SERVER_DS) -// .await -// .expect("[1] - Failed the query to the database") -// .expect("[1] - No entity found for the primary key value passed in"); -// -// // The ext_id field value is extracted from the sql scripts under the -// // docker/sql folder. We are retrieving the first entity inserted at the -// // wake-up time of the database, and now checking some of its properties. -// assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); -// -// // Modify the value, and perform the update -// let updt_value: i64 = 59306442534_i64; -// updt_candidate.ext_id = updt_value; -// updt_candidate -// .update_with(SQL_SERVER_DS) -// .await -// .expect("Failed the update operation"); -// -// // Retrieve it again, and check if the value was really updated -// let updt_entity: League = League::find_by_pk_with(&1, SQL_SERVER_DS) -// .await -// .expect("[2] - Failed the query to the database") -// .expect("[2] - No entity found for the primary key value passed in"); -// -// assert_eq!(updt_entity.ext_id, updt_value); -// -// // We roll back the changes to the initial value to don't broke other tests -// // the next time that will run -// updt_candidate.ext_id = 100695891328981122_i64; -// updt_candidate -// .update_with(SQL_SERVER_DS) -// .await -// .expect("Failed to restablish the initial value update operation"); -// } -// -// /// Same as the above test, but with the specified datasource. -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_with_mysql_method_operation() { -// // We first retrieve some entity from the database. Note that we must make -// // the retrieved instance mutable of clone it to a new mutable resource -// -// let mut updt_candidate: League = League::find_by_pk_with(&1, MYSQL_DS) -// .await -// .expect("[1] - Failed the query to the database") -// .expect("[1] - No entity found for the primary key value passed in"); -// -// // The ext_id field value is extracted from the sql scripts under the -// // docker/sql folder. We are retrieving the first entity inserted at the -// // wake up time of the database, and now checking some of its properties. -// assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); -// -// // Modify the value, and perform the update -// let updt_value: i64 = 59306442534_i64; -// updt_candidate.ext_id = updt_value; -// updt_candidate -// .update_with(MYSQL_DS) -// .await -// .expect("Failed the update operation"); -// -// // Retrieve it again, and check if the value was really updated -// let updt_entity: League = League::find_by_pk_with(&1, MYSQL_DS) -// .await -// .expect("[2] - Failed the query to the database") -// .expect("[2] - No entity found for the primary key value passed in"); -// -// assert_eq!(updt_entity.ext_id, updt_value); -// -// // We rollback the changes to the initial value to don't broke other tests -// // the next time that will run -// updt_candidate.ext_id = 100695891328981122_i64; -// updt_candidate -// .update_with(MYSQL_DS) -// .await -// .expect("Failed to restablish the initial value update operation"); -// } +use crate::tests_models::league::*; +// Integration tests for the CRUD operations available in `Canyon` that +/// generates and executes *UPDATE* statements +use canyon_sql::crud::CrudOperations; + +#[cfg(feature = "mysql")] +use crate::constants::MYSQL_DS; +#[cfg(feature = "mssql")] +use crate::constants::SQL_SERVER_DS; + +/// Update operation is a *CRUD* method defined for some entity `T`, that works by appliying +/// some change to a Rust's entity instance, and persisting them into the database. +/// +/// The `t.update(&self)` operation is only enabled for types that +/// has, at least, one of its fields annotated with a `#[primary_key]` +/// operation, because we use that concrete field to construct the clause that targets +/// that entity. +/// +/// Attempt of usage the `t.update(&self)` method on an entity without `#[primary_key]` +/// will raise a runtime error. +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_update_method_operation() { + // We first retrieve some entity from the database. Note that we must make + // the retrieved instance mutable of clone it to a new mutable resource + let mut updt_candidate: League = League::find_by_pk(&1) + .await + .expect("[1] - Failed the query to the database") + .expect("[1] - No entity found for the primary key value passed in"); + + // The ext_id field value is extracted from the sql scripts under the + // docker/sql folder. We are retrieving the first entity inserted at the + // wake-up time of the database, and now checking some of its properties. + assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); + + // Modify the value, and perform the update + let updt_value: i64 = 593064_i64; + updt_candidate.ext_id = updt_value; + updt_candidate + .update() + .await + .expect("Failed the update operation"); + + // Retrieve it again, and check if the value was really updated + let updt_entity: League = League::find_by_pk(&1) + .await + .expect("[2] - Failed the query to the database") + .expect("[2] - No entity found for the primary key value passed in"); + + assert_eq!(updt_entity.ext_id, updt_value); + + // We roll back the changes to the initial value to don't broke other tests + // the next time that will run + updt_candidate.ext_id = 100695891328981122_i64; + updt_candidate + .update() + .await + .expect("Failed to restore the initial value in the psql update operation"); +} + +/// Same as the above test, but with the specified datasource. +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_update_with_mssql_method_operation() { + // We first retrieve some entity from the database. Note that we must make + // the retrieved instance mutable of clone it to a new mutable resource + let mut updt_candidate: League = League::find_by_pk_with(&1, SQL_SERVER_DS) + .await + .expect("[1] - Failed the query to the database") + .expect("[1] - No entity found for the primary key value passed in"); + + // The ext_id field value is extracted from the sql scripts under the + // docker/sql folder. We are retrieving the first entity inserted at the + // wake-up time of the database, and now checking some of its properties. + assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); + + // Modify the value, and perform the update + let updt_value: i64 = 59306442534_i64; + updt_candidate.ext_id = updt_value; + updt_candidate + .update_with(SQL_SERVER_DS) + .await + .expect("Failed the update operation"); + + // Retrieve it again, and check if the value was really updated + let updt_entity: League = League::find_by_pk_with(&1, SQL_SERVER_DS) + .await + .expect("[2] - Failed the query to the database") + .expect("[2] - No entity found for the primary key value passed in"); + + assert_eq!(updt_entity.ext_id, updt_value); + + // We roll back the changes to the initial value to don't broke other tests + // the next time that will run + updt_candidate.ext_id = 100695891328981122_i64; + updt_candidate + .update_with(SQL_SERVER_DS) + .await + .expect("Failed to restablish the initial value update operation"); +} + +/// Same as the above test, but with the specified datasource. +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_update_with_mysql_method_operation() { + // We first retrieve some entity from the database. Note that we must make + // the retrieved instance mutable of clone it to a new mutable resource + + let mut updt_candidate: League = League::find_by_pk_with(&1, MYSQL_DS) + .await + .expect("[1] - Failed the query to the database") + .expect("[1] - No entity found for the primary key value passed in"); + + // The ext_id field value is extracted from the sql scripts under the + // docker/sql folder. We are retrieving the first entity inserted at the + // wake-up time of the database, and now checking some of its properties. + assert_eq!(updt_candidate.ext_id, 100695891328981122_i64); + + // Modify the value, and perform the update + let updt_value: i64 = 59306442534_i64; + updt_candidate.ext_id = updt_value; + updt_candidate + .update_with(MYSQL_DS) + .await + .expect("Failed the update operation"); + + // Retrieve it again, and check if the value was really updated + let updt_entity: League = League::find_by_pk_with(&1, MYSQL_DS) + .await + .expect("[2] - Failed the query to the database") + .expect("[2] - No entity found for the primary key value passed in"); + + assert_eq!(updt_entity.ext_id, updt_value); + + // We roll back the changes to the initial value to don't broke other tests + // the next time that will run + updt_candidate.ext_id = 100695891328981122_i64; + updt_candidate + .update_with(MYSQL_DS) + .await + .expect("Failed to restablish the initial value update operation"); +} From 077461ee4c398b141dffa4a19cb8d6914f0f3e81 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 1 May 2025 18:41:57 +0200 Subject: [PATCH 093/155] feat: crud operations are finally implemented (along with the querybuilder ones) according to the new Transaction-DbConnection contract specifications --- canyon_core/src/rows.rs | 10 - canyon_crud/src/bounds.rs | 4 +- canyon_crud/src/crud.rs | 27 +- .../src/query_elements/query_builder.rs | 50 +- canyon_entities/src/manager_builder.rs | 2 - canyon_macros/src/query_operations/consts.rs | 5 + canyon_macros/src/query_operations/delete.rs | 360 ++++++------- .../src/query_operations/doc_comments.rs | 5 - .../src/query_operations/foreign_key.rs | 28 +- canyon_macros/src/query_operations/insert.rs | 4 +- .../src/query_operations/macro_template.rs | 435 ---------------- canyon_macros/src/query_operations/mod.rs | 1 - canyon_macros/src/query_operations/read.rs | 11 +- canyon_macros/src/query_operations/update.rs | 129 ++--- canyon_macros/src/utils/macro_tokens.rs | 2 +- tests/crud/delete_operations.rs | 318 ++++++------ tests/crud/foreign_key_operations.rs | 326 ++++++------ tests/crud/insert_operations.rs | 24 +- tests/crud/querybuilder_operations.rs | 478 +++++++++--------- 19 files changed, 891 insertions(+), 1328 deletions(-) delete mode 100644 canyon_macros/src/query_operations/macro_template.rs diff --git a/canyon_core/src/rows.rs b/canyon_core/src/rows.rs index 768a4d23..738dfbe5 100644 --- a/canyon_core/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -7,22 +7,12 @@ use tokio_postgres::{self}; use crate::mapper::{CanyonError, IntoResults, RowMapper}; use crate::row::Row; -use std::error::Error; use cfg_if::cfg_if; // Helper macro to conditionally add trait bounds // these are the hacky intermediate traits cfg_if! { - // if #[cfg(feature = "postgres")] { - // trait FromSql<'a, T> where T: tokio_postgres::types::FromSql<'a> { } - // } else if #[cfg(feature = "mssql")] { - // trait FromSql<'a, T> where T: tiberius::FromSql<'a> { } - // } else if #[cfg(feature = "mysql")] { - // trait FromSql<'a, T> where T: mysql_async::types::FromSql<'a> { } - // } - - // } else if #[cfg(all(feature = "postgres", feature = "mysql", feature = "mssql"))] { if #[cfg(all(feature = "postgres", feature = "mysql", feature = "mssql"))] { pub trait FromSql<'a, T>: tokio_postgres::types::FromSql<'a> + tiberius::FromSql<'a> diff --git a/canyon_crud/src/bounds.rs b/canyon_crud/src/bounds.rs index 59c2e49d..1d3fed58 100644 --- a/canyon_crud/src/bounds.rs +++ b/canyon_crud/src/bounds.rs @@ -1,6 +1,4 @@ -use canyon_core::{mapper::RowMapper, query_parameters::QueryParameter, transaction::Transaction}; - -use crate::crud::CrudOperations; +use canyon_core::query_parameters::QueryParameter; /// Created for retrieve the field's name of a field of a struct, giving /// the Canyon's autogenerated enum with the variants that maps this diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 40a37370..703ee813 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -1,7 +1,6 @@ use crate::query_elements::query_builder::{ DeleteQueryBuilder, SelectQueryBuilder, UpdateQueryBuilder, }; -use canyon_core::connection::database_type::DatabaseType; use canyon_core::connection::db_connector::DbConnection; use canyon_core::mapper::RowMapper; use canyon_core::query_parameters::QueryParameter; @@ -96,11 +95,14 @@ where where I: DbConnection + Send + 'a; - // fn update_query<'a>() -> UpdateQueryBuilder<'a>; - // - // fn update_query_with<'a, I>(input: I) -> UpdateQueryBuilder<'a> - // where - // I: DbConnection + Send + 'a; + fn update_query<'a>( + ) -> Result, Box<(dyn Error + Send + Sync + 'a)>>; + + fn update_query_with<'a, I>( + input: &'a I, + ) -> Result, Box<(dyn Error + Send + Sync + 'a)>> + where + I: DbConnection + Send + 'a + ?Sized; fn delete(&self) -> impl Future>> + Send; @@ -111,9 +113,12 @@ where where I: DbConnection + Send + 'a; - // fn delete_query<'a>() -> DeleteQueryBuilder<'a>; - // - // fn delete_query_with<'a, I>(input: I) -> DeleteQueryBuilder<'a> - // where - // I: DbConnection + Send + 'a; + fn delete_query<'a>( + ) -> Result, Box<(dyn Error + Send + Sync + 'a)>>; + + fn delete_query_with<'a, I>( + input: &'a I, + ) -> Result, Box<(dyn Error + Send + Sync + 'a)>> + where + I: DbConnection + Send + 'a + ?Sized; } diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index a36846ba..ef93767a 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -61,27 +61,27 @@ pub mod ops { /// Generates a `WHERE` SQL clause for constraint the query. /// /// * `column` - A [`FieldValueIdentifier`] that will provide the target - /// column name and the value for the filter + /// column name and the value for the filter /// * `op` - Any element that implements [`Operator`] for create the comparison - /// or equality binary operator + /// or equality binary operator fn r#where>(self, column: Z, op: impl Operator) -> Self; /// Generates an `AND` SQL clause for constraint the query. /// /// * `column` - A [`FieldValueIdentifier`] that will provide the target - /// column name and the value for the filter + /// column name and the value for the filter /// * `op` - Any element that implements [`Operator`] for create the comparison - /// or equality binary operator + /// or equality binary operator fn and>(self, column: Z, op: impl Operator) -> Self; /// Generates an `AND` SQL clause for constraint the query that will create /// the filter in conjunction with an `IN` operator that will ac /// /// * `column` - A [`FieldIdentifier`] that will provide the target - /// column name for the filter, based on the variant that represents - /// the field name that maps the targeted column name + /// column name for the filter, based on the variant that represents + /// the field name that maps the targeted column name /// * `values` - An array of [`QueryParameter`] with the values to filter - /// inside the `IN` operator + /// inside the `IN` operator fn and_values_in(self, column: Z, values: &'a [Q]) -> Self where Z: FieldIdentifier, @@ -91,10 +91,10 @@ pub mod ops { /// the filter in conjunction with an `IN` operator that will ac /// /// * `column` - A [`FieldIdentifier`] that will provide the target - /// column name for the filter, based on the variant that represents - /// the field name that maps the targeted column name + /// column name for the filter, based on the variant that represents + /// the field name that maps the targeted column name /// * `values` - An array of [`QueryParameter`] with the values to filter - /// inside the `IN` operator + /// inside the `IN` operator fn or_values_in(self, r#or: Z, values: &'a [Q]) -> Self where Z: FieldIdentifier, @@ -103,9 +103,9 @@ pub mod ops { /// Generates an `OR` SQL clause for constraint the query. /// /// * `column` - A [`FieldValueIdentifier`] that will provide the target - /// column name and the value for the filter + /// column name and the value for the filter /// * `op` - Any element that implements [`Operator`] for create the comparison - /// or equality binary operator + /// or equality binary operator fn or>(self, column: Z, op: impl Operator) -> Self; /// Generates a `ORDER BY` SQL clause for constraint the query. @@ -126,7 +126,7 @@ pub struct QueryBuilder<'a, I: DbConnection + ?Sized, R: RowMapper> { pd: PhantomData, } -unsafe impl<'a, I: DbConnection + ?Sized, R: RowMapper> Sync for QueryBuilder<'a, I, R> {} +unsafe impl Sync for QueryBuilder<'_, I, R> {} impl<'a, I: DbConnection + ?Sized, R: RowMapper> QueryBuilder<'a, I, R> { pub fn new(sql: String, input: &'a I) -> Result> { @@ -390,12 +390,12 @@ impl<'a, I: DbConnection + ?Sized, R: RowMapper> ops::QueryBuilder<'a> /// Contains the specific database operations of the *UPDATE* SQL statements. /// /// * `set` - To construct a new `SET` clause to determine the columns to -/// update with the provided values -pub struct UpdateQueryBuilder<'a, I: DbConnection, R: RowMapper> { +/// update with the provided values +pub struct UpdateQueryBuilder<'a, I: DbConnection + ?Sized, R: RowMapper> { _inner: QueryBuilder<'a, I, R>, } -impl<'a, I: DbConnection, R: RowMapper> UpdateQueryBuilder<'a, I, R> { +impl<'a, I: DbConnection + ?Sized, R: RowMapper> UpdateQueryBuilder<'a, I, R> { /// Generates a new public instance of the [`UpdateQueryBuilder`] pub fn new( table_schema_data: &str, @@ -408,7 +408,7 @@ impl<'a, I: DbConnection, R: RowMapper> UpdateQueryBuilder<'a, I, R> { /// Launches the generated query to the database pointed by the selected datasource #[inline] - pub async fn query(self, input: I) -> Result, Box<(dyn Error + Send + Sync + 'a)>> + pub async fn query(self) -> Result, Box<(dyn Error + Send + Sync + 'a)>> where Vec: FromIterator<::Output>, { @@ -455,7 +455,9 @@ impl<'a, I: DbConnection, R: RowMapper> UpdateQueryBuilder<'a, I, R> { } } -impl<'a, I: DbConnection, R: RowMapper> ops::QueryBuilder<'a> for UpdateQueryBuilder<'a, I, R> { +impl<'a, I: DbConnection + ?Sized, R: RowMapper> ops::QueryBuilder<'a> + for UpdateQueryBuilder<'a, I, R> +{ #[inline] fn read_sql(&'a self) -> &'a str { self._inner.sql.as_str() @@ -515,12 +517,12 @@ impl<'a, I: DbConnection, R: RowMapper> ops::QueryBuilder<'a> for UpdateQueryBui /// *DELETE* SQL statements. /// /// * `set` - To construct a new `SET` clause to determine the columns to -/// update with the provided values -pub struct DeleteQueryBuilder<'a, I: DbConnection, R: RowMapper> { +/// update with the provided values +pub struct DeleteQueryBuilder<'a, I: DbConnection + ?Sized, R: RowMapper> { _inner: QueryBuilder<'a, I, R>, } -impl<'a, I: DbConnection, R: RowMapper> DeleteQueryBuilder<'a, I, R> { +impl<'a, I: DbConnection + ?Sized, R: RowMapper> DeleteQueryBuilder<'a, I, R> { /// Generates a new public instance of the [`DeleteQueryBuilder`] pub fn new( table_schema_data: &str, @@ -533,7 +535,7 @@ impl<'a, I: DbConnection, R: RowMapper> DeleteQueryBuilder<'a, I, R> { /// Launches the generated query to the database pointed by the selected datasource #[inline] - pub async fn query(self, input: I) -> Result, Box<(dyn Error + Send + Sync + 'a)>> + pub async fn query(self) -> Result, Box<(dyn Error + Send + Sync + 'a)>> where Vec: FromIterator<::Output>, { @@ -541,7 +543,9 @@ impl<'a, I: DbConnection, R: RowMapper> DeleteQueryBuilder<'a, I, R> { } } -impl<'a, I: DbConnection, R: RowMapper> ops::QueryBuilder<'a> for DeleteQueryBuilder<'a, I, R> { +impl<'a, I: DbConnection + ?Sized, R: RowMapper> ops::QueryBuilder<'a> + for DeleteQueryBuilder<'a, I, R> +{ #[inline] fn read_sql(&'a self) -> &'a str { self._inner.sql.as_str() diff --git a/canyon_entities/src/manager_builder.rs b/canyon_entities/src/manager_builder.rs index 6f51bd64..7362268a 100644 --- a/canyon_entities/src/manager_builder.rs +++ b/canyon_entities/src/manager_builder.rs @@ -31,7 +31,6 @@ pub fn generate_user_struct(canyon_entity: &CanyonEntity) -> TokenStream { /// will be called though macro code to obtain the &str representation /// of the field name. pub fn generate_enum_with_fields(canyon_entity: &CanyonEntity) -> TokenStream { - let ty = &canyon_entity.struct_name; let struct_name = canyon_entity.struct_name.to_string(); let enum_name = Ident::new((struct_name + "Field").as_str(), Span::call_site()); @@ -93,7 +92,6 @@ pub fn generate_enum_with_fields(canyon_entity: &CanyonEntity) -> TokenStream { /// The type of the inner value `(Enum::Variant(SomeType))` is the same /// that the field that the variant represents pub fn generate_enum_with_fields_values(canyon_entity: &CanyonEntity) -> TokenStream { - let ty = &canyon_entity.struct_name; let struct_name = canyon_entity.struct_name.to_string(); let enum_name = Ident::new((struct_name + "FieldValue").as_str(), Span::call_site()); diff --git a/canyon_macros/src/query_operations/consts.rs b/canyon_macros/src/query_operations/consts.rs index f9db6f19..8f416cbb 100644 --- a/canyon_macros/src/query_operations/consts.rs +++ b/canyon_macros/src/query_operations/consts.rs @@ -6,6 +6,11 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{Ident, Type}; +pub const UNAVAILABLE_CRUD_OP_ON_INSTANCE: &str = + "Operation is unavailable. T doesn't contain a #[primary_key]\ + annotation. You must construct the query with the QueryBuilder type\ + (_query method for the CrudOperations implementors"; + thread_local! { pub static USER_MOCK_TY: RefCell = RefCell::new(Ident::new("User", Span::call_site())); pub static USER_MOCK_MAPPER_TY: RefCell = RefCell::new(Ident::new("User", Span::call_site())); diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index 57116e86..904adff7 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -1,11 +1,6 @@ -use crate::query_operations::delete::__details::{ - create_delete_err_macro, create_delete_err_with_macro, create_delete_macro, - create_delete_with_macro, -}; use crate::utils::macro_tokens::MacroTokens; -use proc_macro2::{Ident, TokenStream}; +use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use syn::Type; /// Generates the TokenStream for the __delete() CRUD operation /// returning a result, indicating a possible failure querying the database @@ -13,53 +8,69 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let mut delete_ops_tokens = TokenStream::new(); let ty = macro_data.ty; - let fields = macro_data.get_struct_fields(); let pk = macro_data.get_primary_key_annotation(); - let ret_ty: Type = syn::parse_str("()").expect("Failed to parse unit type"); - let q_ret_ty: TokenStream = quote! {#ret_ty}; + let delete_signature = quote! { + /// Deletes from a database entity the row that matches + /// the current instance of a T type, returning a result + /// indicating a possible failure querying the database. + async fn delete(&self) -> Result<(), Box<(dyn std::error::Error + Send + Sync)>> + }; + let delete_with_signature = quote! { + /// Deletes from a database entity the row that matches + /// the current instance of a T type, returning a result + /// indicating a possible failure querying the database with the specified datasource. + async fn delete_with<'a, I>(&self, input: I) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'a)>> + where I: canyon_sql::core::DbConnection + Send + 'a + }; if let Some(primary_key) = pk { - let pk_field = fields - .iter() - .find(|f| *f.to_string() == primary_key) - .expect( - "Something really bad happened finding the Ident for the pk field on the delete", - ); + let pk_field = Ident::new(&primary_key, Span::call_site()); let pk_field_value = quote! { &self.#pk_field as &dyn canyon_sql::core::QueryParameter<'_> }; - - let stmt = format!( + let delete_stmt = format!( "DELETE FROM {} WHERE {:?} = $1", table_schema_data, primary_key ); - let delete_tokens = create_delete_macro(ty, &stmt, &pk_field_value, &q_ret_ty); - let delete_with_tokens = create_delete_with_macro(ty, &stmt, &pk_field_value, &q_ret_ty); - delete_ops_tokens.extend(quote! { - #delete_tokens - #delete_with_tokens + #delete_signature { + <#ty as canyon_sql::core::Transaction>::execute(#delete_stmt, &[#pk_field_value], "").await?; + Ok(()) + } + + #delete_with_signature { + input.execute(#delete_stmt, &[#pk_field_value]).await?; + Ok(()) + } }); } else { - let delete_err_tokens = create_delete_err_macro(ty, &q_ret_ty); - let delete_err_with_tokens = create_delete_err_with_macro(ty, &q_ret_ty); + // Delete operation over an instance isn't available without declaring a primary key. + // The delete querybuilder variant must be used for the case when there's no pk declared + let no_pk_error = quote! { + Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "You can't use the 'delete' method on a \ + CanyonEntity that does not have a #[primary_key] annotation. \ + If you need to perform an specific search, use the Querybuilder instead." + ).into_inner().unwrap()) + }; delete_ops_tokens.extend(quote! { - #delete_err_tokens - #delete_err_with_tokens + #delete_signature { #no_pk_error } + #delete_with_signature { #no_pk_error } }); } - // let delete_with_querybuilder = generate_delete_query_tokens(ty, table_schema_data); - // delete_ops_tokens.extend(delete_with_querybuilder); + let delete_with_querybuilder = generate_delete_querybuilder_tokens(ty, table_schema_data); + delete_ops_tokens.extend(delete_with_querybuilder); delete_ops_tokens } /// Generates the TokenStream for the __delete() CRUD operation as a /// [`query_elements::query_builder::QueryBuilder<'a, #ty>`] -fn generate_delete_query_tokens(ty: &Ident, table_schema_data: &str) -> TokenStream { +fn generate_delete_querybuilder_tokens(ty: &Ident, table_schema_data: &str) -> TokenStream { quote! { /// Generates a [`canyon_sql::query::DeleteQueryBuilder`] /// that allows you to customize the query by adding parameters and constrains dynamically. @@ -68,7 +79,9 @@ fn generate_delete_query_tokens(ty: &Ident, table_schema_data: &str) -> TokenStr /// entity but converted to the corresponding database convention, /// unless concrete values are set on the available parameters of the /// `canyon_macro(table_name = "table_name", schema = "schema")` - fn delete_query<'a>() -> canyon_sql::query::DeleteQueryBuilder<'a, #ty, &'a str> { + fn delete_query<'a>() -> Result< + canyon_sql::query::DeleteQueryBuilder<'a, str, #ty>, + Box<(dyn std::error::Error + Send + Sync + 'a)>> { canyon_sql::query::DeleteQueryBuilder::new(#table_schema_data, "") } @@ -82,150 +95,153 @@ fn generate_delete_query_tokens(ty: &Ident, table_schema_data: &str) -> TokenStr /// /// The query it's made against the database with the configured datasource /// described in the configuration file, selected with the input parameter - fn delete_query_with<'a, I>(input: I) -> canyon_sql::query::DeleteQueryBuilder<'a, #ty, I> - where I: canyon_sql::core::DbConnection + Send + 'a + fn delete_query_with<'a, I>(input: &'a I) -> Result< + canyon_sql::query::DeleteQueryBuilder<'a, I, #ty>, + Box<(dyn std::error::Error + Send + Sync + 'a)> + > where I: canyon_sql::core::DbConnection + Send + 'a + ?Sized { canyon_sql::query::DeleteQueryBuilder::new(#table_schema_data, input) } } } - -// NOTE: The delete operations shouldn't be using TransactionMethod::QueryRows -// This should be refactored on the future -mod __details { - - use super::*; - use crate::query_operations::doc_comments; - use crate::query_operations::macro_template::{MacroOperationBuilder, TransactionMethod}; - - pub fn create_delete_macro( - ty: &Ident, - stmt: &str, - pk_field_value: &TokenStream, - ret_ty: &TokenStream, - ) -> MacroOperationBuilder { - MacroOperationBuilder::new() - .fn_name("delete") - .with_self_as_ref() - .user_type(ty) - .return_type_ts(ret_ty) - .raw_return() - .add_doc_comment(doc_comments::DELETE) - .query_string(stmt) - .forwarded_parameters(quote! {&[#pk_field_value]}) - .propagate_transaction_result() - .with_transaction_method(TransactionMethod::Execute) - .raw_return() - .with_no_result_value() - } - - pub fn create_delete_with_macro( - ty: &Ident, - stmt: &str, - pk_field_value: &TokenStream, - ret_ty: &TokenStream, - ) -> MacroOperationBuilder { - MacroOperationBuilder::new() - .fn_name("delete_with") - .with_self_as_ref() - .with_input_param() - .user_type(ty) - .return_type_ts(ret_ty) - .raw_return() - .add_doc_comment(doc_comments::DELETE) - .add_doc_comment(doc_comments::DS_ADVERTISING) - .query_string(stmt) - .forwarded_parameters(quote! {&[#pk_field_value]}) - .propagate_transaction_result() - .with_transaction_method(TransactionMethod::Execute) - .raw_return() - .with_no_result_value() - } - - pub fn create_delete_err_macro(ty: &Ident, ret_ty: &TokenStream) -> MacroOperationBuilder { - MacroOperationBuilder::new() - .fn_name("delete") - .with_self_as_ref() - .user_type(ty) - .return_type_ts(ret_ty) - .raw_return() - .add_doc_comment(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) - .with_direct_error_return(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) - } - - pub fn create_delete_err_with_macro(ty: &Ident, ret_ty: &TokenStream) -> MacroOperationBuilder { - MacroOperationBuilder::new() - .fn_name("delete_with") - .with_self_as_ref() - .with_input_param() - .user_type(ty) - .return_type_ts(ret_ty) - .raw_return() - .add_doc_comment(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) - .with_direct_error_return(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) - } -} - -#[cfg(test)] -mod delete_tests { - use super::__details::*; - use crate::query_operations::consts::*; - - const DELETE_MOCK_STMT: &str = "DELETE FROM public.user WHERE user.id = 1"; - - #[test] - fn test_macro_builder_delete() { - let delete_builder = create_delete_macro( - &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - DELETE_MOCK_STMT, - &PK_MOCK_FIELD_VALUE.with(|pk_field_mock_value| pk_field_mock_value.borrow().clone()), - &VOID_RET_TY.with(|void_ret_ty| void_ret_ty.borrow().clone()), - ); - let delete = delete_builder.generate_tokens().to_string(); - - assert!(delete.contains("async fn delete")); - assert!(delete.contains(RES_VOID_RET_TY)); - } - - #[test] - fn test_macro_builder_delete_with() { - let delete_builder = create_delete_with_macro( - &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - DELETE_MOCK_STMT, - &PK_MOCK_FIELD_VALUE.with(|pk_field_mock_value| pk_field_mock_value.borrow().clone()), - &VOID_RET_TY.with(|void_ret_ty| void_ret_ty.borrow().clone()), - ); - let delete_with = delete_builder.generate_tokens().to_string(); - - assert!(delete_with.contains("async fn delete_with")); - assert!(delete_with.contains(RES_VOID_RET_TY_LT)); - assert!(delete_with.contains(LT_CONSTRAINT)); - assert!(delete_with.contains(INPUT_PARAM)); - } - - #[test] - fn test_macro_builder_delete_err() { - let delete_err_builder = create_delete_err_macro( - &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - &VOID_RET_TY.with(|void_ret_ty| void_ret_ty.borrow().clone()), - ); - let delete_err = delete_err_builder.generate_tokens().to_string(); - - assert!(delete_err.contains("async fn delete")); - assert!(delete_err.contains(RES_VOID_RET_TY)); - } - - #[test] - fn test_macro_builder_delete_err_with() { - let delete_err_with_builder = create_delete_err_with_macro( - &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - &VOID_RET_TY.with(|void_ret_ty| void_ret_ty.borrow().clone()), - ); - let delete_err_with = delete_err_with_builder.generate_tokens().to_string(); - - assert!(delete_err_with.contains("async fn delete_with")); - assert!(delete_err_with.contains(RES_VOID_RET_TY_LT)); - assert!(delete_err_with.contains(LT_CONSTRAINT)); - assert!(delete_err_with.contains(INPUT_PARAM)); - } -} +// +// // NOTE: The delete operations shouldn't be using TransactionMethod::QueryRows +// // This should be refactored on the future +// mod __details { +// +// use super::*; +// use crate::query_operations::doc_comments; +// use crate::query_operations::macro_template::{MacroOperationBuilder, TransactionMethod}; +// +// pub fn create_delete_macro( +// ty: &Ident, +// stmt: &str, +// pk_field_value: &TokenStream, +// ret_ty: &TokenStream, +// ) -> TokenStream { +// MacroOperationBuilder::new() +// .fn_name("delete") +// .with_self_as_ref() +// .user_type(ty) +// .return_type_ts(ret_ty) +// .raw_return() +// .add_doc_comment(doc_comments::DELETE) +// .query_string(stmt) +// .forwarded_parameters(quote! {&[#pk_field_value]}) +// .propagate_transaction_result() +// .with_transaction_method(TransactionMethod::Execute) +// .raw_return() +// .with_no_result_value() +// +// } +// +// pub fn create_delete_with_macro( +// ty: &Ident, +// stmt: &str, +// pk_field_value: &TokenStream, +// ret_ty: &TokenStream, +// ) -> MacroOperationBuilder { +// MacroOperationBuilder::new() +// .fn_name("delete_with") +// .with_self_as_ref() +// .with_input_param() +// .user_type(ty) +// .return_type_ts(ret_ty) +// .raw_return() +// .add_doc_comment(doc_comments::DELETE) +// .add_doc_comment(doc_comments::DS_ADVERTISING) +// .query_string(stmt) +// .forwarded_parameters(quote! {&[#pk_field_value]}) +// .propagate_transaction_result() +// .with_transaction_method(TransactionMethod::Execute) +// .raw_return() +// .with_no_result_value() +// } +// +// pub fn create_delete_err_macro(ty: &Ident, ret_ty: &TokenStream) -> MacroOperationBuilder { +// MacroOperationBuilder::new() +// .fn_name("delete") +// .with_self_as_ref() +// .user_type(ty) +// .return_type_ts(ret_ty) +// .raw_return() +// .add_doc_comment(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) +// .with_direct_error_return(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) +// } +// +// pub fn create_delete_err_with_macro(ty: &Ident, ret_ty: &TokenStream) -> MacroOperationBuilder { +// MacroOperationBuilder::new() +// .fn_name("delete_with") +// .with_self_as_ref() +// .with_input_param() +// .user_type(ty) +// .return_type_ts(ret_ty) +// .raw_return() +// .add_doc_comment(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) +// .with_direct_error_return(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) +// } +// } +// +// #[cfg(test)] +// mod delete_tests { +// use super::__details::*; +// use crate::query_operations::consts::*; +// +// const DELETE_MOCK_STMT: &str = "DELETE FROM public.user WHERE user.id = 1"; +// +// #[test] +// fn test_macro_builder_delete() { +// let delete_builder = create_delete_macro( +// &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), +// DELETE_MOCK_STMT, +// &PK_MOCK_FIELD_VALUE.with(|pk_field_mock_value| pk_field_mock_value.borrow().clone()), +// &VOID_RET_TY.with(|void_ret_ty| void_ret_ty.borrow().clone()), +// ); +// let delete = delete_builder.generate_tokens().to_string(); +// +// assert!(delete.contains("async fn delete")); +// assert!(delete.contains(RES_VOID_RET_TY)); +// } +// +// #[test] +// fn test_macro_builder_delete_with() { +// let delete_builder = create_delete_with_macro( +// &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), +// DELETE_MOCK_STMT, +// &PK_MOCK_FIELD_VALUE.with(|pk_field_mock_value| pk_field_mock_value.borrow().clone()), +// &VOID_RET_TY.with(|void_ret_ty| void_ret_ty.borrow().clone()), +// ); +// let delete_with = delete_builder.generate_tokens().to_string(); +// +// assert!(delete_with.contains("async fn delete_with")); +// assert!(delete_with.contains(RES_VOID_RET_TY_LT)); +// assert!(delete_with.contains(LT_CONSTRAINT)); +// assert!(delete_with.contains(INPUT_PARAM)); +// } +// +// #[test] +// fn test_macro_builder_delete_err() { +// let delete_err_builder = create_delete_err_macro( +// &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), +// &VOID_RET_TY.with(|void_ret_ty| void_ret_ty.borrow().clone()), +// ); +// let delete_err = delete_err_builder.generate_tokens().to_string(); +// +// assert!(delete_err.contains("async fn delete")); +// assert!(delete_err.contains(RES_VOID_RET_TY)); +// } +// +// #[test] +// fn test_macro_builder_delete_err_with() { +// let delete_err_with_builder = create_delete_err_with_macro( +// &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), +// &VOID_RET_TY.with(|void_ret_ty| void_ret_ty.borrow().clone()), +// ); +// let delete_err_with = delete_err_with_builder.generate_tokens().to_string(); +// +// assert!(delete_err_with.contains("async fn delete_with")); +// assert!(delete_err_with.contains(RES_VOID_RET_TY_LT)); +// assert!(delete_err_with.contains(LT_CONSTRAINT)); +// assert!(delete_err_with.contains(INPUT_PARAM)); +// } +// } diff --git a/canyon_macros/src/query_operations/doc_comments.rs b/canyon_macros/src/query_operations/doc_comments.rs index c7d6f5a6..14f8dd7a 100644 --- a/canyon_macros/src/query_operations/doc_comments.rs +++ b/canyon_macros/src/query_operations/doc_comments.rs @@ -37,8 +37,3 @@ pub const DELETE: &str = "Deletes from a database entity the row that matches the current instance of a T type based on the actual value of the primary key field, returning a result indicating a possible failure querying the database."; - -pub const UNAVAILABLE_CRUD_OP_ON_INSTANCE: &str = - "Operation is unavailable. T doesn't contain a #[primary_key]\ - annotation. You must construct the query with the QueryBuilder type\ - (_query method for the CrudOperations implementors"; diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index fa9984af..5aaabe40 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -140,6 +140,10 @@ fn generate_find_by_reverse_foreign_key_tokens( ) -> Vec<(TokenStream, TokenStream)> { let mut rev_fk_quotes: Vec<(TokenStream, TokenStream)> = Vec::new(); let ty = macro_data.ty; + let mapper_ty = macro_data + .retrieve_mapping_target_type() + .expect("Expected mapping target ") + .unwrap_or_else(|| ty.clone()); for (field_ident, fk_annot) in macro_data.get_fk_annotations().iter() { if let EntityFieldAnnotation::ForeignKey(table, column) = fk_annot { @@ -153,15 +157,15 @@ fn generate_find_by_reverse_foreign_key_tokens( proc_macro2::Span::call_site(), ); let quoted_method_signature: TokenStream = quote! { - async fn #method_name_ident<'a, R, F>(value: &F) - -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> - where R: RowMapper, + async fn #method_name_ident<'a, F>(value: &F) + -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + where F: canyon_sql::crud::bounds::ForeignKeyable + Send + Sync }; let quoted_with_method_signature: TokenStream = quote! { - async fn #method_name_ident_with<'a, R, F, I> (value: &F, input: I) - -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> - where R: RowMapper, + async fn #method_name_ident_with<'a, F, I> (value: &F, input: I) + -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + where F: canyon_sql::crud::bounds::ForeignKeyable + Send + Sync, I: canyon_sql::core::DbConnection + Send + 'a }; @@ -180,13 +184,13 @@ fn generate_find_by_reverse_foreign_key_tokens( "Column: {:?} not found in type: {:?}", #column, #table ).as_str()); - let stmt = format!( + let stmt = &format!( "SELECT * FROM {} WHERE {} = $1", #table_schema_data, format!("\"{}\"", #f_ident).as_str() ); - <#ty as canyon_sql::core::Transaction>::query( + <#ty as canyon_sql::core::Transaction>::query::<&str, #mapper_ty>( stmt, &[lookage_value], "" @@ -208,17 +212,13 @@ fn generate_find_by_reverse_foreign_key_tokens( "Column: {:?} not found in type: {:?}", #column, #table ).as_str()); - let stmt = format!( + let stmt = &format!( "SELECT * FROM {} WHERE {} = $1", #table_schema_data, format!("\"{}\"", #f_ident).as_str() ); - <#ty as canyon_sql::core::Transaction>::query( - stmt, - &[lookage_value], - input - ).await + input.query::<&str, #mapper_ty>(stmt, &[lookage_value]).await } }, )); diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 9821a55c..51bd7f9d 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -182,14 +182,14 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri /// /// This, also lets the user have the option to be able to insert multiple /// [`T`] objects in only one query -fn generate_multiple_insert_tokens( +fn _generate_multiple_insert_tokens( macro_data: &MacroTokens, table_schema_data: &String, ) -> TokenStream { let ty = macro_data.ty; // Retrieves the fields of the Struct as continuous String - let column_names = macro_data.get_struct_fields_as_strings(); + let column_names = macro_data._get_struct_fields_as_strings(); // Retrieves the fields of the Struct let fields = macro_data.get_struct_fields(); diff --git a/canyon_macros/src/query_operations/macro_template.rs b/canyon_macros/src/query_operations/macro_template.rs deleted file mode 100644 index cd777196..00000000 --- a/canyon_macros/src/query_operations/macro_template.rs +++ /dev/null @@ -1,435 +0,0 @@ -use proc_macro2::{Ident, Span, TokenStream}; -use quote::{quote, ToTokens}; - -#[derive(Debug, Copy, Clone)] -#[allow(dead_code)] -pub enum TransactionMethod { - Query, - QueryOne, - QueryOneFor, - QueryRows, - Execute, -} - -impl ToTokens for TransactionMethod { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - TransactionMethod::Query => tokens.extend(quote! {query}), - TransactionMethod::QueryOne => tokens.extend(quote! {query_one}), - TransactionMethod::QueryOneFor => tokens.extend(quote! {query_one_for}), - TransactionMethod::QueryRows => tokens.extend(quote! {query_rows}), - TransactionMethod::Execute => tokens.extend(quote! {execute}), - } - } -} - -pub struct MacroOperationBuilder { - fn_name: Option, - user_type: Option, - lifetime: bool, - self_as_ref: bool, - type_is_row_mapper: bool, - input_param: Option, - input_fwd_arg: Option, - return_type: Option, - return_type_ts: Option, - where_clause_bounds: Vec, - doc_comments: Vec, - query_string: Option, - input_parameters: Option, - forwarded_parameters: Option, - transaction_method: TransactionMethod, - single_result: bool, - with_unwrap: bool, - with_no_result_value: bool, // Ok(()) - transaction_as_variable: bool, - direct_error_return: Option, - raw_return: bool, - propagate_transaction_result: bool, - post_body: Option, -} - -impl ToTokens for MacroOperationBuilder { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(self.generate_tokens()); - } -} - -impl MacroOperationBuilder { - pub const fn new() -> Self { - Self { - fn_name: None, - user_type: None, - lifetime: false, - self_as_ref: false, - type_is_row_mapper: false, - input_param: None, - input_fwd_arg: None, - return_type: None, - return_type_ts: None, - where_clause_bounds: Vec::new(), - doc_comments: Vec::new(), - query_string: None, - input_parameters: None, - forwarded_parameters: None, - transaction_method: TransactionMethod::Query, - single_result: false, - with_unwrap: false, - with_no_result_value: false, - transaction_as_variable: false, - direct_error_return: None, - raw_return: false, - propagate_transaction_result: false, - post_body: None, - } - } - - fn get_fn_name(&self) -> TokenStream { - if let Some(fn_name) = &self.fn_name { - quote! { #fn_name } - } else { - panic!("No function name provided") - } - } - - pub fn fn_name(mut self, name: &str) -> Self { - self.fn_name = Some(Ident::new(name, Span::call_site())); - self - } - - fn get_user_type(&self) -> TokenStream { - if let Some(user_type) = &self.user_type { - quote! { #user_type } - } else { - panic!("No T type provided for determining the operations implementor") - } - } - - pub fn user_type(mut self, ty: &Ident) -> Self { - self.user_type = Some(ty.clone()); - self - } - - fn compose_fn_signature_generics(&self) -> TokenStream { - if !&self.lifetime && self.input_param.is_none() && !self.type_is_row_mapper { - return quote! {}; - } - - let mut generics = quote! { < }; - - if self.lifetime { - generics.extend(quote! { 'a, }); - } - if self.type_is_row_mapper { - generics.extend(quote! { R, }); - } - if self.input_param.is_some() { - generics.extend(quote! { I }); - } - generics.extend(quote! { > }); - generics - } - - fn compose_self_params_separator(&self) -> TokenStream { - // TODO: missing combinations - if self.self_as_ref && self.input_param.is_some() { - quote! {, } - } else { - quote! {} - } - } - - fn compose_params_separator(&self) -> TokenStream { - if self.input_parameters.is_some() && self.input_param.is_some() { - quote! {, } - } else { - quote! {} - } - } - - fn get_as_method(&self) -> TokenStream { - if self.self_as_ref { - let self_ident = Ident::new("self", Span::call_site()); - quote! { &#self_ident } - } else { - quote! {} - } - } - - fn get_input_param(&self) -> TokenStream { - let input_param = &self.input_param; - quote! { #input_param } - } - - fn get_input_arg(&self) -> TokenStream { - if let Some(input_arg) = &self.input_fwd_arg { - let ds_arg0 = input_arg; - quote! { #ds_arg0 } - } else { - quote! { "" } - } - } - - pub fn with_self_as_ref(mut self) -> Self { - self.self_as_ref = true; - self - } - - pub fn with_lifetime(mut self) -> Self { - self.lifetime = true; - self - } - - pub fn with_input_param(mut self) -> Self { - self.input_param = Some(quote! { input: I }); - self.input_fwd_arg = Some(quote! { input }); - self.lifetime = true; - self.where_clause_bounds.push(quote! { - I: canyon_sql::core::DbConnection + Send + 'a - }); - self - } - - pub fn type_is_row_mapper(mut self) -> Self { - self.type_is_row_mapper = true; - let ret_ty = self.get_organic_ret_ty(); - self.where_clause_bounds.push(quote! { - R: RowMapper - }); - self - } - - fn get_organic_ret_ty(&self) -> TokenStream { - if let Some(return_ty_ts) = &self.return_type_ts { - let rt_ts = return_ty_ts; - quote! { #rt_ts } - } else if self.type_is_row_mapper { - quote! { R } - } else { - let rt = &self.return_type; - quote! { #rt } - } - } - - fn get_return_type(&self) -> TokenStream { - let organic_ret_type = self.get_organic_ret_ty(); - - let container_ret_type = if self.single_result { - quote! { Option } - } else { - quote! { Vec } - }; - - let ret_type = if self.raw_return { - quote! { #organic_ret_type } - } else { - quote! { #container_ret_type<#organic_ret_type> } - }; - - match &self.with_unwrap { - // TODO: distinguish collection from rows with only results - true => quote! { #ret_type }, - false => { - let err_variant = if self.lifetime { - quote! { Box<(dyn std::error::Error + Send + Sync + 'a)> } - } else { - quote! { Box<(dyn std::error::Error + Send + Sync)>} - }; - - quote! { Result<#ret_type, #err_variant> } - } - } - } - - fn get_where_clause_bounds(&self) -> TokenStream { - if self.where_clause_bounds.is_empty() { - quote! {} - } else { - let where_bounds = &self.where_clause_bounds; - quote! { - where #(#where_bounds),* - } - } - } - - pub fn return_type(mut self, return_type: &Ident) -> Self { - self.return_type = Some(return_type.clone()); - self - } - - pub fn return_type_ts(mut self, return_type: &TokenStream) -> Self { - self.return_type_ts = Some(return_type.clone()); - self - } - - pub fn single_result(mut self) -> Self { - self.single_result = true; - self - } - - pub fn add_doc_comment(mut self, comment: &str) -> Self { - self.doc_comments.push(comment.to_string()); - self - } - - pub fn query_string(mut self, query: &str) -> Self { - self.query_string = Some(query.to_string()); - self - } - - pub fn input_parameters(mut self, params: TokenStream) -> Self { - self.input_parameters = Some(params); - self - } - - pub fn get_fn_parameters(&self) -> TokenStream { - let func_parameters = &self.input_parameters; - quote! { #func_parameters } - } - - pub fn forwarded_parameters(mut self, params: TokenStream) -> Self { - self.forwarded_parameters = Some(params); - self - } - - fn get_forwarded_parameters(&self) -> TokenStream { - if let Some(fwd_params) = &self.forwarded_parameters { - quote! { #fwd_params } - } else { - quote! { &[] } - } - } - - pub fn with_transaction_method(mut self, transaction_method: TransactionMethod) -> Self { - self.transaction_method = transaction_method; - match &self.transaction_method { - TransactionMethod::QueryOne => self.single_result(), - _ => self, - } - } - - fn get_transaction_method(&self) -> TransactionMethod { - self.transaction_method - } - - fn get_unwrap(&self) -> TokenStream { - if self.with_unwrap { - quote! { .unwrap() } - } else { - quote! {} - } - } - - pub fn with_unwrap(mut self) -> Self { - self.with_unwrap = true; - self - } - - pub fn with_no_result_value(mut self) -> Self { - self.with_no_result_value = true; - self - } - - pub fn with_direct_error_return>(mut self, err: E) -> Self { - self.direct_error_return = Some(err.as_ref().to_string()); - self - } - - pub fn transaction_as_variable(mut self, result_handling: TokenStream) -> Self { - self.transaction_as_variable = true; - self.post_body = Some(result_handling); - self - } - - pub fn raw_return(mut self) -> Self { - self.raw_return = true; - self - } - - pub fn propagate_transaction_result(mut self) -> Self { - self.propagate_transaction_result = true; - self - } - - /// Generates the final `quote!` tokens for this operation - pub fn generate_tokens(&self) -> TokenStream { - let doc_comments = &self - .doc_comments - .iter() - .map(|doc_comment| quote! { #[doc = #doc_comment] }) - .collect::>(); - - let ty = self.get_user_type(); - let fn_name = self.get_fn_name(); - let generics = self.compose_fn_signature_generics(); - - let as_method = self.get_as_method(); - let input_param = self.get_input_param(); - let input_fwd_arg = self.get_input_arg(); // TODO: replace - let fn_parameters = self.get_fn_parameters(); - - let query_string = &self.query_string; - let forwarded_parameters = self.get_forwarded_parameters(); - let return_type = self.get_return_type(); - let where_clause = self.get_where_clause_bounds(); - let transaction_method = self.get_transaction_method(); - let unwrap = self.get_unwrap(); - - let mut base_body_tokens = quote! { - <#ty as canyon_sql::core::Transaction>::#transaction_method( - #query_string, - #forwarded_parameters, - #input_fwd_arg - ).await - }; - - if self.propagate_transaction_result { - base_body_tokens.extend(quote! { ? }) - }; - - if self.with_no_result_value { - // TODO: should we validate some combinations? in the future, some of them can be hard to reason about - // like transaction_as_variable and with_no_result_value, they can't coexist - base_body_tokens.extend(quote! {; Ok(()) }) - } - - let body_tokens = if let Some(direct_err_return) = &self.direct_error_return { - let err = direct_err_return; - quote! { - Err( - std::io::Error::new( - std::io::ErrorKind::Unsupported, - #err - ).into_inner().unwrap() - ) // TODO: waiting for creating our custom error types - } - } else if self.transaction_as_variable { - let result_handling = &self.post_body; - quote! { - let transaction_result = #base_body_tokens; - #result_handling - } - } else { - base_body_tokens - }; - - let separate_params = self.compose_params_separator(); - let separate_self_params = self.compose_self_params_separator(); - - quote! { - #(#doc_comments)* - async fn #fn_name #generics( - #as_method - #separate_self_params - #fn_parameters - #separate_params - #input_param - ) -> #return_type - #where_clause - { - #body_tokens - #unwrap - } - } - } -} diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index 8c3c0bc0..400c8d0b 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -15,7 +15,6 @@ pub mod update; mod consts; mod doc_comments; -mod macro_template; pub fn impl_crud_operations_trait_for_struct( macro_data: &MacroTokens<'_>, diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index fe4c7aac..72057798 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -117,11 +117,11 @@ fn generate_find_by_pk_operations_tokens( let err_msg = consts::FIND_BY_PK_ERR_NO_PK; Some(quote! { Err( - std::io::Error::new( - std::io::ErrorKind::Unsupported, - #err_msg, - ).into_inner().unwrap() - ) + std::io::Error::new( + std::io::ErrorKind::Unsupported, + #err_msg, + ).into_inner().unwrap() + ) }) }; let stmt = format!( @@ -339,7 +339,6 @@ mod macro_builder_read_ops_tests { #[test] fn test_create_count_macro() { let ty = syn::parse_str::("User").unwrap(); - let mapper_ty = syn::parse_str::("User").unwrap(); let tokens = create_count_macro(&ty, COUNT_STMT); let generated = tokens.to_string(); diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index 33ef4a94..a27a4068 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -1,8 +1,7 @@ +use crate::query_operations::consts; +use crate::utils::macro_tokens::MacroTokens; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use crate::query_operations::doc_comments; -use crate::query_operations::update::__details::*; -use crate::utils::macro_tokens::MacroTokens; /// Generates the TokenStream for the __update() CRUD operation pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &String) -> TokenStream { @@ -23,13 +22,13 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let update_values = fields.iter().map(|ident| { quote! { &self.#ident } }); - + let update_signature = quote! { /// Updates a database record that matches the current instance of a T type, returning a /// result indicating a possible failure querying the database. - async fn update(&self) -> Result> + async fn update(&self) -> Result> }; - let update_with_signature = quote! { + let update_with_signature = quote! { async fn update_with<'a, I>(&self, input: I) -> Result> where I: canyon_sql::core::DbConnection + Send + 'a @@ -37,7 +36,7 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri if let Some(primary_key) = macro_data.get_primary_key_annotation() { let pk_ident = Ident::new(&primary_key, Span::call_site()); - let stmt = quote!{format!( + let stmt = quote! {&format!( "UPDATE {} SET {} WHERE {} = ${:?}", #table_schema_data, #str_columns_values, #primary_key, &self.#pk_ident )}; @@ -52,13 +51,13 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri } #update_with_signature { let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = #update_values; - <#ty as canyon_sql::core::Transaction>::execute(#stmt, update_values, input).await + input.execute(#stmt, update_values).await } }); } else { // If there's no primary key, update method over self won't be available. // Use instead the update associated function of the querybuilder - let err_msg = doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE; // TODO: not on doc comments pls + let err_msg = consts::UNAVAILABLE_CRUD_OP_ON_INSTANCE; let no_pk_err = quote! { Err( std::io::Error::new( @@ -74,8 +73,8 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri }); } - // let querybuilder_update_tokens = generate_update_query_tokens(ty, table_schema_data); - // update_ops_tokens.extend(querybuilder_update_tokens); + let querybuilder_update_tokens = generate_update_querybuilder_tokens(ty, table_schema_data); + update_ops_tokens.extend(querybuilder_update_tokens); update_ops_tokens } @@ -91,7 +90,9 @@ fn generate_update_querybuilder_tokens(ty: &Ident, table_schema_data: &String) - /// entity but converted to the corresponding database convention, /// unless concrete values are set on the available parameters of the /// `canyon_macro(table_name = "table_name", schema = "schema")` - fn update_query<'a>() -> canyon_sql::query::UpdateQueryBuilder<'a, #ty, &'a str> { + fn update_query<'a>() -> Result< + canyon_sql::query::UpdateQueryBuilder<'a, str, #ty>, + Box<(dyn std::error::Error + Send + Sync + 'a)>> { canyon_sql::query::UpdateQueryBuilder::new(#table_schema_data, "") } @@ -105,75 +106,47 @@ fn generate_update_querybuilder_tokens(ty: &Ident, table_schema_data: &String) - /// /// The query it's made against the database with the configured datasource /// described in the configuration file, and selected with the input parameter - fn update_query_with<'a, I>(input: I) -> canyon_sql::query::UpdateQueryBuilder<'a, #ty, I> - where I: canyon_sql::core::DbConnection + Send + 'a + fn update_query_with<'a, I>(input: &'a I) -> Result< + canyon_sql::query::UpdateQueryBuilder<'a, I, #ty>, + Box<(dyn std::error::Error + Send + Sync + 'a)> + > where I: canyon_sql::core::DbConnection + Send + 'a + ?Sized { canyon_sql::query::UpdateQueryBuilder::new(#table_schema_data, input) } } } -mod __details { - use crate::query_operations::doc_comments; - use crate::query_operations::macro_template::MacroOperationBuilder; - use proc_macro2::{Ident, Span, TokenStream}; - use quote::quote; - - pub fn create_update_err_macro(ty: &syn::Ident) -> TokenStream { - let err_msg = doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE; // TODO: not on doc comments pls - quote! { - Err( - std::io::Error::new( - std::io::ErrorKind::Unsupported, - #err_msg - ) - ).into_inner().unwrap() - } // TODO: waiting for creating our custom error types - } - - pub fn create_update_err_with_macro(ty: &syn::Ident) -> MacroOperationBuilder { - MacroOperationBuilder::new() - .fn_name("update_with") - .with_self_as_ref() - .with_input_param() - .user_type(ty) - .return_type(&Ident::new("u64", Span::call_site())) - .raw_return() - .add_doc_comment(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) - .with_direct_error_return(doc_comments::UNAVAILABLE_CRUD_OP_ON_INSTANCE) - } -} - -#[cfg(test)] -mod update_tokens_tests { - use crate::query_operations::consts::{ - INPUT_PARAM, LT_CONSTRAINT, RES_VOID_RET_TY, RES_VOID_RET_TY_LT, USER_MOCK_TY, - }; - use crate::query_operations::update::__details::{ - create_update_err_macro, create_update_err_with_macro, - }; - - #[test] - fn test_macro_builder_update_err() { - let update_err_builder = create_update_err_macro( - &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - ); - let update_err = update_err_builder.generate_tokens().to_string(); - - assert!(update_err.contains("async fn update")); - assert!(update_err.contains(RES_VOID_RET_TY)); - } - - #[test] - fn test_macro_builder_update_err_with() { - let update_err_with_builder = create_update_err_with_macro( - &USER_MOCK_TY.with(|user_mock_ty| user_mock_ty.borrow().clone()), - ); - let update_err_with = update_err_with_builder.generate_tokens().to_string(); - - assert!(update_err_with.contains("async fn update_with")); - assert!(update_err_with.contains(RES_VOID_RET_TY_LT)); - assert!(update_err_with.contains(LT_CONSTRAINT)); - assert!(update_err_with.contains(INPUT_PARAM)); - } -} +// +// #[cfg(test)] +// mod update_tokens_tests { +// use proc_macro2::Ident; +// use crate::query_operations::consts; +// +// // use crate::query_operations::consts::{ +// // INPUT_PARAM, LT_CONSTRAINT, RES_VOID_RET_TY, RES_VOID_RET_TY_LT, USER_MOCK_TY, +// // }; +// #[test] +// fn test_create_update_macro() { +// let ty = syn::parse_str::("User").unwrap(); +// let mapper_ty = syn::parse_str::("User").unwrap(); +// let tokens = crate::query_operations::read::__details::find_all_generators::create_find_all_macro(&ty, &mapper_ty, crate::query_operations::read::macro_builder_read_ops_tests::SELECT_ALL_STMT); +// let generated = tokens.to_string(); +// +// assert!(generated.contains("async fn find_all")); +// assert!(generated.contains(consts::RES_RET_TY)); +// assert!(generated.contains(crate::query_operations::read::macro_builder_read_ops_tests::SELECT_ALL_STMT)); +// } +// +// #[test] +// fn test_create_find_all_with_macro() { +// let mapper_ty = syn::parse_str::("User").unwrap(); +// let tokens = crate::query_operations::read::__details::find_all_generators::create_find_all_with_macro(&mapper_ty, crate::query_operations::read::macro_builder_read_ops_tests::SELECT_ALL_STMT); +// let generated = tokens.to_string(); +// +// assert!(generated.contains("async fn find_all_with")); +// assert!(generated.contains(consts::RES_RET_TY)); +// assert!(generated.contains(consts::LT_CONSTRAINT)); +// assert!(generated.contains(consts::INPUT_PARAM)); +// assert!(generated.contains(crate::query_operations::read::macro_builder_read_ops_tests::SELECT_ALL_STMT)); +// } +// } diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index 68c408fc..e11b493e 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -116,7 +116,7 @@ impl<'a> MacroTokens<'a> { } /// Retrieves the fields of the Struct as continuous String, comma separated - pub fn get_struct_fields_as_strings(&self) -> String { + pub fn _get_struct_fields_as_strings(&self) -> String { let column_names: String = self .get_struct_fields() .iter() diff --git a/tests/crud/delete_operations.rs b/tests/crud/delete_operations.rs index 22ffacca..e9bb61ef 100644 --- a/tests/crud/delete_operations.rs +++ b/tests/crud/delete_operations.rs @@ -1,159 +1,159 @@ -// //! Integration tests for the CRUD operations available in `Canyon` that -// //! generates and executes *INSERT* statements -// use canyon_sql::crud::CrudOperations; -// -// #[cfg(feature = "mysql")] -// use crate::constants::MYSQL_DS; -// #[cfg(feature = "postgres")] -// use crate::constants::PSQL_DS; -// #[cfg(feature = "mssql")] -// use crate::constants::SQL_SERVER_DS; -// -// use crate::tests_models::league::*; -// -// /// Deletes a row from the database that is mapped into some instance of a `T` entity. -// /// -// /// The `t.delete(&self)` operation is only enabled for types that -// /// has, at least, one of it's fields annotated with a `#[primary_key]` -// /// operation, because we use that concrete field to construct the clause that targets -// /// that entity. -// /// -// /// Attempt of usage the `t.delete(&self)` method on an entity without `#[primary_key]` -// /// will raise a runtime error. -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_method_operation() { -// // For test the delete operation, we will insert a new instance of the database, and then, -// // after inspect it, we will proceed to delete it -// let mut new_league: League = League { -// id: Default::default(), -// ext_id: 7892635306594_i64, -// slug: "some-new-league".to_string(), -// name: "Some New League".to_string(), -// region: "Bahía de cochinos".to_string(), -// image_url: "https://nobodyspectsandimage.io".to_string(), -// }; -// -// // We insert the instance on the database, on the `League` entity -// new_league.insert().await.expect("Failed insert operation"); -// -// assert_eq!( -// new_league.id, -// League::find_by_pk_with(&new_league.id, PSQL_DS) -// .await -// .expect("Request error") -// .expect("None value") -// .id -// ); -// -// // Now that we have an instance mapped to some entity by a primary key, we can now -// // remove that entry from the database with the delete operation -// new_league -// .delete() -// .await -// .expect("Failed to delete the operation"); -// -// // To check the success, we can query by the primary key value and check if, after unwrap() -// // the result of the operation, the find by primary key contains Some(v) or None -// // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> -// assert_eq!( -// League::find_by_pk(&new_league.id) -// .await -// .expect("Unwrapping the result, letting the Option"), -// None -// ); -// } -// -// /// Same as the delete test, but performing the operations with the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_with_mssql_method_operation() { -// // For test the delete, we will insert a new instance of the database, and then, -// // after inspect it, we will proceed to delete it -// let mut new_league: League = League { -// id: Default::default(), -// ext_id: 7892635306594_i64, -// slug: "some-new-league".to_string(), -// name: "Some New League".to_string(), -// region: "Bahía de cochinos".to_string(), -// image_url: "https://nobodyspectsandimage.io".to_string(), -// }; -// -// // We insert the instance on the database, on the `League` entity -// new_league -// .insert_with(SQL_SERVER_DS) -// .await -// .expect("Failed insert operation"); -// assert_eq!( -// new_league.id, -// League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) -// .await -// .expect("Request error") -// .expect("None value") -// .id -// ); -// -// // Now that we have an instance mapped to some entity by a primary key, we can now -// // remove that entry from the database with the delete operation -// new_league -// .delete_with(SQL_SERVER_DS) -// .await -// .expect("Failed to delete the operation"); -// -// // To check the success, we can query by the primary key value and check if, after unwrap() -// // the result of the operation, the find by primary key contains Some(v) or None -// // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> -// assert_eq!( -// League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) -// .await -// .expect("Unwrapping the result, letting the Option"), -// None -// ); -// } -// -// /// Same as the delete test, but performing the operations with the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_with_mysql_method_operation() { -// // For test the delete, we will insert a new instance of the database, and then, -// // after inspect it, we will proceed to delete it -// let mut new_league: League = League { -// id: Default::default(), -// ext_id: 7892635306594_i64, -// slug: "some-new-league".to_string(), -// name: "Some New League".to_string(), -// region: "Bahía de cochinos".to_string(), -// image_url: "https://nobodyspectsandimage.io".to_string(), -// }; -// -// // We insert the instance on the database, on the `League` entity -// new_league -// .insert_with(MYSQL_DS) -// .await -// .expect("Failed insert operation"); -// assert_eq!( -// new_league.id, -// League::find_by_pk_with(&new_league.id, MYSQL_DS) -// .await -// .expect("Request error") -// .expect("None value") -// .id -// ); -// -// // Now that we have an instance mapped to some entity by a primary key, we can now -// // remove that entry from the database with the delete operation -// new_league -// .delete_with(MYSQL_DS) -// .await -// .expect("Failed to delete the operation"); -// -// // To check the success, we can query by the primary key value and check if, after unwrap() -// // the result of the operation, the find by primary key contains Some(v) or None -// // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> -// assert_eq!( -// League::find_by_pk_with(&new_league.id, MYSQL_DS) -// .await -// .expect("Unwrapping the result, letting the Option"), -// None -// ); -// } +//! Integration tests for the CRUD operations available in `Canyon` that +//! generates and executes *INSERT* statements +use canyon_sql::crud::CrudOperations; + +#[cfg(feature = "mysql")] +use crate::constants::MYSQL_DS; +#[cfg(feature = "postgres")] +use crate::constants::PSQL_DS; +#[cfg(feature = "mssql")] +use crate::constants::SQL_SERVER_DS; + +use crate::tests_models::league::*; + +/// Deletes a row from the database that is mapped into some instance of a `T` entity. +/// +/// The `t.delete(&self)` operation is only enabled for types that +/// has, at least, one of it's fields annotated with a `#[primary_key]` +/// operation, because we use that concrete field to construct the clause that targets +/// that entity. +/// +/// Attempt of usage the `t.delete(&self)` method on an entity without `#[primary_key]` +/// will raise a runtime error. +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_delete_method_operation() { + // For test the delete operation, we will insert a new instance of the database, and then, + // after inspect it, we will proceed to delete it + let mut new_league: League = League { + id: Default::default(), + ext_id: 7892635306594_i64, + slug: "some-new-league".to_string(), + name: "Some New League".to_string(), + region: "Bahía de cochinos".to_string(), + image_url: "https://nobodyspectsandimage.io".to_string(), + }; + + // We insert the instance on the database, on the `League` entity + new_league.insert().await.expect("Failed insert operation"); + + assert_eq!( + new_league.id, + League::find_by_pk_with(&new_league.id, PSQL_DS) + .await + .expect("Request error") + .expect("None value") + .id + ); + + // Now that we have an instance mapped to some entity by a primary key, we can now + // remove that entry from the database with the delete operation + new_league + .delete() + .await + .expect("Failed to delete the operation"); + + // To check the success, we can query by the primary key value and check if, after unwrap() + // the result of the operation, the find by primary key contains Some(v) or None + // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> + assert_eq!( + League::find_by_pk(&new_league.id) + .await + .expect("Unwrapping the result, letting the Option"), + None + ); +} + +/// Same as the delete test, but performing the operations with the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_delete_with_mssql_method_operation() { + // For test the delete, we will insert a new instance of the database, and then, + // after inspect it, we will proceed to delete it + let mut new_league: League = League { + id: Default::default(), + ext_id: 7892635306594_i64, + slug: "some-new-league".to_string(), + name: "Some New League".to_string(), + region: "Bahía de cochinos".to_string(), + image_url: "https://nobodyspectsandimage.io".to_string(), + }; + + // We insert the instance on the database, on the `League` entity + new_league + .insert_with(SQL_SERVER_DS) + .await + .expect("Failed insert operation"); + assert_eq!( + new_league.id, + League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) + .await + .expect("Request error") + .expect("None value") + .id + ); + + // Now that we have an instance mapped to some entity by a primary key, we can now + // remove that entry from the database with the delete operation + new_league + .delete_with(SQL_SERVER_DS) + .await + .expect("Failed to delete the operation"); + + // To check the success, we can query by the primary key value and check if, after unwrap() + // the result of the operation, the find by primary key contains Some(v) or None + // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> + assert_eq!( + League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) + .await + .expect("Unwrapping the result, letting the Option"), + None + ); +} + +/// Same as the delete test, but performing the operations with the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_delete_with_mysql_method_operation() { + // For test the delete, we will insert a new instance of the database, and then, + // after inspect it, we will proceed to delete it + let mut new_league: League = League { + id: Default::default(), + ext_id: 7892635306594_i64, + slug: "some-new-league".to_string(), + name: "Some New League".to_string(), + region: "Bahía de cochinos".to_string(), + image_url: "https://nobodyspectsandimage.io".to_string(), + }; + + // We insert the instance on the database, on the `League` entity + new_league + .insert_with(MYSQL_DS) + .await + .expect("Failed insert operation"); + assert_eq!( + new_league.id, + League::find_by_pk_with(&new_league.id, MYSQL_DS) + .await + .expect("Request error") + .expect("None value") + .id + ); + + // Now that we have an instance mapped to some entity by a primary key, we can now + // remove that entry from the database with the delete operation + new_league + .delete_with(MYSQL_DS) + .await + .expect("Failed to delete the operation"); + + // To check the success, we can query by the primary key value and check if, after unwrap() + // the result of the operation, the find by primary key contains Some(v) or None + // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> + assert_eq!( + League::find_by_pk_with(&new_league.id, MYSQL_DS) + .await + .expect("Unwrapping the result, letting the Option"), + None + ); +} diff --git a/tests/crud/foreign_key_operations.rs b/tests/crud/foreign_key_operations.rs index e8544df2..ae1c843c 100644 --- a/tests/crud/foreign_key_operations.rs +++ b/tests/crud/foreign_key_operations.rs @@ -1,163 +1,163 @@ -// /// Integration tests for the CRUD operations available in `Canyon` that -// /// generates and executes *SELECT* statements based on a entity -// /// annotated with the `#[foreign_key(... args)]` annotation looking -// /// for the related data with some entity `U` that acts as is parent, where `U` -// /// impls `ForeignKeyable` (isn't required, but it won't unlock the -// /// reverse search features parent -> child, only the child -> parent ones). -// /// -// /// Names of the foreign key methods are autogenerated for the direct and -// /// reverse side of the implementations. -// /// For more info: TODO -> Link to the docs of the foreign key chapter -// use canyon_sql::crud::CrudOperations; -// -// #[cfg(feature = "mssql")] -// use crate::constants::MYSQL_DS; -// #[cfg(feature = "mssql")] -// use crate::constants::SQL_SERVER_DS; -// -// use crate::tests_models::league::*; -// use crate::tests_models::tournament::*; -// -// /// Given an entity `T` which has some field declaring a foreign key relation -// /// with some another entity `U`, for example, performs a search to find -// /// what is the parent type `U` of `T` -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_by_foreign_key() { -// let some_tournament: Tournament = Tournament::find_by_pk(&1) -// .await -// .expect("Result variant of the query is err") -// .expect("No result found for the given parameter"); -// -// // We can get the parent entity for the retrieved child instance -// let parent_entity: Option = some_tournament -// .search_league() -// .await -// .expect("Result variant of the query is err"); -// -// if let Some(league) = parent_entity { -// assert_eq!(some_tournament.league, league.id) -// } else { -// assert_eq!(parent_entity, None) -// } -// } -// -// /// Same as the search by foreign key, but with the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_by_foreign_key_with_mssql() { -// let some_tournament: Tournament = Tournament::find_by_pk_with(&10, SQL_SERVER_DS) -// .await -// .expect("Result variant of the query is err") -// .expect("No result found for the given parameter"); -// -// // We can get the parent entity for the retrieved child instance -// let parent_entity: Option = some_tournament -// .search_league_with(SQL_SERVER_DS) -// .await -// .expect("Result variant of the query is err"); -// -// // These are tests, and we could unwrap the result contained in the option, because -// // it always should exist that search for the data inserted when the docker starts. -// // But, just for change the style a little bit and offer more options about how to -// // handle things done with Canyon -// if let Some(league) = parent_entity { -// assert_eq!(some_tournament.league, league.id) -// } else { -// assert_eq!(parent_entity, None) -// } -// } -// -// /// Same as the search by foreign key, but with the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_by_foreign_key_with_mysql() { -// let some_tournament: Tournament = Tournament::find_by_pk_with(&10, MYSQL_DS) -// .await -// .expect("Result variant of the query is err") -// .expect("No result found for the given parameter"); -// -// // We can get the parent entity for the retrieved child instance -// let parent_entity: Option = some_tournament -// .search_league_with(MYSQL_DS) -// .await -// .expect("Result variant of the query is err"); -// -// // These are tests, and we could unwrap the result contained in the option, because -// // it always should exist that search for the data inserted when the docker starts. -// // But, just for change the style a little bit and offer more options about how to -// // handle things done with Canyon -// if let Some(league) = parent_entity { -// assert_eq!(some_tournament.league, league.id) -// } else { -// assert_eq!(parent_entity, None) -// } -// } -// -// /// Given an entity `U` that is know as the "parent" side of the relation with another -// /// entity `T`, for example, we can ask to the parent for the childrens that belongs -// /// to `U`. -// /// -// /// For this to work, `U`, the parent, must have derived the `ForeignKeyable` proc macro -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_reverse_side_foreign_key() { -// let some_league: League = League::find_by_pk(&1) -// .await -// .expect("Result variant of the query is err") -// .expect("No result found for the given parameter"); -// -// // Computes how many tournaments are pointing to the retrieved league -// let child_tournaments: Vec = Tournament::search_league_childrens(&some_league) -// .await -// .expect("Result variant of the query is err"); -// -// assert!(!child_tournaments.is_empty()); -// child_tournaments -// .iter() -// .for_each(|t| assert_eq!(t.league, some_league.id)); -// } -// -// /// Same as the search by the reverse side of a foreign key relation -// /// but with the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_reverse_side_foreign_key_with_mssql() { -// let some_league: League = League::find_by_pk_with(&1, SQL_SERVER_DS) -// .await -// .expect("Result variant of the query is err") -// .expect("No result found for the given parameter"); -// -// // Computes how many tournaments are pointing to the retrieved league -// let child_tournaments: Vec = -// Tournament::search_league_childrens_with(&some_league, SQL_SERVER_DS) -// .await -// .expect("Result variant of the query is err"); -// -// assert!(!child_tournaments.is_empty()); -// child_tournaments -// .iter() -// .for_each(|t| assert_eq!(t.league, some_league.id)); -// } -// -// /// Same as the search by the reverse side of a foreign key relation -// /// but with the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_search_reverse_side_foreign_key_with_mysql() { -// let some_league: League = League::find_by_pk_with(&1, MYSQL_DS) -// .await -// .expect("Result variant of the query is err") -// .expect("No result found for the given parameter"); -// -// // Computes how many tournaments are pointing to the retrieved league -// let child_tournaments: Vec = -// Tournament::search_league_childrens_with(&some_league, MYSQL_DS) -// .await -// .expect("Result variant of the query is err"); -// -// assert!(!child_tournaments.is_empty()); -// child_tournaments -// .iter() -// .for_each(|t| assert_eq!(t.league, some_league.id)); -// } +/// Integration tests for the CRUD operations available in `Canyon` that +/// generates and executes *SELECT* statements based on a entity +/// annotated with the `#[foreign_key(... args)]` annotation looking +/// for the related data with some entity `U` that acts as is parent, where `U` +/// impls `ForeignKeyable` (isn't required, but it won't unlock the +/// reverse search features parent -> child, only the child -> parent ones). +/// +/// Names of the foreign key methods are autogenerated for the direct and +/// reverse side of the implementations. +/// For more info: TODO -> Link to the docs of the foreign key chapter +use canyon_sql::crud::CrudOperations; + +#[cfg(feature = "mssql")] +use crate::constants::MYSQL_DS; +#[cfg(feature = "mssql")] +use crate::constants::SQL_SERVER_DS; + +use crate::tests_models::league::*; +use crate::tests_models::tournament::*; + +/// Given an entity `T` which has some field declaring a foreign key relation +/// with some another entity `U`, for example, performs a search to find +/// what is the parent type `U` of `T` +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_search_by_foreign_key() { + let some_tournament: Tournament = Tournament::find_by_pk(&1) + .await + .expect("Result variant of the query is err") + .expect("No result found for the given parameter"); + + // We can get the parent entity for the retrieved child instance + let parent_entity: Option = some_tournament + .search_league() + .await + .expect("Result variant of the query is err"); + + if let Some(league) = parent_entity { + assert_eq!(some_tournament.league, league.id) + } else { + assert_eq!(parent_entity, None) + } +} + +/// Same as the search by foreign key, but with the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_search_by_foreign_key_with_mssql() { + let some_tournament: Tournament = Tournament::find_by_pk_with(&10, SQL_SERVER_DS) + .await + .expect("Result variant of the query is err") + .expect("No result found for the given parameter"); + + // We can get the parent entity for the retrieved child instance + let parent_entity: Option = some_tournament + .search_league_with(SQL_SERVER_DS) + .await + .expect("Result variant of the query is err"); + + // These are tests, and we could unwrap the result contained in the option, because + // it always should exist that search for the data inserted when the docker starts. + // But, just for change the style a little bit and offer more options about how to + // handle things done with Canyon + if let Some(league) = parent_entity { + assert_eq!(some_tournament.league, league.id) + } else { + assert_eq!(parent_entity, None) + } +} + +/// Same as the search by foreign key, but with the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_search_by_foreign_key_with_mysql() { + let some_tournament: Tournament = Tournament::find_by_pk_with(&10, MYSQL_DS) + .await + .expect("Result variant of the query is err") + .expect("No result found for the given parameter"); + + // We can get the parent entity for the retrieved child instance + let parent_entity: Option = some_tournament + .search_league_with(MYSQL_DS) + .await + .expect("Result variant of the query is err"); + + // These are tests, and we could unwrap the result contained in the option, because + // it always should exist that search for the data inserted when the docker starts. + // But, just for change the style a little bit and offer more options about how to + // handle things done with Canyon + if let Some(league) = parent_entity { + assert_eq!(some_tournament.league, league.id) + } else { + assert_eq!(parent_entity, None) + } +} + +/// Given an entity `U` that is know as the "parent" side of the relation with another +/// entity `T`, for example, we can ask to the parent for the childrens that belongs +/// to `U`. +/// +/// For this to work, `U`, the parent, must have derived the `ForeignKeyable` proc macro +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_search_reverse_side_foreign_key() { + let some_league: League = League::find_by_pk(&1) + .await + .expect("Result variant of the query is err") + .expect("No result found for the given parameter"); + + // Computes how many tournaments are pointing to the retrieved league + let child_tournaments = Tournament::search_league_childrens(&some_league) + .await + .expect("Result variant of the query is err"); + + assert!(!child_tournaments.is_empty()); + child_tournaments + .iter() + .for_each(|t| assert_eq!(t.league, some_league.id)); +} + +/// Same as the search by the reverse side of a foreign key relation +/// but with the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_search_reverse_side_foreign_key_with_mssql() { + let some_league: League = League::find_by_pk_with(&1, SQL_SERVER_DS) + .await + .expect("Result variant of the query is err") + .expect("No result found for the given parameter"); + + // Computes how many tournaments are pointing to the retrieved league + let child_tournaments: Vec = + Tournament::search_league_childrens_with(&some_league, SQL_SERVER_DS) + .await + .expect("Result variant of the query is err"); + + assert!(!child_tournaments.is_empty()); + child_tournaments + .iter() + .for_each(|t| assert_eq!(t.league, some_league.id)); +} + +/// Same as the search by the reverse side of a foreign key relation +/// but with the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_search_reverse_side_foreign_key_with_mysql() { + let some_league: League = League::find_by_pk_with(&1, MYSQL_DS) + .await + .expect("Result variant of the query is err") + .expect("No result found for the given parameter"); + + // Computes how many tournaments are pointing to the retrieved league + let child_tournaments: Vec = + Tournament::search_league_childrens_with(&some_league, MYSQL_DS) + .await + .expect("Result variant of the query is err"); + + assert!(!child_tournaments.is_empty()); + child_tournaments + .iter() + .for_each(|t| assert_eq!(t.league, some_league.id)); +} diff --git a/tests/crud/insert_operations.rs b/tests/crud/insert_operations.rs index 1f0e1078..f6a7d074 100644 --- a/tests/crud/insert_operations.rs +++ b/tests/crud/insert_operations.rs @@ -120,7 +120,7 @@ fn test_crud_insert_with_mysql_operation() { assert_eq!(new_league.id, inserted_league.id); } -// +// // /// The multi insert operation is a shorthand for insert multiple instances of *T* // /// in the database at once. // /// @@ -158,7 +158,7 @@ fn test_crud_insert_with_mysql_operation() { // region: "The dark side of the moon".to_string(), // image_url: "https://interplanetary-league.io".to_string(), // }; -// +// // // Insert the instance as database entities // new_league_mi // .insert() @@ -172,7 +172,7 @@ fn test_crud_insert_with_mysql_operation() { // .insert() // .await // .expect("Failed insert datasource operation"); -// +// // // Recover the inserted data by primary key // let inserted_league = League::find_by_pk(&new_league_mi.id) // .await @@ -186,12 +186,12 @@ fn test_crud_insert_with_mysql_operation() { // .await // .expect("[3] - Failed the query to the database") // .expect("[3] - No entity found for the primary key value passed in"); -// +// // assert_eq!(new_league_mi.id, inserted_league.id); // assert_eq!(new_league_mi_2.id, inserted_league_2.id); // assert_eq!(new_league_mi_3.id, inserted_league_3.id); // } -// +// // /// Same as the multi insert above, but with the specified datasource // #[cfg(feature = "mssql")] // #[canyon_sql::macros::canyon_tokio_test] @@ -220,7 +220,7 @@ fn test_crud_insert_with_mysql_operation() { // region: "The dark side of the moon".to_string(), // image_url: "https://interplanetary-league.io".to_string(), // }; -// +// // // Insert the instance as database entities // new_league_mi // .insert_with(SQL_SERVER_DS) @@ -234,7 +234,7 @@ fn test_crud_insert_with_mysql_operation() { // .insert_with(SQL_SERVER_DS) // .await // .expect("Failed insert datasource operation"); -// +// // // Recover the inserted data by primary key // let inserted_league = League::find_by_pk_with(&new_league_mi.id, SQL_SERVER_DS) // .await @@ -248,12 +248,12 @@ fn test_crud_insert_with_mysql_operation() { // .await // .expect("[3] - Failed the query to the database") // .expect("[3] - No entity found for the primary key value passed in"); -// +// // assert_eq!(new_league_mi.id, inserted_league.id); // assert_eq!(new_league_mi_2.id, inserted_league_2.id); // assert_eq!(new_league_mi_3.id, inserted_league_3.id); // } -// +// // /// Same as the multi insert above, but with the specified datasource // #[cfg(feature = "mysql")] // #[canyon_sql::macros::canyon_tokio_test] @@ -282,7 +282,7 @@ fn test_crud_insert_with_mysql_operation() { // region: "The dark side of the moon".to_string(), // image_url: "https://interplanetary-league.io".to_string(), // }; -// +// // // Insert the instance as database entities // new_league_mi // .insert_with(MYSQL_DS) @@ -296,7 +296,7 @@ fn test_crud_insert_with_mysql_operation() { // .insert_with(MYSQL_DS) // .await // .expect("Failed insert datasource operation"); -// +// // // Recover the inserted data by primary key // let inserted_league = League::find_by_pk_with(&new_league_mi.id, MYSQL_DS) // .await @@ -310,7 +310,7 @@ fn test_crud_insert_with_mysql_operation() { // .await // .expect("[3] - Failed the query to the database") // .expect("[3] - No entity found for the primary key value passed in"); -// +// // assert_eq!(new_league_mi.id, inserted_league.id); // assert_eq!(new_league_mi_2.id, inserted_league_2.id); // assert_eq!(new_league_mi_3.id, inserted_league_3.id); diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index 0e7d03ed..a8dd56c3 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -234,234 +234,250 @@ fn test_crud_find_with_querybuilder_with_mysql() { assert!(!filtered_find_players.unwrap().is_empty()); } -// /// Updates the values of the range on entries defined by the constraint parameters -// /// in the database entity -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_with_querybuilder() { -// // Find all the leagues with ID less or equals that 7 -// // and where it's region column value is equals to 'Korea' -// let q = League::update_query() -// .set(&[ -// (LeagueField::slug, "Updated with the QueryBuilder"), -// (LeagueField::name, "Random"), -// ]) -// .r#where(LeagueFieldValue::id(&1), Comp::Gt) -// .and(LeagueFieldValue::id(&8), Comp::Lt); -// -// /* NOTE: Family of QueryBuilders are clone, useful in case of need to read the generated SQL -// let qpr = q.clone(); -// println!("PSQL: {:?}", qpr.read_sql()); -// */ -// q.query() -// .await -// .expect("Failed to update records with the querybuilder"); -// -// let found_updated_values = League::select_query() -// .r#where(LeagueFieldValue::id(&1), Comp::Gt) -// .and(LeagueFieldValue::id(&7), Comp::Lt) -// .query() -// .await -// .expect("Failed to retrieve database League entries with the querybuilder"); -// -// found_updated_values -// .iter() -// .for_each(|league| assert_eq!(league.slug, "Updated with the QueryBuilder")); -// } -// -// /// Same as above, but with the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_with_querybuilder_with_mssql() { -// // Find all the leagues with ID less or equals that 7 -// // and where it's region column value is equals to 'Korea' -// let q = Player::update_query_with(SQL_SERVER_DS); -// q.set(&[ -// (PlayerField::summoner_name, "Random updated player name"), -// (PlayerField::first_name, "I am an updated first name"), -// ]) -// .r#where(PlayerFieldValue::id(&1), Comp::Gt) -// .and(PlayerFieldValue::id(&8), Comp::Lt) -// .query() -// .await -// .expect("Failed to update records with the querybuilder"); -// -// let found_updated_values = Player::select_query_with(SQL_SERVER_DS) -// .r#where(PlayerFieldValue::id(&1), Comp::Gt) -// .and(PlayerFieldValue::id(&7), Comp::LtEq) -// .query() -// .await -// .expect("Failed to retrieve database League entries with the querybuilder"); -// -// found_updated_values.iter().for_each(|player| { -// assert_eq!(player.summoner_name, "Random updated player name"); -// assert_eq!(player.first_name, "I am an updated first name"); -// }); -// } -// -// /// Same as above, but with the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_update_with_querybuilder_with_mysql() { -// // Find all the leagues with ID less or equals that 7 -// // and where it's region column value is equals to 'Korea' -// -// let q = Player::update_query_with(MYSQL_DS); -// q.set(&[ -// (PlayerField::summoner_name, "Random updated player name"), -// (PlayerField::first_name, "I am an updated first name"), -// ]) -// .r#where(PlayerFieldValue::id(&1), Comp::Gt) -// .and(PlayerFieldValue::id(&8), Comp::Lt) -// .query() -// .await -// .expect("Failed to update records with the querybuilder"); -// -// let found_updated_values = Player::select_query_with(MYSQL_DS) -// .r#where(PlayerFieldValue::id(&1), Comp::Gt) -// .and(PlayerFieldValue::id(&7), Comp::LtEq) -// .query() -// .await -// .expect("Failed to retrieve database League entries with the querybuilder"); -// -// found_updated_values.iter().for_each(|player| { -// assert_eq!(player.summoner_name, "Random updated player name"); -// assert_eq!(player.first_name, "I am an updated first name"); -// }); -// } -// -// /// Deletes entries from the mapped entity `T` that are in the ranges filtered -// /// with the QueryBuilder -// /// -// /// Note if the database is persisted (not created and destroyed on every docker or -// /// GitHub Action wake up), it won't delete things that already have been deleted, -// /// but this isn't an error. They just don't exists. -// #[cfg(feature = "postgres")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_with_querybuilder() { -// Tournament::delete_query() -// .r#where(TournamentFieldValue::id(&14), Comp::Gt) -// .and(TournamentFieldValue::id(&16), Comp::Lt) -// .query() -// .await -// .expect("Error connecting with the database on the delete operation"); -// -// assert_eq!(Tournament::find_by_pk(&15).await.unwrap(), None); -// } -// -// /// Same as the above delete, but with the specified datasource -// #[cfg(feature = "mssql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_with_querybuilder_with_mssql() { -// Player::delete_query_with(SQL_SERVER_DS) -// .r#where(PlayerFieldValue::id(&120), Comp::Gt) -// .and(PlayerFieldValue::id(&130), Comp::Lt) -// .query() -// .await -// .expect("Error connecting with the database when we are going to delete data! :)"); -// -// assert!(Player::select_query_with(SQL_SERVER_DS) -// .r#where(PlayerFieldValue::id(&122), Comp::Eq) -// .query() -// .await -// .unwrap() -// .is_empty()); -// } -// -// /// Same as the above delete, but with the specified datasource -// #[cfg(feature = "mysql")] -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_crud_delete_with_querybuilder_with_mysql() { -// Player::delete_query_with(MYSQL_DS) -// .r#where(PlayerFieldValue::id(&120), Comp::Gt) -// .and(PlayerFieldValue::id(&130), Comp::Lt) -// .query() -// .await -// .expect("Error connecting with the database when we are going to delete data! :)"); -// -// assert!(Player::select_query_with(MYSQL_DS) -// .r#where(PlayerFieldValue::id(&122), Comp::Eq) -// .query() -// .await -// .unwrap() -// .is_empty()); -// } -// -// /// Tests for the generated SQL query after use the -// /// WHERE clause -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_where_clause() { -// let l = League::select_query().r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq); -// -// assert_eq!(l.read_sql(), "SELECT * FROM league WHERE name = $1") -// } -// -// /// Tests for the generated SQL query after use the -// /// AND clause -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_and_clause() { -// let l = League::select_query() -// .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) -// .and(LeagueFieldValue::id(&10), Comp::LtEq); -// -// assert_eq!( -// l.read_sql().trim(), -// "SELECT * FROM league WHERE name = $1 AND id <= $2" -// ) -// } -// -// /// Tests for the generated SQL query after use the -// /// AND clause -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_and_clause_with_in_constraint() { -// let l = League::select_query() -// .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) -// .and_values_in(LeagueField::id, &[1, 7, 10]); -// -// assert_eq!( -// l.read_sql().trim(), -// "SELECT * FROM league WHERE name = $1 AND id IN ($1, $2, $3)" -// ) -// } -// -// /// Tests for the generated SQL query after use the -// /// AND clause -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_or_clause() { -// let l = League::select_query() -// .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) -// .or(LeagueFieldValue::id(&10), Comp::LtEq); -// -// assert_eq!( -// l.read_sql().trim(), -// "SELECT * FROM league WHERE name = $1 OR id <= $2" -// ) -// } -// -// /// Tests for the generated SQL query after use the -// /// AND clause -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_or_clause_with_in_constraint() { -// let l = League::select_query() -// .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) -// .or_values_in(LeagueField::id, &[1, 7, 10]); -// -// assert_eq!( -// l.read_sql(), -// "SELECT * FROM league WHERE name = $1 OR id IN ($1, $2, $3)" -// ) -// } -// -// /// Tests for the generated SQL query after use the -// /// AND clause -// #[canyon_sql::macros::canyon_tokio_test] -// fn test_order_by_clause() { -// let l = League::select_query() -// .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) -// .order_by(LeagueField::id, false); -// -// assert_eq!( -// l.read_sql(), -// "SELECT * FROM league WHERE name = $1 ORDER BY id" -// ) -// } +/// Updates the values of the range on entries defined by the constraint parameters +/// in the database entity +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_update_with_querybuilder() { + // Find all the leagues with ID less or equals that 7 + // and where it's region column value is equals to 'Korea' + let q = League::update_query() + .unwrap() + .set(&[ + (LeagueField::slug, "Updated with the QueryBuilder"), + (LeagueField::name, "Random"), + ]) + .r#where(LeagueFieldValue::id(&1), Comp::Gt) + .and(LeagueFieldValue::id(&8), Comp::Lt); + + /* NOTE: Family of QueryBuilders are clone, useful in case of need to read the generated SQL + let qpr = q.clone(); + println!("PSQL: {:?}", qpr.read_sql()); + */ + q.query() + .await + .expect("Failed to update records with the querybuilder"); + + let found_updated_values = League::select_query() + .unwrap() + .r#where(LeagueFieldValue::id(&1), Comp::Gt) + .and(LeagueFieldValue::id(&7), Comp::Lt) + .query() + .await + .expect("Failed to retrieve database League entries with the querybuilder"); + + found_updated_values + .iter() + .for_each(|league| assert_eq!(league.slug, "Updated with the QueryBuilder")); +} + +/// Same as above, but with the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_update_with_querybuilder_with_mssql() { + // Find all the leagues with ID less or equals that 7 + // and where it's region column value is equals to 'Korea' + let q = Player::update_query_with(SQL_SERVER_DS).unwrap(); + q.set(&[ + (PlayerField::summoner_name, "Random updated player name"), + (PlayerField::first_name, "I am an updated first name"), + ]) + .r#where(PlayerFieldValue::id(&1), Comp::Gt) + .and(PlayerFieldValue::id(&8), Comp::Lt) + .query() + .await + .expect("Failed to update records with the querybuilder"); + + let found_updated_values = Player::select_query_with(SQL_SERVER_DS) + .unwrap() + .r#where(PlayerFieldValue::id(&1), Comp::Gt) + .and(PlayerFieldValue::id(&7), Comp::LtEq) + .query() + .await + .expect("Failed to retrieve database League entries with the querybuilder"); + + found_updated_values.iter().for_each(|player| { + assert_eq!(player.summoner_name, "Random updated player name"); + assert_eq!(player.first_name, "I am an updated first name"); + }); +} + +/// Same as above, but with the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_update_with_querybuilder_with_mysql() { + // Find all the leagues with ID less or equals that 7 + // and where it's region column value is equals to 'Korea' + + let q = Player::update_query_with(MYSQL_DS).unwrap(); + q.set(&[ + (PlayerField::summoner_name, "Random updated player name"), + (PlayerField::first_name, "I am an updated first name"), + ]) + .r#where(PlayerFieldValue::id(&1), Comp::Gt) + .and(PlayerFieldValue::id(&8), Comp::Lt) + .query() + .await + .expect("Failed to update records with the querybuilder"); + + let found_updated_values = Player::select_query_with(MYSQL_DS) + .unwrap() + .r#where(PlayerFieldValue::id(&1), Comp::Gt) + .and(PlayerFieldValue::id(&7), Comp::LtEq) + .query() + .await + .expect("Failed to retrieve database League entries with the querybuilder"); + + found_updated_values.iter().for_each(|player| { + assert_eq!(player.summoner_name, "Random updated player name"); + assert_eq!(player.first_name, "I am an updated first name"); + }); +} + +/// Deletes entries from the mapped entity `T` that are in the ranges filtered +/// with the QueryBuilder +/// +/// Note if the database is persisted (not created and destroyed on every docker or +/// GitHub Action wake up), it won't delete things that already have been deleted, +/// but this isn't an error. They just don't exists. +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_delete_with_querybuilder() { + Tournament::delete_query() + .unwrap() + .r#where(TournamentFieldValue::id(&14), Comp::Gt) + .and(TournamentFieldValue::id(&16), Comp::Lt) + .query() + .await + .expect("Error connecting with the database on the delete operation"); + + assert_eq!(Tournament::find_by_pk(&15).await.unwrap(), None); +} + +/// Same as the above delete, but with the specified datasource +#[cfg(feature = "mssql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_delete_with_querybuilder_with_mssql() { + Player::delete_query_with(SQL_SERVER_DS) + .unwrap() + .r#where(PlayerFieldValue::id(&120), Comp::Gt) + .and(PlayerFieldValue::id(&130), Comp::Lt) + .query() + .await + .expect("Error connecting with the database when we are going to delete data! :)"); + + assert!(Player::select_query_with(SQL_SERVER_DS) + .unwrap() + .r#where(PlayerFieldValue::id(&122), Comp::Eq) + .query() + .await + .unwrap() + .is_empty()); +} + +/// Same as the above delete, but with the specified datasource +#[cfg(feature = "mysql")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_crud_delete_with_querybuilder_with_mysql() { + Player::delete_query_with(MYSQL_DS) + .unwrap() + .r#where(PlayerFieldValue::id(&120), Comp::Gt) + .and(PlayerFieldValue::id(&130), Comp::Lt) + .query() + .await + .expect("Error connecting with the database when we are going to delete data! :)"); + + assert!(Player::select_query_with(MYSQL_DS) + .unwrap() + .r#where(PlayerFieldValue::id(&122), Comp::Eq) + .query() + .await + .unwrap() + .is_empty()); +} + +/// Tests for the generated SQL query after use the +/// WHERE clause +#[canyon_sql::macros::canyon_tokio_test] +fn test_where_clause() { + let l = League::select_query() + .unwrap() + .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq); + + assert_eq!(l.read_sql(), "SELECT * FROM league WHERE name = $1") +} + +/// Tests for the generated SQL query after use the +/// AND clause +#[canyon_sql::macros::canyon_tokio_test] +fn test_and_clause() { + let l = League::select_query() + .unwrap() + .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) + .and(LeagueFieldValue::id(&10), Comp::LtEq); + + assert_eq!( + l.read_sql().trim(), + "SELECT * FROM league WHERE name = $1 AND id <= $2" + ) +} + +/// Tests for the generated SQL query after use the +/// AND clause +#[canyon_sql::macros::canyon_tokio_test] +fn test_and_clause_with_in_constraint() { + let l = League::select_query() + .unwrap() + .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) + .and_values_in(LeagueField::id, &[1, 7, 10]); + + assert_eq!( + l.read_sql().trim(), + "SELECT * FROM league WHERE name = $1 AND id IN ($1, $2, $3)" + ) +} + +/// Tests for the generated SQL query after use the +/// AND clause +#[canyon_sql::macros::canyon_tokio_test] +fn test_or_clause() { + let l = League::select_query() + .unwrap() + .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) + .or(LeagueFieldValue::id(&10), Comp::LtEq); + + assert_eq!( + l.read_sql().trim(), + "SELECT * FROM league WHERE name = $1 OR id <= $2" + ) +} + +/// Tests for the generated SQL query after use the +/// AND clause +#[canyon_sql::macros::canyon_tokio_test] +fn test_or_clause_with_in_constraint() { + let l = League::select_query() + .unwrap() + .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) + .or_values_in(LeagueField::id, &[1, 7, 10]); + + assert_eq!( + l.read_sql(), + "SELECT * FROM league WHERE name = $1 OR id IN ($1, $2, $3)" + ) +} + +/// Tests for the generated SQL query after use the +/// AND clause +#[canyon_sql::macros::canyon_tokio_test] +fn test_order_by_clause() { + let l = League::select_query() + .unwrap() + .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) + .order_by(LeagueField::id, false); + + assert_eq!( + l.read_sql(), + "SELECT * FROM league WHERE name = $1 ORDER BY id" + ) +} From 5291334d664bca6be2e64d0689ca21679e905a3c Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 2 May 2025 08:41:00 +0200 Subject: [PATCH 094/155] feat: upgrades to the internal macro implementation of the proc macros of the foreign key crud operations --- .../src/query_operations/foreign_key.rs | 42 ++++++++----------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index 5aaabe40..18fe19ad 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -171,6 +171,18 @@ fn generate_find_by_reverse_foreign_key_tokens( }; let f_ident = field_ident.to_string(); + let lookup_value = quote! { + value.get_fk_column(#column) + .ok_or_else(|| format!( + "Column: {:?} not found in type: {:?}", #column, #table + ))?; + }; + + let stmt = quote!{&format!( + "SELECT * FROM {} WHERE {} = $1", + #table_schema_data, + format!("\"{}\"", #f_ident).as_str() + )}; rev_fk_quotes.push(( quote! { #quoted_method_signature; }, @@ -179,20 +191,10 @@ fn generate_find_by_reverse_foreign_key_tokens( /// performs a search to find the children that belong to that concrete parent. #quoted_method_signature { - let lookage_value = value.get_fk_column(#column) - .expect(format!( - "Column: {:?} not found in type: {:?}", #column, #table - ).as_str()); - - let stmt = &format!( - "SELECT * FROM {} WHERE {} = $1", - #table_schema_data, - format!("\"{}\"", #f_ident).as_str() - ); - + let lookup_value = #lookup_value; <#ty as canyon_sql::core::Transaction>::query::<&str, #mapper_ty>( - stmt, - &[lookage_value], + #stmt, + &[lookup_value], "" ).await } @@ -207,18 +209,8 @@ fn generate_find_by_reverse_foreign_key_tokens( /// with the specified datasource. #quoted_with_method_signature { - let lookage_value = value.get_fk_column(#column) - .expect(format!( - "Column: {:?} not found in type: {:?}", #column, #table - ).as_str()); - - let stmt = &format!( - "SELECT * FROM {} WHERE {} = $1", - #table_schema_data, - format!("\"{}\"", #f_ident).as_str() - ); - - input.query::<&str, #mapper_ty>(stmt, &[lookage_value]).await + let lookup_value = #lookup_value; + input.query::<&str, #mapper_ty>(#stmt, &[lookup_value]).await } }, )); From 9b9b8db29f21e6411b756426830c9de4533f6f9a Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 2 May 2025 09:38:27 +0200 Subject: [PATCH 095/155] perf: avoiding unnecessary Vec heap allocations on the pk macros when passing the target input parameter --- canyon_macros/src/query_operations/read.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 72057798..2d8261f4 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -256,7 +256,7 @@ mod __details { &str, &[&'a (dyn QueryParameter<'a>)], #mapper_ty - >(#stmt, &vec![value], "").await + >(#stmt, &[value], "").await } } else { quote! { #pk_runtime_error } @@ -278,7 +278,7 @@ mod __details { ) -> TokenStream { let body = if pk_runtime_error.is_none() { quote! { - input.query_one::<#mapper_ty>(#stmt, &vec![value]).await + input.query_one::<#mapper_ty>(#stmt, &[value]).await } } else { quote! { #pk_runtime_error } From 53a94ac0393a5ddcf1657d96dff186923a6cdc51 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 2 May 2025 10:56:26 +0200 Subject: [PATCH 096/155] refactor: upgrades to the `[canyon::main]` macro --- canyon_core/src/connection/mod.rs | 24 ++++++---------- canyon_macros/src/canyon_macro.rs | 2 ++ canyon_macros/src/lib.rs | 33 ++++++++++++---------- canyon_macros/src/utils/function_parser.rs | 24 ++++------------ 4 files changed, 34 insertions(+), 49 deletions(-) diff --git a/canyon_core/src/connection/mod.rs b/canyon_core/src/connection/mod.rs index 5b96e420..bfe56664 100644 --- a/canyon_core/src/connection/mod.rs +++ b/canyon_core/src/connection/mod.rs @@ -22,7 +22,7 @@ pub mod db_connector; use std::path::PathBuf; use std::{error::Error, fs}; - +use std::sync::OnceLock; use conn_errors::DatasourceNotFound; use datasources::{CanyonSqlConfig, DatasourceConfig}; use db_connector::DatabaseConnection; @@ -88,25 +88,17 @@ fn find_canyon_config_file() -> PathBuf { /// job done. pub async fn init_connections_cache() { for datasource in DATASOURCES.iter() { - let db_conn = DatabaseConnection::new(datasource).await; - - if let Err(e) = db_conn { - panic!( - "Error opening database connection for {}. Err: {}", - datasource.name, e - ); + match DatabaseConnection::new(datasource).await { + Ok(conn) => { + CACHED_DATABASE_CONN.lock().await.insert(&datasource.name, conn); + } + Err(e) => { + panic!("Error opening database connection for {}: {}", datasource.name, e); + } } - - CACHED_DATABASE_CONN.lock().await.insert( - &datasource.name, - DatabaseConnection::new(datasource).await.unwrap(), - ); } } -// TODO: idea. Should we leak the datasources config pull to the user, so we can be more flexible and let the -// user code determine whenever you can find a valid datasource via a concrete type instead of an string? - // TODO: doc (main way for the user to obtain a db connection given a datasource identifier) pub async fn get_database_connection_by_ds( datasource_name: Option<&str>, diff --git a/canyon_macros/src/canyon_macro.rs b/canyon_macros/src/canyon_macro.rs index 17ace82f..dbf0a822 100644 --- a/canyon_macros/src/canyon_macro.rs +++ b/canyon_macros/src/canyon_macro.rs @@ -7,6 +7,8 @@ use canyon_migrations::{CM_QUERIES_TO_EXECUTE, QUERIES_TO_EXECUTE}; use proc_macro2::TokenStream; use quote::quote; + + pub fn main_with_queries() -> TokenStream { // TODO: migrations on main instead of main_with_queries CANYON_TOKIO_RUNTIME.block_on(async { diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index 2ee8e599..a7e19b20 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -13,7 +13,7 @@ mod utils; use proc_macro::TokenStream as CompilerTokenStream; use quote::quote; -use syn::DeriveInput; +use syn::{parse_macro_input, DeriveInput, Error}; use utils::{function_parser::FunctionParser, helpers, macro_tokens::MacroTokens}; use crate::canyon_entity_macro::generate_canyon_entity_tokens; @@ -35,26 +35,28 @@ use canyon_entities::{ /// the necessary operations for the migrations #[proc_macro_attribute] pub fn main(_meta: CompilerTokenStream, input: CompilerTokenStream) -> CompilerTokenStream { - let func_res = syn::parse::(input); - if func_res.is_err() { - return quote! { fn main() {} }.into(); + let func = parse_macro_input!(input as FunctionParser); + + if func.sig.ident != "main" { // Ensure the function is literally named "main" + return Error::new( + func.sig.ident.span(), + "The #[canyon::main] macro can only be applied to `fn main()`", + ).to_compile_error().into(); } - // TODO check if the `canyon` macro it's attached only to main? - let func = func_res.ok().unwrap(); - let sign = func.sig; + let vis = func.sig; + let sign = func.vis; + let attrs = func.attrs; let body = func.block.stmts; #[allow(unused_mut, unused_assignments)] let mut migrations_tokens = quote! {}; #[cfg(feature = "migrations")] - { - migrations_tokens = main_with_queries(); - } - - // The final code wired in main() - quote! { - #sign { + { migrations_tokens = main_with_queries(); } + + quote! { // The final code wired in main() + #(#attrs)* + #vis #sign { canyon_sql::runtime::CANYON_TOKIO_RUNTIME .handle() .block_on( async { @@ -80,6 +82,7 @@ pub fn canyon_tokio_test( quote! { fn non_valid_test_fn() {} }.into() } else { let func = func_res.ok().unwrap(); + let vis = func.vis; let sign = func.sig; let body = func.block.stmts; let attrs = func.attrs; @@ -87,7 +90,7 @@ pub fn canyon_tokio_test( quote! { #[test] #(#attrs)* - #sign { + #vis #sign { canyon_sql::runtime::CANYON_TOKIO_RUNTIME .handle() .block_on( async { diff --git a/canyon_macros/src/utils/function_parser.rs b/canyon_macros/src/utils/function_parser.rs index 841e534d..819d84f7 100644 --- a/canyon_macros/src/utils/function_parser.rs +++ b/canyon_macros/src/utils/function_parser.rs @@ -1,11 +1,7 @@ -use syn::{ - parse::{Parse, ParseBuffer}, - Attribute, Block, ItemFn, Signature, Visibility, -}; +use syn::{parse::{Parse, ParseBuffer}, Attribute, Block, ItemFn, Signature, Visibility}; /// Implementation of syn::Parse for the `#[canyon]` proc-macro #[derive(Clone)] -#[allow(dead_code)] pub struct FunctionParser { pub attrs: Vec, pub vis: Visibility, @@ -15,21 +11,13 @@ pub struct FunctionParser { impl Parse for FunctionParser { fn parse(input: &ParseBuffer) -> syn::Result { - let func = input.parse::(); + let func = input.parse::()?; - if func.is_err() { - return Err(syn::Error::new( - input.cursor().span(), - "Error on `fn main()`", - )); - } - - let func_ok = func.ok().unwrap(); Ok(Self { - attrs: func_ok.attrs, - vis: func_ok.vis, - sig: func_ok.sig, - block: func_ok.block, + attrs: func.attrs, + vis: func.vis, + sig: func.sig, + block: func.block, }) } } From a3761f06ea33e78441bf951ce6f15d7cb23e946f Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 2 May 2025 13:15:38 +0200 Subject: [PATCH 097/155] perf: reworked almost every bit of the shared global state (or Canyon Context) to work with Arc>. Solved some other perf issues while looking for connections. Made the default user defined connection much faster to access. --- Cargo.toml | 1 - canyon_core/Cargo.toml | 1 - canyon_core/src/connection/database_type.rs | 9 - canyon_core/src/connection/db_connector.rs | 32 ++- canyon_core/src/connection/mod.rs | 200 ++++++++++++------ canyon_macros/src/canyon_macro.rs | 6 +- canyon_macros/src/lib.rs | 21 +- .../src/query_operations/foreign_key.rs | 2 +- canyon_macros/src/utils/function_parser.rs | 5 +- canyon_migrations/src/migrations/handler.rs | 39 ++-- canyon_migrations/src/migrations/memory.rs | 23 +- canyon_migrations/src/migrations/processor.rs | 29 ++- src/lib.rs | 3 +- tests/migrations/mod.rs | 22 +- 14 files changed, 232 insertions(+), 161 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4fd57ff5..09cdcfc3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,6 @@ chrono = { version = "0.4", features = ["serde"] } # Just from TP better? serde = { version = "1.0.138", features = ["derive"] } futures = "0.3.25" -indexmap = "1.9.1" async-std = "1.12.0" lazy_static = "1.4.0" toml = "0.7.3" diff --git a/canyon_core/Cargo.toml b/canyon_core/Cargo.toml index d1829270..1c15fb29 100644 --- a/canyon_core/Cargo.toml +++ b/canyon_core/Cargo.toml @@ -23,7 +23,6 @@ tokio = { workspace = true } tokio-util = { workspace = true } futures = { workspace = true } -indexmap = { workspace = true } lazy_static = { workspace = true } toml = { workspace = true } serde = { workspace = true } diff --git a/canyon_core/src/connection/database_type.rs b/canyon_core/src/connection/database_type.rs index 8ac8a481..251e1af1 100644 --- a/canyon_core/src/connection/database_type.rs +++ b/canyon_core/src/connection/database_type.rs @@ -1,5 +1,4 @@ use super::datasources::Auth; -use crate::connection::DEFAULT_DATASOURCE; use serde::Deserialize; /// Holds the current supported databases by Canyon-SQL @@ -21,11 +20,3 @@ impl From<&Auth> for DatabaseType { value.get_db_type() } } - -/// The default implementation for [`DatabaseType`] returns the database type for the first -/// datasource configured -impl Default for DatabaseType { - fn default() -> Self { - DEFAULT_DATASOURCE.get_db_type() - } -} diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index a7c5b0f3..e3f6723c 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -9,7 +9,7 @@ use crate::connection::db_clients::postgresql::PostgreSqlConnection; use crate::connection::db_connector::connection_helpers::{ db_conn_launch_impl, db_conn_query_one_impl, }; -use crate::connection::{find_datasource_by_name_or_try_default, get_database_connection_by_ds}; +use crate::connection::{find_datasource_by_name_or_try_default, get_cached_connection}; use crate::mapper::RowMapper; use crate::query_parameters::QueryParameter; use crate::rows::{CanyonRows, FromSqlOwnedValue}; @@ -75,7 +75,7 @@ impl DbConnection for str { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result> { - let conn = get_database_connection_by_ds(Some(self)).await?; + let conn = get_cached_connection(self).await?; conn.query_rows(stmt, params).await } @@ -89,7 +89,7 @@ impl DbConnection for str { R: RowMapper, Vec: FromIterator<::Output>, { - let conn = get_database_connection_by_ds(Some(self)).await?; + let conn = get_cached_connection(self).await?; conn.query(stmt, params).await } @@ -101,8 +101,7 @@ impl DbConnection for str { where R: RowMapper, { - let sane_ds_name = if !self.is_empty() { Some(self) } else { None }; - let conn = get_database_connection_by_ds(sane_ds_name).await?; + let conn = get_cached_connection(self).await?; conn.query_one::(stmt, params).await } @@ -111,8 +110,7 @@ impl DbConnection for str { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result> { - let sane_ds_name = if !self.is_empty() { Some(self) } else { None }; - let conn = get_database_connection_by_ds(sane_ds_name).await?; + let conn = get_cached_connection(self).await?; conn.query_one_for(stmt, params).await } @@ -121,13 +119,12 @@ impl DbConnection for str { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result> { - let sane_ds_name = if !self.is_empty() { Some(self) } else { None }; - let conn = get_database_connection_by_ds(sane_ds_name).await?; + let conn = get_cached_connection(self).await?; conn.execute(stmt, params).await } fn get_database_type(&self) -> Result> { - Ok(find_datasource_by_name_or_try_default(Some(self))?.get_db_type()) + Ok(find_datasource_by_name_or_try_default(self)?.get_db_type()) } } @@ -141,7 +138,7 @@ impl DbConnection for &str { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result> { - let conn = get_database_connection_by_ds(Some(self)).await?; + let conn = get_cached_connection(self).await?; conn.query_rows(stmt, params).await } @@ -155,7 +152,7 @@ impl DbConnection for &str { R: RowMapper, Vec: FromIterator<::Output>, { - let conn = get_database_connection_by_ds(Some(self)).await?; + let conn = get_cached_connection(self).await?; conn.query(stmt, params).await } @@ -167,8 +164,7 @@ impl DbConnection for &str { where R: RowMapper, { - let sane_ds_name = if !self.is_empty() { Some(*self) } else { None }; - let conn = get_database_connection_by_ds(sane_ds_name).await?; + let conn = get_cached_connection(self).await?; conn.query_one::(stmt, params).await } @@ -177,8 +173,7 @@ impl DbConnection for &str { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result> { - let sane_ds_name = if !self.is_empty() { Some(*self) } else { None }; - let conn = get_database_connection_by_ds(sane_ds_name).await?; + let conn = get_cached_connection(self).await?; conn.query_one_for(stmt, params).await } @@ -187,13 +182,12 @@ impl DbConnection for &str { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result> { - let sane_ds_name = if !self.is_empty() { Some(*self) } else { None }; - let conn = get_database_connection_by_ds(sane_ds_name).await?; + let conn = get_cached_connection(self).await?; conn.execute(stmt, params).await } fn get_database_type(&self) -> Result> { - Ok(find_datasource_by_name_or_try_default(Some(*self))?.get_db_type()) + Ok(find_datasource_by_name_or_try_default(*self)?.get_db_type()) } } diff --git a/canyon_core/src/connection/mod.rs b/canyon_core/src/connection/mod.rs index bfe56664..9a037df7 100644 --- a/canyon_core/src/connection/mod.rs +++ b/canyon_core/src/connection/mod.rs @@ -20,102 +20,172 @@ pub mod datasources; pub mod db_clients; pub mod db_connector; -use std::path::PathBuf; -use std::{error::Error, fs}; -use std::sync::OnceLock; use conn_errors::DatasourceNotFound; use datasources::{CanyonSqlConfig, DatasourceConfig}; use db_connector::DatabaseConnection; -use indexmap::IndexMap; use lazy_static::lazy_static; +use std::collections::HashMap; +use std::path::PathBuf; +use std::sync::{Arc, OnceLock}; +use std::{error::Error, fs}; use tokio::sync::Mutex; use walkdir::WalkDir; // TODO's: DatabaseConnection and DataSource can implement default, so there's no need to use str and &str // as defaults anymore, since the can load as the default the first one defined in the config file, or have more // complex workflows that are deferred to initialization time -// NOTE: There's some way to read the cfg at compile time, an if there's no datasource defined, for the ops that -// handle the db connection (the _with ones) to just look for a datasource -> runtime in the cfg file? + // TODO: Crud Operations should be split into two different derives, splitting the automagic from the _with ones -// TODO: T + lazy_static! { pub static ref CANYON_TOKIO_RUNTIME: tokio::runtime::Runtime = tokio::runtime::Runtime::new() // TODO Make the config with the builder .expect("Failed initializing the Canyon-SQL Tokio Runtime"); +} +static CONFIG_FILE_PATH: OnceLock = OnceLock::new(); +static CONFIG: OnceLock = OnceLock::new(); +static DATASOURCES: OnceLock> = OnceLock::new(); - static ref CONFIG_FILE: CanyonSqlConfig = toml::from_str(&fs::read_to_string(find_canyon_config_file()) - .expect("Error opening or reading the Canyon configuration file")) // unwrap or default, to allow the builder to later configure manually data - .expect("Error generating the configuration for Canyon-SQL"); - - pub static ref DATASOURCES: Vec = - CONFIG_FILE.canyon_sql.datasources.clone(); - - pub static ref DEFAULT_DATASOURCE: &'static DatasourceConfig = DATASOURCES.first() - .expect("No datasource configured"); +// Safer connection wrapper: each conn has its own async mutex +pub type SharedConnection = Arc>; - pub static ref CACHED_DATABASE_CONN: Mutex> = - Mutex::new(IndexMap::new()); -} +static CACHED_DATABASE_CONN: OnceLock> = OnceLock::new(); +static DEFAULT_CONNECTION: OnceLock = OnceLock::new(); -fn find_canyon_config_file() -> PathBuf { - for e in WalkDir::new(".") +/// Attempts to locate a canyon config file. +/// Returns Ok(None) if not found, or Ok(Some(PathBuf)) if found. +pub fn find_canyon_config_file() -> Result, std::io::Error> { + let result = WalkDir::new(".") .max_depth(2) .into_iter() - .filter_map(|e| e.ok()) - { - let filename = e.file_name().to_str().unwrap(); // TODO: remove the .unwrap(). Use - // lowercase to allow Canyon.toml - if e.metadata().unwrap().is_file() - && filename.starts_with("canyon") - && filename.ends_with(".toml") - { - return e.path().to_path_buf(); - } - } + .filter_map(Result::ok) + .find_map(|e| { + let filename = e.file_name().to_string_lossy().to_lowercase(); + if e.metadata().ok()?.is_file() + && filename.starts_with("canyon") + && filename.ends_with(".toml") + { + Some(e.path().to_path_buf()) + } else { + None + } + }); - panic!() // TODO: get rid out of this panic and return Err instead + Ok(result) } -/// Convenient free function to initialize a kind of connection pool based on the datasources present defined -/// in the configuration file. +/// Initializes shared config state by loading the config file if found. /// -/// This avoids Canyon to create a new connection to the database on every query, potentially avoiding bottlenecks -/// coming from the instantiation of that new conn every time. +/// - Used by macro/automatic path to enforce config presence. +/// - Can be used optionally in manual mode. /// -/// Note: We noticed with the integration tests that the [`tokio_postgres`] crate (PostgreSQL) is able to work in an async environment -/// with a new connection per query without no problem, but the [`tiberius`] crate (MSSQL) suffers a lot when it has continuous -/// statements with multiple queries, like and insert followed by a find by id to check if the insert query has done its -/// job done. -pub async fn init_connections_cache() { - for datasource in DATASOURCES.iter() { - match DatabaseConnection::new(datasource).await { - Ok(conn) => { - CACHED_DATABASE_CONN.lock().await.insert(&datasource.name, conn); - } - Err(e) => { - panic!("Error opening database connection for {}: {}", datasource.name, e); - } +/// Returns: +/// - `Ok(Some(()))` => config loaded +/// - `Ok(None)` => config not found +/// - `Err` => parsing or IO error +pub fn try_init_config() -> Result, Box> { + let Some(path) = find_canyon_config_file()? else { + return Ok(None); // Not an error! + }; + + let content = fs::read_to_string(&path)?; + let config: CanyonSqlConfig = toml::from_str(&content)?; + + CONFIG_FILE_PATH.set(path).ok(); + CONFIG.set(config).ok(); + + let datasources = CONFIG + .get() + .map(|cfg| cfg.canyon_sql.datasources.clone()) + .unwrap_or_default(); + + DATASOURCES.set(datasources).ok(); + + Ok(Some(())) +} + +/// Required in macro mode only: forcefully load or panic +pub fn force_init_config() { + match try_init_config() { + Ok(Some(())) => {} + Ok(None) => panic!("Canyon config file not found but required in macro mode."), + Err(e) => panic!("Failed to load Canyon config: {}", e), + } +} + +/// Public accessor for datasources, safe even if uninitialized +pub fn get_datasources() -> &'static [DatasourceConfig] { + DATASOURCES.get().map(Vec::as_slice).unwrap_or_default() +} + +pub async fn init_connections_cache() -> Result<(), Box> { + try_init_config()?; + + let datasources = get_datasources(); + if datasources.is_empty() { + return Err("No datasources found for connection pool".into()); + } + + let mut cache = HashMap::new(); + for ds in datasources { + let conn = DatabaseConnection::new(ds).await?; + let name: &'static str = Box::leak(ds.name.clone().into_boxed_str()); + let conn_arc = Arc::new(Mutex::new(conn)); + + if cache.is_empty() { + DEFAULT_CONNECTION.set(conn_arc.clone()).ok(); // Store direct ref } + + cache.insert(name, conn_arc); } + + CACHED_DATABASE_CONN.set(cache).ok(); + Ok(()) } -// TODO: doc (main way for the user to obtain a db connection given a datasource identifier) -pub async fn get_database_connection_by_ds( - datasource_name: Option<&str>, -) -> Result> { - let ds = find_datasource_by_name_or_try_default(datasource_name)?; - DatabaseConnection::new(ds).await +/// Borrow a connection for read-only use (if immutable suffices) +pub async fn get_cached_connection( + name: &str, +) -> Result, DatasourceNotFound> { + if name.is_empty() { + let default = DEFAULT_CONNECTION + .get() + .ok_or_else(|| DatasourceNotFound::from(None))?; + return Ok(default.lock().await); + } + + let cache = CACHED_DATABASE_CONN + .get() + .expect("Connection cache not initialized"); + + let conn = cache + .get(name) + .ok_or_else(|| DatasourceNotFound::from(Some(name)))?; + + Ok(conn.lock().await) } +/// Mutable access — same as above (just aliasing for clarity) +pub async fn get_mut_cached_connection( + name: &str, +) -> Result, DatasourceNotFound> { + get_cached_connection(name).await +} pub fn find_datasource_by_name_or_try_default( - datasource_name: Option<&str>, // TODO: with the new inputs, we don't want anymore this as Option + name: &str, ) -> Result<&DatasourceConfig, DatasourceNotFound> { - let datasource_name = datasource_name.filter(|&ds_name| !ds_name.is_empty()); - - datasource_name - .map_or_else( - || DATASOURCES.first(), - |ds_name| DATASOURCES.iter().find(|ds| ds.name.eq(ds_name)), - ) - .ok_or_else(|| DatasourceNotFound::from(datasource_name)) + let configs = DATASOURCES + .get() + .expect("Datasources cache not initialized"); + + if name.is_empty() { + return configs + .first() + .ok_or_else(|| DatasourceNotFound::from(None)); + } + + configs + .iter() + .find(|ds| ds.name == name) + .ok_or_else(|| DatasourceNotFound::from(Some(name))) } diff --git a/canyon_macros/src/canyon_macro.rs b/canyon_macros/src/canyon_macro.rs index dbf0a822..7ce08e27 100644 --- a/canyon_macros/src/canyon_macro.rs +++ b/canyon_macros/src/canyon_macro.rs @@ -7,12 +7,12 @@ use canyon_migrations::{CM_QUERIES_TO_EXECUTE, QUERIES_TO_EXECUTE}; use proc_macro2::TokenStream; use quote::quote; - - pub fn main_with_queries() -> TokenStream { // TODO: migrations on main instead of main_with_queries CANYON_TOKIO_RUNTIME.block_on(async { - canyon_core::connection::init_connections_cache().await; // TODO: isn't this cache always initialized anyway? try to remove it + canyon_core::connection::init_connections_cache() + .await + .expect("Error initializing the connections POOL"); Migrations::migrate().await; }); diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index a7e19b20..83836c9e 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -36,12 +36,15 @@ use canyon_entities::{ #[proc_macro_attribute] pub fn main(_meta: CompilerTokenStream, input: CompilerTokenStream) -> CompilerTokenStream { let func = parse_macro_input!(input as FunctionParser); - - if func.sig.ident != "main" { // Ensure the function is literally named "main" + + if func.sig.ident != "main" { + // Ensure the function is literally named "main" return Error::new( func.sig.ident.span(), "The #[canyon::main] macro can only be applied to `fn main()`", - ).to_compile_error().into(); + ) + .to_compile_error() + .into(); } let vis = func.sig; @@ -52,15 +55,18 @@ pub fn main(_meta: CompilerTokenStream, input: CompilerTokenStream) -> CompilerT #[allow(unused_mut, unused_assignments)] let mut migrations_tokens = quote! {}; #[cfg(feature = "migrations")] - { migrations_tokens = main_with_queries(); } - + { + migrations_tokens = main_with_queries(); + } + quote! { // The final code wired in main() #(#attrs)* #vis #sign { canyon_sql::runtime::CANYON_TOKIO_RUNTIME .handle() .block_on( async { - canyon_sql::runtime::init_connections_cache().await; + canyon_sql::runtime::init_connections_cache().await + .expect("Error initializing the connections POOL"); #migrations_tokens #(#body)* } @@ -94,7 +100,8 @@ pub fn canyon_tokio_test( canyon_sql::runtime::CANYON_TOKIO_RUNTIME .handle() .block_on( async { - canyon_sql::runtime::init_connections_cache().await; + canyon_sql::runtime::init_connections_cache().await + .expect("Error initializing the connections POOL"); #(#body)* }); } diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index 18fe19ad..d7586a9f 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -178,7 +178,7 @@ fn generate_find_by_reverse_foreign_key_tokens( ))?; }; - let stmt = quote!{&format!( + let stmt = quote! {&format!( "SELECT * FROM {} WHERE {} = $1", #table_schema_data, format!("\"{}\"", #f_ident).as_str() diff --git a/canyon_macros/src/utils/function_parser.rs b/canyon_macros/src/utils/function_parser.rs index 819d84f7..81266d4d 100644 --- a/canyon_macros/src/utils/function_parser.rs +++ b/canyon_macros/src/utils/function_parser.rs @@ -1,4 +1,7 @@ -use syn::{parse::{Parse, ParseBuffer}, Attribute, Block, ItemFn, Signature, Visibility}; +use syn::{ + parse::{Parse, ParseBuffer}, + Attribute, Block, ItemFn, Signature, Visibility, +}; /// Implementation of syn::Parse for the `#[canyon]` proc-macro #[derive(Clone)] diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index 292a795f..7463b508 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -1,13 +1,3 @@ -use canyon_core::{ - column::Column, - connection::{db_connector::DatabaseConnection, DATASOURCES}, - row::{Row, RowOperations}, - rows::CanyonRows, - transaction::Transaction, -}; -use canyon_entities::CANYON_REGISTER_ENTITIES; -use partialdebug::placeholder::PartialDebug; - use crate::{ canyon_crud::DatabaseType, constants, @@ -17,6 +7,16 @@ use crate::{ processor::MigrationsProcessor, }, }; +use canyon_core::connection::get_datasources; +use canyon_core::{ + column::Column, + connection::db_connector::DatabaseConnection, + row::{Row, RowOperations}, + rows::CanyonRows, + transaction::Transaction, +}; +use canyon_entities::CANYON_REGISTER_ENTITIES; +use partialdebug::placeholder::PartialDebug; #[derive(PartialDebug)] pub struct Migrations; @@ -28,7 +28,7 @@ impl Migrations { /// and the database table with the memory of Canyon to perform the /// migrations over the targeted database pub async fn migrate() { - for datasource in DATASOURCES.iter() { + for datasource in get_datasources() { if !datasource.has_migrations_enabled() { continue; } @@ -38,15 +38,14 @@ impl Migrations { ); let mut migrations_processor = MigrationsProcessor::default(); - let mut db_conn = - canyon_core::connection::get_database_connection_by_ds(Some(&datasource.name)) - .await - .unwrap_or_else(|_| { - panic!( - "Unable to get a database connection on the migrations processor for: {:?}", - datasource.name - ) - }); + let mut db_conn = canyon_core::connection::get_cached_connection(&datasource.name) + .await + .unwrap_or_else(|_| { + panic!( + "Unable to get a database connection on the migrations processor for: {:?}", + datasource.name + ) + }); let canyon_entities = CANYON_REGISTER_ENTITIES.lock().unwrap().to_vec(); let canyon_memory = CanyonMemory::remember(datasource, &canyon_entities).await; diff --git a/canyon_migrations/src/migrations/memory.rs b/canyon_migrations/src/migrations/memory.rs index efadbff0..d240c28b 100644 --- a/canyon_migrations/src/migrations/memory.rs +++ b/canyon_migrations/src/migrations/memory.rs @@ -1,5 +1,5 @@ use crate::constants; -use canyon_core::connection::db_connector::DatabaseConnection; +use canyon_core::connection::db_connector::{DatabaseConnection, DbConnection}; use canyon_core::transaction::Transaction; use canyon_crud::{DatabaseType, DatasourceConfig}; use regex::Regex; @@ -64,22 +64,21 @@ impl CanyonMemory { datasource: &DatasourceConfig, canyon_entities: &[CanyonRegisterEntity<'_>], ) -> Self { - let datasource_name = &datasource.name; - let mut db_conn = - canyon_core::connection::get_database_connection_by_ds(Some(datasource_name)) - .await - .unwrap_or_else(|_| { - panic!( - "Unable to get a database connection on the migrations processor for: {:?}", - datasource_name - ) - }); + let mut db_conn = canyon_core::connection::get_cached_connection(&datasource.name) + .await + .unwrap_or_else(|_| { + panic!( + "Unable to get a database connection on Canyon Memory: {:?}", + datasource.name + ) + }); // Creates the memory table if not exists Self::create_memory(&datasource.name, &mut db_conn, &datasource.get_db_type()).await; // Retrieve the last status data from the `canyon_memory` table - let res = Self::query_rows("SELECT * FROM canyon_memory", [], &mut db_conn) + let res = db_conn + .query_rows("SELECT * FROM canyon_memory", &[]) .await .expect("Error querying Canyon Memory"); diff --git a/canyon_migrations/src/migrations/processor.rs b/canyon_migrations/src/migrations/processor.rs index 1094c7fd..2aaedf4f 100644 --- a/canyon_migrations/src/migrations/processor.rs +++ b/canyon_migrations/src/migrations/processor.rs @@ -1,5 +1,9 @@ //! File that contains all the datatypes and logic to perform the migrations //! over a target database +use crate::canyon_crud::DatasourceConfig; +use crate::constants::regex_patterns; +use crate::save_migrations_query_to_execute; +use canyon_core::connection::db_connector::DbConnection; use canyon_core::transaction::Transaction; use canyon_crud::DatabaseType; use regex::Regex; @@ -8,10 +12,6 @@ use std::fmt::Debug; use std::future::Future; use std::ops::Not; -use crate::canyon_crud::DatasourceConfig; -use crate::constants::regex_patterns; -use crate::save_migrations_query_to_execute; - use super::information_schema::{ColumnMetadata, TableMetadata}; use super::memory::CanyonMemory; #[cfg(feature = "postgres")] @@ -582,21 +582,18 @@ impl MigrationsProcessor { /// Make the detected migrations for the next Canyon-SQL run pub async fn from_query_register(queries_to_execute: &HashMap<&str, Vec<&str>>) { for datasource in queries_to_execute.iter() { - for query_to_execute in datasource.1 { - let datasource_name = datasource.0; - - let db_conn = - canyon_core::connection::get_database_connection_by_ds(Some(datasource_name)) - .await - .unwrap_or_else(|_| { - panic!( - "Unable to get a database connection on the migrations processor for: {:?}", + let datasource_name = datasource.0; + let db_conn = canyon_core::connection::get_cached_connection(datasource_name) + .await + .unwrap_or_else(|_| { + panic!( + "Unable to get a database connection on Canyon Memory: {:?}", datasource_name ) - }); - - let res = Self::query_rows(query_to_execute, [], db_conn).await; + }); + for query_to_execute in datasource.1 { + let res = db_conn.query_rows(query_to_execute, &[]).await; match res { Ok(_) => println!( "\t[OK] - {:?} - Query: {:?}", diff --git a/src/lib.rs b/src/lib.rs index 783eb71e..6b0362b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,8 @@ pub mod macros { pub mod connection { pub use canyon_core::connection::database_type::DatabaseType; pub use canyon_core::connection::db_connector::DatabaseConnection; - pub use canyon_core::connection::get_database_connection_by_ds; + pub use canyon_core::connection::find_datasource_by_name_or_try_default; + pub use canyon_core::connection::get_cached_connection; } pub mod core { diff --git a/tests/migrations/mod.rs b/tests/migrations/mod.rs index 5ff741f1..ad67304c 100644 --- a/tests/migrations/mod.rs +++ b/tests/migrations/mod.rs @@ -1,5 +1,8 @@ #![allow(unused_imports)] + use crate::constants; +use canyon_sql::connection::{find_datasource_by_name_or_try_default, get_cached_connection}; +use canyon_sql::core::DbConnection; /// Integration tests for the migrations feature of `Canyon-SQL` use canyon_sql::core::Transaction; #[cfg(feature = "migrations")] @@ -9,12 +12,21 @@ use canyon_sql::migrations::handler::Migrations; #[cfg(all(feature = "postgres", feature = "migrations"))] #[canyon_sql::macros::canyon_tokio_test] fn test_migrations_postgresql_status_query() { - let conn_res = - canyon_sql::connection::get_database_connection_by_ds(Some(constants::PSQL_DS)).await; - assert!(conn_res.is_ok()); + let ds = find_datasource_by_name_or_try_default(constants::PSQL_DS); + assert!(ds.is_ok()); + let ds = ds.unwrap(); + let ds_name = &ds.name; + + let db_conn = get_cached_connection(ds_name).await.unwrap_or_else(|_| { + panic!( + "Unable to get a database connection on Canyon Memory: {:?}", + ds_name + ) + }); - let db_conn = &mut conn_res.unwrap(); - let results = Migrations::query_rows(constants::FETCH_PUBLIC_SCHEMA, [], db_conn).await; + let results = db_conn + .query_rows(constants::FETCH_PUBLIC_SCHEMA, &[]) + .await; assert!(results.is_ok()); let res = results.unwrap(); From 2890d1a0fe8c0d0a50279b208ecfadb5817cdd65 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 2 May 2025 14:29:48 +0200 Subject: [PATCH 098/155] refactor: getting rid out of lazy_static! --- .github/workflows/code-quality.yml | 2 +- Cargo.toml | 1 - bash_aliases.sh | 7 +- canyon_core/Cargo.toml | 1 - canyon_core/src/connection/conn_errors.rs | 2 +- canyon_core/src/connection/db_connector.rs | 2 +- canyon_core/src/connection/mod.rs | 15 +- canyon_core/src/lib.rs | 2 - canyon_core/src/query_parameters.rs | 4 +- canyon_core/src/rows.rs | 47 +----- .../src/query_elements/query_builder.rs | 8 +- canyon_entities/src/entity.rs | 2 +- canyon_macros/src/canyon_macro.rs | 36 ++-- canyon_macros/src/lib.rs | 4 +- canyon_migrations/src/constants.rs | 95 ----------- canyon_migrations/src/lib.rs | 28 ++- canyon_migrations/src/migrations/memory.rs | 18 +- canyon_migrations/src/migrations/processor.rs | 159 ++++++++++++++---- src/lib.rs | 2 +- 19 files changed, 198 insertions(+), 237 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 3d1908fb..b9c501b8 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -56,7 +56,7 @@ jobs: strategy: fail-fast: false matrix: - crate: [canyon_connection, canyon_crud, canyon_macros, canyon_migrations] + crate: [canyon_core, canyon_crud, canyon_macros, canyon_entities, canyon_migrations] steps: - uses: actions/checkout@v3 diff --git a/Cargo.toml b/Cargo.toml index 09cdcfc3..aedd1420 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,6 @@ serde = { version = "1.0.138", features = ["derive"] } futures = "0.3.25" async-std = "1.12.0" -lazy_static = "1.4.0" toml = "0.7.3" walkdir = "2.3.3" regex = "1.9.3" diff --git a/bash_aliases.sh b/bash_aliases.sh index 64b40415..348b039f 100755 --- a/bash_aliases.sh +++ b/bash_aliases.sh @@ -14,6 +14,10 @@ alias DockerDown='docker-compose -f ./docker/docker-compose.yml down' # Cleans the generated cache folder for the postgres in the docker alias CleanPostgres='rm -rf ./docker/postgres-data' +# Code Quality +alias Clippy='cargo clippy --all-targets --all-features --workspace -- -D warnings' +alias Fmt='cargo fmt --all -- --check' + # Build the project for Windows targets alias BuildCanyonWin='cargo build --all-features --target=x86_64-pc-windows-msvc' alias BuildCanyonWinFull='cargo clean && cargo build --all-features --target=x86_64-pc-windows-msvc' @@ -37,10 +41,11 @@ alias IntegrationTestsLinux='cargo test --all-features --no-fail-fast -p tests - alias ITIncludeIgnoredLinux='cargo test --all-features --no-fail-fast -p tests --target=x86_64-unknown-linux-gnu -- --show-output --test-threads=1 --nocapture --test-threads=1 --include-ignored' alias SqlServerInitializationLinux='cargo test initialize_sql_server_docker_instance -p tests --all-features --no-fail-fast --target=x86_64-unknown-linux-gnu -- --show-output --test-threads=1 --nocapture --include-ignored' - +----- # Publish Canyon-SQL to the registry with its dependencies alias PublishCanyon='cargo publish -p canyon_connection && cargo publish -p canyon_crud && cargo publish -p canyon_migrations && cargo publish -p canyon_macros && cargo publish -p canyon_sql_root' +----- # Collects the code coverage for the project (tests must run before this) alias CcEnvVars='export CARGO_INCREMENTAL=0 export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" diff --git a/canyon_core/Cargo.toml b/canyon_core/Cargo.toml index 1c15fb29..ca9b77be 100644 --- a/canyon_core/Cargo.toml +++ b/canyon_core/Cargo.toml @@ -23,7 +23,6 @@ tokio = { workspace = true } tokio-util = { workspace = true } futures = { workspace = true } -lazy_static = { workspace = true } toml = { workspace = true } serde = { workspace = true } walkdir = { workspace = true } diff --git a/canyon_core/src/connection/conn_errors.rs b/canyon_core/src/connection/conn_errors.rs index 18da4c80..64f80cf6 100644 --- a/canyon_core/src/connection/conn_errors.rs +++ b/canyon_core/src/connection/conn_errors.rs @@ -1,6 +1,6 @@ //! Defines the Canyon-SQL custom connection error types -/// Raised when a [`crate::datasources::DatasourceConfig`] isn't found given a user input +/// Raised when a [`crate::connection::datasources::DatasourceConfig`] isn't found given a user input #[derive(Debug, Clone)] pub struct DatasourceNotFound { pub datasource_name: String, diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index e3f6723c..0557aaa1 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -187,7 +187,7 @@ impl DbConnection for &str { } fn get_database_type(&self) -> Result> { - Ok(find_datasource_by_name_or_try_default(*self)?.get_db_type()) + Ok(find_datasource_by_name_or_try_default(self)?.get_db_type()) } } diff --git a/canyon_core/src/connection/mod.rs b/canyon_core/src/connection/mod.rs index 9a037df7..fa8565cd 100644 --- a/canyon_core/src/connection/mod.rs +++ b/canyon_core/src/connection/mod.rs @@ -10,7 +10,6 @@ pub extern crate tiberius; pub extern crate mysql_async; pub extern crate futures; -pub extern crate lazy_static; pub extern crate tokio; pub extern crate tokio_util; @@ -23,11 +22,11 @@ pub mod db_connector; use conn_errors::DatasourceNotFound; use datasources::{CanyonSqlConfig, DatasourceConfig}; use db_connector::DatabaseConnection; -use lazy_static::lazy_static; use std::collections::HashMap; use std::path::PathBuf; use std::sync::{Arc, OnceLock}; use std::{error::Error, fs}; +use tokio::runtime::Runtime; use tokio::sync::Mutex; use walkdir::WalkDir; @@ -37,11 +36,15 @@ use walkdir::WalkDir; // TODO: Crud Operations should be split into two different derives, splitting the automagic from the _with ones -lazy_static! { - pub static ref CANYON_TOKIO_RUNTIME: tokio::runtime::Runtime = - tokio::runtime::Runtime::new() // TODO Make the config with the builder - .expect("Failed initializing the Canyon-SQL Tokio Runtime"); +// Use OnceLock for the Tokio runtime +static CANYON_TOKIO_RUNTIME: OnceLock = OnceLock::new(); + +// Function to get the runtime (lazy initialization) +pub fn get_canyon_tokio_runtime() -> &'static Runtime { + CANYON_TOKIO_RUNTIME + .get_or_init(|| Runtime::new().expect("Failed initializing the Canyon-SQL Tokio Runtime")) } + static CONFIG_FILE_PATH: OnceLock = OnceLock::new(); static CONFIG: OnceLock = OnceLock::new(); static DATASOURCES: OnceLock> = OnceLock::new(); diff --git a/canyon_core/src/lib.rs b/canyon_core/src/lib.rs index 8b9af66c..f4ffda29 100644 --- a/canyon_core/src/lib.rs +++ b/canyon_core/src/lib.rs @@ -10,8 +10,6 @@ pub extern crate tiberius; pub extern crate mysql_async; extern crate core; -pub extern crate lazy_static; -// extern crate cfg_if; pub mod column; pub mod connection; diff --git a/canyon_core/src/query_parameters.rs b/canyon_core/src/query_parameters.rs index d3d14ff9..6b624126 100644 --- a/canyon_core/src/query_parameters.rs +++ b/canyon_core/src/query_parameters.rs @@ -19,11 +19,11 @@ pub trait QueryParameter<'a>: std::fmt::Debug + Send + Sync { fn as_mysql_param(&self) -> &dyn ToValue; } -/// The implementation of the [`canyon_core::connection::tiberius`] [`IntoSql`] for the +/// The implementation of the [`crate::connection::tiberius`] [`IntoSql`] for the /// query parameters. /// /// This implementation is necessary because of the generic amplitude -/// of the arguments of the [`Transaction::query`], that should work with +/// of the arguments of the [`crate::transaction::Transaction::query`], that should work with /// a collection of [`QueryParameter<'a>`], in order to allow a workflow /// that is not dependent of the specific type of the argument that holds /// the query parameters of the database connectors diff --git a/canyon_core/src/rows.rs b/canyon_core/src/rows.rs index 738dfbe5..c8947dab 100644 --- a/canyon_core/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -118,7 +118,7 @@ impl CanyonRows { /// Returns the entity at the given index for the returned rows /// - /// This is just a wrapper get operation over the [Vec::get] operation + /// This is just a wrapper get operation over the [Vec] get operation pub fn get_row_at(&self, index: usize) -> Option<&dyn Row> { match self { #[cfg(feature = "postgres")] @@ -141,51 +141,6 @@ impl CanyonRows { } } - // pub fn get_column_at_row<'a, C: FromSql<'a, C>>( - // &'a self, - // column_name: &str, - // index: usize, - // ) -> Result> { - // let row_extraction_failure = || { - // format!( - // "{:?} - Failure getting the row: {} at index: {}", - // self, column_name, index - // ) - // }; - // - // match self { - // #[cfg(feature = "postgres")] - // Self::Postgres(v) => Ok(v - // .get(index) - // .ok_or_else(row_extraction_failure)? - // .get::<&str, C>(column_name)), - // #[cfg(feature = "mssql")] - // Self::Tiberius(ref v) => v - // .get(index) - // .ok_or_else(row_extraction_failure)? - // .get::(column_name) - // .ok_or_else(|| { - // format!( - // "{:?} - Failure getting the row: {} at index: {}", - // self, column_name, index - // ) - // .into() - // }), - // #[cfg(feature = "mysql")] - // Self::MySQL(ref v) => v - // .get(index) - // .ok_or_else(row_extraction_failure)? - // .get::(0) - // .ok_or_else(|| { - // format!( - // "{:?} - Failure getting the row: {} at index: {}", - // self, column_name, index - // ) - // .into() - // }), - // } - // } - /// Returns the number of elements present on the wrapped collection pub fn len(&self) -> usize { match self { diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index ef93767a..73781f6b 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -269,7 +269,7 @@ impl<'a, I: DbConnection + ?Sized, R: RowMapper> SelectQueryBuilder<'a, I, R> { } /// Adds a *LEFT JOIN* SQL statement to the underlying - /// [`Query`] held by the [`QueryBuilder`], where: + /// `Sql Statement` held by the [`QueryBuilder`], where: /// /// * `join_table` - The table target of the join operation /// * `col1` - The left side of the ON operator for the join @@ -284,7 +284,7 @@ impl<'a, I: DbConnection + ?Sized, R: RowMapper> SelectQueryBuilder<'a, I, R> { } /// Adds a *INNER JOIN* SQL statement to the underlying - /// [`Query`] held by the [`QueryBuilder`], where: + /// `Sql Statement` held by the [`QueryBuilder`], where: /// /// * `join_table` - The table target of the join operation /// * `col1` - The left side of the ON operator for the join @@ -299,7 +299,7 @@ impl<'a, I: DbConnection + ?Sized, R: RowMapper> SelectQueryBuilder<'a, I, R> { } /// Adds a *RIGHT JOIN* SQL statement to the underlying - /// [`Query`] held by the [`QueryBuilder`], where: + /// `Sql Statement` held by the [`QueryBuilder`], where: /// /// * `join_table` - The table target of the join operation /// * `col1` - The left side of the ON operator for the join @@ -314,7 +314,7 @@ impl<'a, I: DbConnection + ?Sized, R: RowMapper> SelectQueryBuilder<'a, I, R> { } /// Adds a *FULL JOIN* SQL statement to the underlying - /// [`Query`] held by the [`QueryBuilder`], where: + /// `Sql Statement` held by the [`QueryBuilder`], where: /// /// * `join_table` - The table target of the join operation /// * `col1` - The left side of the ON operator for the join diff --git a/canyon_entities/src/entity.rs b/canyon_entities/src/entity.rs index fa8834a5..bb1e9297 100644 --- a/canyon_entities/src/entity.rs +++ b/canyon_entities/src/entity.rs @@ -44,7 +44,7 @@ impl CanyonEntity { /// which this enum is related to. /// /// Makes a variant `#field_name(#ty)` where `#ty` it's a trait object - /// of type [`canyon_crud::bounds::QueryParameter`] + /// of type `canyon_core::QueryParameter` pub fn get_fields_as_enum_variants_with_value(&self) -> Vec { self.fields .iter() diff --git a/canyon_macros/src/canyon_macro.rs b/canyon_macros/src/canyon_macro.rs index 7ce08e27..1abcc05f 100644 --- a/canyon_macros/src/canyon_macro.rs +++ b/canyon_macros/src/canyon_macro.rs @@ -1,7 +1,7 @@ //! Provides helpers to build the `#[canyon_macros::canyon]` procedural like attribute macro #![cfg(feature = "migrations")] -use canyon_core::connection::CANYON_TOKIO_RUNTIME; +use canyon_core::connection::get_canyon_tokio_runtime; use canyon_migrations::migrations::handler::Migrations; use canyon_migrations::{CM_QUERIES_TO_EXECUTE, QUERIES_TO_EXECUTE}; use proc_macro2::TokenStream; @@ -9,7 +9,7 @@ use quote::quote; pub fn main_with_queries() -> TokenStream { // TODO: migrations on main instead of main_with_queries - CANYON_TOKIO_RUNTIME.block_on(async { + get_canyon_tokio_runtime().block_on(async { canyon_core::connection::init_connections_cache() .await .expect("Error initializing the connections POOL"); @@ -29,15 +29,29 @@ pub fn main_with_queries() -> TokenStream { /// Creates a TokenScream that is used to load the data generated at compile-time /// by the `CanyonManaged` macros again on the queries register fn wire_queries_to_execute(canyon_manager_tokens: &mut Vec) { - let cm_data = CM_QUERIES_TO_EXECUTE.lock().unwrap(); - let data = QUERIES_TO_EXECUTE.lock().unwrap(); + let data_to_wire = if let Some(mutex) = QUERIES_TO_EXECUTE.get() { + let queries = mutex.lock().expect("QUERIES_TO_EXECUTE poisoned"); + queries + .iter() + .map(|(key, value)| { + quote! { hm.insert(#key, vec![#(#value),*]); } + }) + .collect::>() + } else { + vec![] + }; - let cm_data_to_wire = cm_data.iter().map(|(key, value)| { - quote! { cm_hm.insert(#key, vec![#(#value),*]); } - }); - let data_to_wire = data.iter().map(|(key, value)| { - quote! { hm.insert(#key, vec![#(#value),*]); } - }); + let cm_data_to_wire = if let Some(mutex) = CM_QUERIES_TO_EXECUTE.get() { + let cm_queries = mutex.lock().expect("CM_QUERIES_TO_EXECUTE poisoned"); + cm_queries + .iter() + .map(|(key, value)| { + quote! { cm_hm.insert(#key, vec![#(#value),*]); } + }) + .collect::>() + } else { + vec![] + }; let tokens = quote! { use std::collections::HashMap; @@ -53,5 +67,5 @@ fn wire_queries_to_execute(canyon_manager_tokens: &mut Vec) { MigrationsProcessor::from_query_register(&hm).await; }; - canyon_manager_tokens.push(tokens) + canyon_manager_tokens.push(tokens); } diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index 83836c9e..ca067529 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -62,7 +62,7 @@ pub fn main(_meta: CompilerTokenStream, input: CompilerTokenStream) -> CompilerT quote! { // The final code wired in main() #(#attrs)* #vis #sign { - canyon_sql::runtime::CANYON_TOKIO_RUNTIME + canyon_sql::runtime::get_canyon_tokio_runtime() .handle() .block_on( async { canyon_sql::runtime::init_connections_cache().await @@ -97,7 +97,7 @@ pub fn canyon_tokio_test( #[test] #(#attrs)* #vis #sign { - canyon_sql::runtime::CANYON_TOKIO_RUNTIME + canyon_sql::runtime::get_canyon_tokio_runtime() .handle() .block_on( async { canyon_sql::runtime::init_connections_cache().await diff --git a/canyon_migrations/src/constants.rs b/canyon_migrations/src/constants.rs index 103b68d5..7674efe5 100644 --- a/canyon_migrations/src/constants.rs +++ b/canyon_migrations/src/constants.rs @@ -168,98 +168,3 @@ pub mod sqlserver_type { pub const TIME: &str = "TIME"; pub const DATETIME: &str = "DATETIME2"; } - -pub mod mocked_data { - use crate::migrations::information_schema::{ColumnMetadata, TableMetadata}; - use canyon_core::lazy_static::lazy_static; - - lazy_static! { - pub static ref TABLE_METADATA_LEAGUE_EX: TableMetadata = TableMetadata { - table_name: "league".to_string(), - columns: vec![ - ColumnMetadata { - column_name: "id".to_owned(), - datatype: "int".to_owned(), - character_maximum_length: None, - is_nullable: false, - column_default: None, - foreign_key_info: None, - foreign_key_name: None, - primary_key_info: Some("PK__league__3213E83FBDA92571".to_owned()), - primary_key_name: Some("PK__league__3213E83FBDA92571".to_owned()), - is_identity: false, - identity_generation: None - }, - ColumnMetadata { - column_name: "ext_id".to_owned(), - datatype: "bigint".to_owned(), - character_maximum_length: None, - is_nullable: false, - column_default: None, - foreign_key_info: None, - foreign_key_name: None, - primary_key_info: None, - primary_key_name: None, - is_identity: false, - identity_generation: None - }, - ColumnMetadata { - column_name: "slug".to_owned(), - datatype: "nvarchar".to_owned(), - character_maximum_length: None, - is_nullable: false, - column_default: None, - foreign_key_info: None, - foreign_key_name: None, - primary_key_info: None, - primary_key_name: None, - is_identity: false, - identity_generation: None - }, - ColumnMetadata { - column_name: "name".to_owned(), - datatype: "nvarchar".to_owned(), - character_maximum_length: None, - is_nullable: false, - column_default: None, - foreign_key_info: None, - foreign_key_name: None, - primary_key_info: None, - primary_key_name: None, - is_identity: false, - identity_generation: None - }, - ColumnMetadata { - column_name: "region".to_owned(), - datatype: "nvarchar".to_owned(), - character_maximum_length: None, - is_nullable: false, - column_default: None, - foreign_key_info: None, - foreign_key_name: None, - primary_key_info: None, - primary_key_name: None, - is_identity: false, - identity_generation: None - }, - ColumnMetadata { - column_name: "image_url".to_owned(), - datatype: "nvarchar".to_owned(), - character_maximum_length: None, - is_nullable: false, - column_default: None, - foreign_key_info: None, - foreign_key_name: None, - primary_key_info: None, - primary_key_name: None, - is_identity: false, - identity_generation: None - } - ] - }; - pub static ref NON_MATCHING_TABLE_METADATA: TableMetadata = TableMetadata { - table_name: "random_name_to_assert_false".to_string(), - columns: vec![] - }; - } -} diff --git a/canyon_migrations/src/lib.rs b/canyon_migrations/src/lib.rs index 79988789..757597cc 100644 --- a/canyon_migrations/src/lib.rs +++ b/canyon_migrations/src/lib.rs @@ -16,29 +16,21 @@ extern crate canyon_entities; mod constants; -use canyon_core::lazy_static::lazy_static; +use std::sync::OnceLock; use std::{collections::HashMap, sync::Mutex}; -lazy_static! { - pub static ref QUERIES_TO_EXECUTE: Mutex>> = - Mutex::new(HashMap::new()); - pub static ref CM_QUERIES_TO_EXECUTE: Mutex>> = - Mutex::new(HashMap::new()); -} +pub static QUERIES_TO_EXECUTE: OnceLock>>> = OnceLock::new(); +pub static CM_QUERIES_TO_EXECUTE: OnceLock>>> = OnceLock::new(); /// Stores a newly generated SQL statement from the migrations into the register pub fn save_migrations_query_to_execute(stmt: String, ds_name: &str) { - if QUERIES_TO_EXECUTE.lock().unwrap().contains_key(ds_name) { - QUERIES_TO_EXECUTE - .lock() - .unwrap() - .get_mut(ds_name) - .unwrap() - .push(stmt); + // Access the QUERIES_TO_EXECUTE hash map and lock it for safe access + let queries_to_execute = QUERIES_TO_EXECUTE.get_or_init(|| Mutex::new(HashMap::new())); + let mut queries = queries_to_execute.lock().unwrap(); + + if queries.contains_key(ds_name) { + queries.get_mut(ds_name).unwrap().push(stmt); } else { - QUERIES_TO_EXECUTE - .lock() - .unwrap() - .insert(ds_name.to_owned(), vec![stmt]); + queries.insert(ds_name.to_owned(), vec![stmt]); } } diff --git a/canyon_migrations/src/migrations/memory.rs b/canyon_migrations/src/migrations/memory.rs index d240c28b..27dac620 100644 --- a/canyon_migrations/src/migrations/memory.rs +++ b/canyon_migrations/src/migrations/memory.rs @@ -5,6 +5,7 @@ use canyon_crud::{DatabaseType, DatasourceConfig}; use regex::Regex; use std::collections::HashMap; use std::fs; +use std::sync::Mutex; use walkdir::WalkDir; use canyon_entities::register_types::CanyonRegisterEntity; @@ -275,19 +276,10 @@ impl CanyonMemory { fn save_canyon_memory_query(stmt: String, ds_name: &str) { use crate::CM_QUERIES_TO_EXECUTE; - if CM_QUERIES_TO_EXECUTE.lock().unwrap().contains_key(ds_name) { - CM_QUERIES_TO_EXECUTE - .lock() - .unwrap() - .get_mut(ds_name) - .unwrap() - .push(stmt); - } else { - CM_QUERIES_TO_EXECUTE - .lock() - .unwrap() - .insert(ds_name.to_owned(), vec![stmt]); - } + let mutex = CM_QUERIES_TO_EXECUTE.get_or_init(|| Mutex::new(HashMap::new())); + let mut queries = mutex.lock().expect("Mutex poisoned"); + + queries.entry(ds_name.to_owned()).or_default().push(stmt); } /// Represents a single row from the `canyon_memory` table diff --git a/canyon_migrations/src/migrations/processor.rs b/canyon_migrations/src/migrations/processor.rs index 2aaedf4f..ef1f2cd3 100644 --- a/canyon_migrations/src/migrations/processor.rs +++ b/canyon_migrations/src/migrations/processor.rs @@ -733,36 +733,6 @@ impl MigrationsHelper { } } -#[cfg(test)] -mod migrations_helper_tests { - use super::*; - use crate::constants; - - const MOCKED_ENTITY_NAME: &str = "league"; - - #[test] - fn test_entity_already_on_database() { - let parse_result_empty_db_tables = - MigrationsHelper::entity_already_on_database(MOCKED_ENTITY_NAME, &[]); - // Always should be false - assert!(!parse_result_empty_db_tables); - - // Rust has a League entity. Database has a `league` entity. Case should be normalized - // and a match must raise - let mocked_league_entity_on_database = MigrationsHelper::entity_already_on_database( - MOCKED_ENTITY_NAME, - &[&constants::mocked_data::TABLE_METADATA_LEAGUE_EX], - ); - assert!(mocked_league_entity_on_database); - - let mocked_league_entity_on_database = MigrationsHelper::entity_already_on_database( - MOCKED_ENTITY_NAME, - &[&constants::mocked_data::NON_MATCHING_TABLE_METADATA], - ); - assert!(!mocked_league_entity_on_database) - } -} - trait DatabaseOperation: Debug { fn generate_sql(&self, datasource: &DatasourceConfig) -> impl Future; } @@ -1057,3 +1027,132 @@ impl DatabaseOperation for SequenceOperation { save_migrations_query_to_execute(stmt, &datasource.name); } } + +#[cfg(test)] +mod migrations_helper_tests { + use super::*; + const MOCKED_ENTITY_NAME: &str = "league"; + + #[test] + fn test_entity_already_on_database() { + mocked_data::init_mocked_data(); + + let parse_result_empty_db_tables = + MigrationsHelper::entity_already_on_database(MOCKED_ENTITY_NAME, &[]); + // Always should be false + assert!(!parse_result_empty_db_tables); + + // Rust has a League entity. Database has a `league` entity. Case should be normalized + // and a match must raise + let mocked_league_entity_on_database = MigrationsHelper::entity_already_on_database( + MOCKED_ENTITY_NAME, + &[mocked_data::TABLE_METADATA_LEAGUE_EX.get().unwrap()], + ); + assert!(mocked_league_entity_on_database); + + let mocked_league_entity_on_database = MigrationsHelper::entity_already_on_database( + MOCKED_ENTITY_NAME, + &[mocked_data::NON_MATCHING_TABLE_METADATA.get().unwrap()], + ); + assert!(!mocked_league_entity_on_database) + } + + pub mod mocked_data { + use crate::migrations::information_schema::{ColumnMetadata, TableMetadata}; + use std::sync::OnceLock; + + pub static TABLE_METADATA_LEAGUE_EX: OnceLock = OnceLock::new(); + pub static NON_MATCHING_TABLE_METADATA: OnceLock = OnceLock::new(); + + pub fn init_mocked_data() { + TABLE_METADATA_LEAGUE_EX.get_or_init(|| TableMetadata { + table_name: "league".to_string(), + columns: vec![ + ColumnMetadata { + column_name: "id".to_owned(), + datatype: "int".to_owned(), + character_maximum_length: None, + is_nullable: false, + column_default: None, + foreign_key_info: None, + foreign_key_name: None, + primary_key_info: Some("PK__league__3213E83FBDA92571".to_owned()), + primary_key_name: Some("PK__league__3213E83FBDA92571".to_owned()), + is_identity: false, + identity_generation: None, + }, + ColumnMetadata { + column_name: "ext_id".to_owned(), + datatype: "bigint".to_owned(), + character_maximum_length: None, + is_nullable: false, + column_default: None, + foreign_key_info: None, + foreign_key_name: None, + primary_key_info: None, + primary_key_name: None, + is_identity: false, + identity_generation: None, + }, + ColumnMetadata { + column_name: "slug".to_owned(), + datatype: "nvarchar".to_owned(), + character_maximum_length: None, + is_nullable: false, + column_default: None, + foreign_key_info: None, + foreign_key_name: None, + primary_key_info: None, + primary_key_name: None, + is_identity: false, + identity_generation: None, + }, + ColumnMetadata { + column_name: "name".to_owned(), + datatype: "nvarchar".to_owned(), + character_maximum_length: None, + is_nullable: false, + column_default: None, + foreign_key_info: None, + foreign_key_name: None, + primary_key_info: None, + primary_key_name: None, + is_identity: false, + identity_generation: None, + }, + ColumnMetadata { + column_name: "region".to_owned(), + datatype: "nvarchar".to_owned(), + character_maximum_length: None, + is_nullable: false, + column_default: None, + foreign_key_info: None, + foreign_key_name: None, + primary_key_info: None, + primary_key_name: None, + is_identity: false, + identity_generation: None, + }, + ColumnMetadata { + column_name: "image_url".to_owned(), + datatype: "nvarchar".to_owned(), + character_maximum_length: None, + is_nullable: false, + column_default: None, + foreign_key_info: None, + foreign_key_name: None, + primary_key_info: None, + primary_key_name: None, + is_identity: false, + identity_generation: None, + }, + ], + }); + + NON_MATCHING_TABLE_METADATA.get_or_init(|| TableMetadata { + table_name: "random_name_to_assert_false".to_string(), + columns: vec![], + }); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 6b0362b1..b7463e78 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,10 +67,10 @@ pub mod db_clients { /// Reexport the needed runtime dependencies pub mod runtime { pub use canyon_core::connection::futures; + pub use canyon_core::connection::get_canyon_tokio_runtime; pub use canyon_core::connection::init_connections_cache; pub use canyon_core::connection::tokio; pub use canyon_core::connection::tokio_util; - pub use canyon_core::connection::CANYON_TOKIO_RUNTIME; } /// Module for reexport the `chrono` crate with the allowed public and available types in Canyon From 4b100a93c33623237d3ece694e6c22a3f7f9cc55 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 2 May 2025 16:29:45 +0200 Subject: [PATCH 099/155] fix(test): tiberius target types on deserialization process that contains a whitespace due to quote! processing --- canyon_macros/src/canyon_mapper_macro.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index 7ed2b1a8..b648ad3d 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -196,8 +196,8 @@ mod mapper_macro_tests { #[test] fn test_regex_extraction_for_the_tiberius_target_types() { - assert_eq!("&str", get_deserializing_type("String").to_string()); - assert_eq!("&str", get_deserializing_type("Option").to_string()); + assert_eq!("& str", get_deserializing_type("String").to_string()); + assert_eq!("& str", get_deserializing_type("Option").to_string()); assert_eq!("i64", get_deserializing_type("i64").to_string()); assert_eq!( From 435f9791d83b44460f750ffaab2469ea834f1f1a Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 2 May 2025 16:51:18 +0200 Subject: [PATCH 100/155] fix(test): tiberius target types on deserialization process that contains a whitespace due to quote! processing --- canyon_macros/src/canyon_mapper_macro.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index b648ad3d..f166565b 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -165,6 +165,15 @@ fn get_deserializing_type(target_type: &str) -> TokenStream { }) } +#[cfg(feature = "mssql")] +fn __get_deserializing_type_str(target_type: &str) -> String { + let tt = get_deserializing_type(target_type); + tt.to_string() + .chars() + .filter(|c| !c.is_whitespace()) + .collect::() +} + #[cfg(feature = "mssql")] use quote::ToTokens; #[cfg(feature = "mssql")] @@ -192,21 +201,21 @@ fn get_field_type_as_string(typ: &Type) -> String { #[cfg(test)] #[cfg(feature = "mssql")] mod mapper_macro_tests { - use crate::canyon_mapper_macro::get_deserializing_type; + use crate::canyon_mapper_macro::__get_deserializing_type_str; #[test] fn test_regex_extraction_for_the_tiberius_target_types() { - assert_eq!("& str", get_deserializing_type("String").to_string()); - assert_eq!("& str", get_deserializing_type("Option").to_string()); - assert_eq!("i64", get_deserializing_type("i64").to_string()); + assert_eq!("&str", __get_deserializing_type_str("String")); + assert_eq!("&str", __get_deserializing_type_str("Option")); + assert_eq!("i64", __get_deserializing_type_str("i64")); assert_eq!( "canyon_sql::date_time::DateTime", - get_deserializing_type("DateTime").to_string() + __get_deserializing_type_str("DateTime") ); assert_eq!( "canyon_sql::date_time::NaiveDateTime", - get_deserializing_type("NaiveDateTime").to_string() + __get_deserializing_type_str("NaiveDateTime") ); } } From 8d6964b5cfd34b48d4fc91f123d5d1474d27b637 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 2 May 2025 17:52:03 +0200 Subject: [PATCH 101/155] perf: By redesigning the public API of Canyon, we noticed an increase of a 8x on running the integration tests. This will likely impact the Canyon users with a massive performance boost --- canyon_core/src/connection/db_connector.rs | 30 +- canyon_core/src/connection/mod.rs | 404 ++++++++++++------ canyon_macros/src/canyon_macro.rs | 2 +- canyon_macros/src/lib.rs | 4 +- canyon_migrations/src/migrations/handler.rs | 14 +- canyon_migrations/src/migrations/memory.rs | 7 +- canyon_migrations/src/migrations/processor.rs | 4 +- src/lib.rs | 5 +- tests/migrations/mod.rs | 8 +- 9 files changed, 321 insertions(+), 157 deletions(-) diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index 0557aaa1..bbbbd701 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -9,7 +9,7 @@ use crate::connection::db_clients::postgresql::PostgreSqlConnection; use crate::connection::db_connector::connection_helpers::{ db_conn_launch_impl, db_conn_query_one_impl, }; -use crate::connection::{find_datasource_by_name_or_try_default, get_cached_connection}; +use crate::connection::Canyon; use crate::mapper::RowMapper; use crate::query_parameters::QueryParameter; use crate::rows::{CanyonRows, FromSqlOwnedValue}; @@ -75,7 +75,7 @@ impl DbConnection for str { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result> { - let conn = get_cached_connection(self).await?; + let conn = Canyon::instance()?.get_connection(self).await?; conn.query_rows(stmt, params).await } @@ -89,7 +89,7 @@ impl DbConnection for str { R: RowMapper, Vec: FromIterator<::Output>, { - let conn = get_cached_connection(self).await?; + let conn = Canyon::instance()?.get_connection(self).await?; conn.query(stmt, params).await } @@ -101,7 +101,7 @@ impl DbConnection for str { where R: RowMapper, { - let conn = get_cached_connection(self).await?; + let conn = Canyon::instance()?.get_connection(self).await?; conn.query_one::(stmt, params).await } @@ -110,7 +110,7 @@ impl DbConnection for str { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result> { - let conn = get_cached_connection(self).await?; + let conn = Canyon::instance()?.get_connection(self).await?; conn.query_one_for(stmt, params).await } @@ -119,12 +119,14 @@ impl DbConnection for str { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result> { - let conn = get_cached_connection(self).await?; + let conn = Canyon::instance()?.get_connection(self).await?; conn.execute(stmt, params).await } fn get_database_type(&self) -> Result> { - Ok(find_datasource_by_name_or_try_default(self)?.get_db_type()) + Ok(Canyon::instance()? + .find_datasource_by_name_or_default(self)? + .get_db_type()) } } @@ -138,7 +140,7 @@ impl DbConnection for &str { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result> { - let conn = get_cached_connection(self).await?; + let conn = Canyon::instance()?.get_connection(self).await?; conn.query_rows(stmt, params).await } @@ -152,7 +154,7 @@ impl DbConnection for &str { R: RowMapper, Vec: FromIterator<::Output>, { - let conn = get_cached_connection(self).await?; + let conn = Canyon::instance()?.get_connection(self).await?; conn.query(stmt, params).await } @@ -164,7 +166,7 @@ impl DbConnection for &str { where R: RowMapper, { - let conn = get_cached_connection(self).await?; + let conn = Canyon::instance()?.get_connection(self).await?; conn.query_one::(stmt, params).await } @@ -173,7 +175,7 @@ impl DbConnection for &str { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result> { - let conn = get_cached_connection(self).await?; + let conn = Canyon::instance()?.get_connection(self).await?; conn.query_one_for(stmt, params).await } @@ -182,12 +184,14 @@ impl DbConnection for &str { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result> { - let conn = get_cached_connection(self).await?; + let conn = Canyon::instance()?.get_connection(self).await?; conn.execute(stmt, params).await } fn get_database_type(&self) -> Result> { - Ok(find_datasource_by_name_or_try_default(self)?.get_db_type()) + Ok(Canyon::instance()? + .find_datasource_by_name_or_default(self)? + .get_db_type()) } } diff --git a/canyon_core/src/connection/mod.rs b/canyon_core/src/connection/mod.rs index fa8565cd..8c3224fd 100644 --- a/canyon_core/src/connection/mod.rs +++ b/canyon_core/src/connection/mod.rs @@ -19,6 +19,7 @@ pub mod datasources; pub mod db_clients; pub mod db_connector; +use crate::connection::datasources::Datasources; use conn_errors::DatasourceNotFound; use datasources::{CanyonSqlConfig, DatasourceConfig}; use db_connector::DatabaseConnection; @@ -30,12 +31,6 @@ use tokio::runtime::Runtime; use tokio::sync::Mutex; use walkdir::WalkDir; -// TODO's: DatabaseConnection and DataSource can implement default, so there's no need to use str and &str -// as defaults anymore, since the can load as the default the first one defined in the config file, or have more -// complex workflows that are deferred to initialization time - -// TODO: Crud Operations should be split into two different derives, splitting the automagic from the _with ones - // Use OnceLock for the Tokio runtime static CANYON_TOKIO_RUNTIME: OnceLock = OnceLock::new(); @@ -45,150 +40,299 @@ pub fn get_canyon_tokio_runtime() -> &'static Runtime { .get_or_init(|| Runtime::new().expect("Failed initializing the Canyon-SQL Tokio Runtime")) } -static CONFIG_FILE_PATH: OnceLock = OnceLock::new(); -static CONFIG: OnceLock = OnceLock::new(); -static DATASOURCES: OnceLock> = OnceLock::new(); - -// Safer connection wrapper: each conn has its own async mutex pub type SharedConnection = Arc>; -static CACHED_DATABASE_CONN: OnceLock> = OnceLock::new(); -static DEFAULT_CONNECTION: OnceLock = OnceLock::new(); - -/// Attempts to locate a canyon config file. -/// Returns Ok(None) if not found, or Ok(Some(PathBuf)) if found. -pub fn find_canyon_config_file() -> Result, std::io::Error> { - let result = WalkDir::new(".") - .max_depth(2) - .into_iter() - .filter_map(Result::ok) - .find_map(|e| { - let filename = e.file_name().to_string_lossy().to_lowercase(); - if e.metadata().ok()?.is_file() - && filename.starts_with("canyon") - && filename.ends_with(".toml") - { - Some(e.path().to_path_buf()) - } else { - None - } - }); - - Ok(result) -} - -/// Initializes shared config state by loading the config file if found. -/// -/// - Used by macro/automatic path to enforce config presence. -/// - Can be used optionally in manual mode. -/// -/// Returns: -/// - `Ok(Some(()))` => config loaded -/// - `Ok(None)` => config not found -/// - `Err` => parsing or IO error -pub fn try_init_config() -> Result, Box> { - let Some(path) = find_canyon_config_file()? else { - return Ok(None); // Not an error! - }; - - let content = fs::read_to_string(&path)?; - let config: CanyonSqlConfig = toml::from_str(&content)?; - - CONFIG_FILE_PATH.set(path).ok(); - CONFIG.set(config).ok(); - - let datasources = CONFIG - .get() - .map(|cfg| cfg.canyon_sql.datasources.clone()) - .unwrap_or_default(); - - DATASOURCES.set(datasources).ok(); - - Ok(Some(())) +pub struct Canyon { + config: Datasources, + connections: HashMap<&'static str, SharedConnection>, + default: Option, } -/// Required in macro mode only: forcefully load or panic -pub fn force_init_config() { - match try_init_config() { - Ok(Some(())) => {} - Ok(None) => panic!("Canyon config file not found but required in macro mode."), - Err(e) => panic!("Failed to load Canyon config: {}", e), +static CANYON_INSTANCE: OnceLock = OnceLock::new(); + +impl Canyon { + // Singleton access + pub fn instance() -> Result<&'static Self, Box> { + Ok(CANYON_INSTANCE.get().ok_or_else(|| { + Box::new(std::io::Error::new( + std::io::ErrorKind::Other, + "Canyon not initialized. Call `Canyon::init()` first.", + )) + })?) } -} -/// Public accessor for datasources, safe even if uninitialized -pub fn get_datasources() -> &'static [DatasourceConfig] { - DATASOURCES.get().map(Vec::as_slice).unwrap_or_default() -} + // Initializes Canyon instance + pub async fn init() -> Result<&'static Self, Box> { + if CANYON_INSTANCE.get().is_some() { + return Canyon::instance(); // Already initialized, no need to do it again + } -pub async fn init_connections_cache() -> Result<(), Box> { - try_init_config()?; + let path = Canyon::find_config_path()?; + let config_content = fs::read_to_string(&path)?; + let config: Datasources = toml::from_str::(&config_content)?.canyon_sql; - let datasources = get_datasources(); - if datasources.is_empty() { - return Err("No datasources found for connection pool".into()); - } + let mut connections = HashMap::new(); + let mut default = None; - let mut cache = HashMap::new(); - for ds in datasources { - let conn = DatabaseConnection::new(ds).await?; - let name: &'static str = Box::leak(ds.name.clone().into_boxed_str()); - let conn_arc = Arc::new(Mutex::new(conn)); + for ds in config.datasources.iter() { + let conn = DatabaseConnection::new(ds).await?; + let name: &'static str = Box::leak(ds.name.clone().into_boxed_str()); + let conn = Arc::new(Mutex::new(conn)); - if cache.is_empty() { - DEFAULT_CONNECTION.set(conn_arc.clone()).ok(); // Store direct ref + if default.is_none() { + default = Some(conn.clone()); // Only cloning the smart pointer + } + + connections.insert(name, conn); } - cache.insert(name, conn_arc); + let canyon = Canyon { + config, + connections, + default, + }; + + get_canyon_tokio_runtime(); // Just ensuring that is initialized in manual-mode + Ok(CANYON_INSTANCE.get_or_init(|| canyon)) } - CACHED_DATABASE_CONN.set(cache).ok(); - Ok(()) -} + // Internal helper to locate the config file + fn find_config_path() -> Result { + WalkDir::new(".") + .max_depth(2) + .into_iter() + .filter_map(Result::ok) + .find_map(|e| { + let filename = e.file_name().to_string_lossy().to_lowercase(); + if e.metadata().ok()?.is_file() + && filename.starts_with("canyon") + && filename.ends_with(".toml") + { + Some(e.path().to_path_buf()) + } else { + None + } + }) + .ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::NotFound, "No Canyon config found") + }) + } -/// Borrow a connection for read-only use (if immutable suffices) -pub async fn get_cached_connection( - name: &str, -) -> Result, DatasourceNotFound> { - if name.is_empty() { - let default = DEFAULT_CONNECTION - .get() - .ok_or_else(|| DatasourceNotFound::from(None))?; - return Ok(default.lock().await); + // Public accessor for datasources + pub fn datasources(&self) -> &[DatasourceConfig] { + &self.config.datasources } - let cache = CACHED_DATABASE_CONN - .get() - .expect("Connection cache not initialized"); + // Retrieve a datasource by name or default to the first + pub fn find_datasource_by_name_or_default( + &self, + name: &str, + ) -> Result<&DatasourceConfig, DatasourceNotFound> { + if name.is_empty() { + self.config + .datasources + .first() + .ok_or_else(|| DatasourceNotFound::from(None)) + } else { + self.config + .datasources + .iter() + .find(|ds| ds.name == name) + .ok_or_else(|| DatasourceNotFound::from(Some(name))) + } + } - let conn = cache - .get(name) - .ok_or_else(|| DatasourceNotFound::from(Some(name)))?; + // Retrieve a read-only connection from the cache + pub async fn get_connection( + &self, + name: &str, + ) -> Result, DatasourceNotFound> { + if name.is_empty() { + return Ok(self + .default + .as_ref() + .ok_or_else(|| DatasourceNotFound::from(None))? + .lock() + .await); + } - Ok(conn.lock().await) -} + let conn = self + .connections + .get(name) + .ok_or_else(|| DatasourceNotFound::from(Some(name)))?; -/// Mutable access — same as above (just aliasing for clarity) -pub async fn get_mut_cached_connection( - name: &str, -) -> Result, DatasourceNotFound> { - get_cached_connection(name).await -} -pub fn find_datasource_by_name_or_try_default( - name: &str, -) -> Result<&DatasourceConfig, DatasourceNotFound> { - let configs = DATASOURCES - .get() - .expect("Datasources cache not initialized"); - - if name.is_empty() { - return configs - .first() - .ok_or_else(|| DatasourceNotFound::from(None)); + Ok(conn.lock().await) } - configs - .iter() - .find(|ds| ds.name == name) - .ok_or_else(|| DatasourceNotFound::from(Some(name))) + // Retrieve a mutable connection from the cache + pub async fn get_mut_connection( + &self, + name: &str, + ) -> Result, DatasourceNotFound> { + self.get_connection(name).await + } } + +// +// // TODO's: DatabaseConnection and DataSource can implement default, so there's no need to use str and &str +// // as defaults anymore, since the can load as the default the first one defined in the config file, or have more +// // complex workflows that are deferred to initialization time +// +// // TODO: Crud Operations should be split into two different derives, splitting the automagic from the _with ones +// +// // Use OnceLock for the Tokio runtime +// static CANYON_TOKIO_RUNTIME: OnceLock = OnceLock::new(); +// +// // Function to get the runtime (lazy initialization) +// pub fn get_canyon_tokio_runtime() -> &'static Runtime { +// CANYON_TOKIO_RUNTIME +// .get_or_init(|| Runtime::new().expect("Failed initializing the Canyon-SQL Tokio Runtime")) +// } +// +// static CONFIG_FILE_PATH: OnceLock = OnceLock::new(); +// static CONFIG: OnceLock = OnceLock::new(); +// static DATASOURCES: OnceLock> = OnceLock::new(); +// +// // Safer connection wrapper: each conn has its own async mutex +// pub type SharedConnection = Arc>; +// +// static CACHED_DATABASE_CONN: OnceLock> = OnceLock::new(); +// static DEFAULT_CONNECTION: OnceLock = OnceLock::new(); +// +// /// Attempts to locate a canyon config file. +// /// Returns Ok(None) if not found, or Ok(Some(PathBuf)) if found. +// pub fn find_canyon_config_file() -> Result, std::io::Error> { +// let result = WalkDir::new(".") +// .max_depth(2) +// .into_iter() +// .filter_map(Result::ok) +// .find_map(|e| { +// let filename = e.file_name().to_string_lossy().to_lowercase(); +// if e.metadata().ok()?.is_file() +// && filename.starts_with("canyon") +// && filename.ends_with(".toml") +// { +// Some(e.path().to_path_buf()) +// } else { +// None +// } +// }); +// +// Ok(result) +// } +// +// /// Initializes shared config state by loading the config file if found. +// /// +// /// - Used by macro/automatic path to enforce config presence. +// /// - Can be used optionally in manual mode. +// /// +// /// Returns: +// /// - `Ok(Some(()))` => config loaded +// /// - `Ok(None)` => config not found +// /// - `Err` => parsing or IO error +// pub fn try_init_config() -> Result, Box> { +// let Some(path) = find_canyon_config_file()? else { +// return Ok(None); // Not an error! +// }; +// +// let content = fs::read_to_string(&path)?; +// let config: CanyonSqlConfig = toml::from_str(&content)?; +// +// CONFIG_FILE_PATH.set(path).ok(); +// CONFIG.set(config).ok(); +// +// let datasources = CONFIG +// .get() +// .map(|cfg| cfg.canyon_sql.datasources.clone()) +// .unwrap_or_default(); +// +// DATASOURCES.set(datasources).ok(); +// +// Ok(Some(())) +// } +// +// /// Required in macro mode only: forcefully load or panic +// pub fn force_init_config() { +// match try_init_config() { +// Ok(Some(())) => {} +// Ok(None) => panic!("Canyon config file not found but required in macro mode."), +// Err(e) => panic!("Failed to load Canyon config: {}", e), +// } +// } +// +// /// Public accessor for datasources, safe even if uninitialized +// pub fn get_datasources() -> &'static [DatasourceConfig] { +// DATASOURCES.get().map(Vec::as_slice).unwrap_or_default() +// } +// +// pub async fn init_connections_cache() -> Result<(), Box> { +// try_init_config()?; +// +// let datasources = get_datasources(); +// if datasources.is_empty() { +// return Err("No datasources found for connection pool".into()); +// } +// +// let mut cache = HashMap::new(); +// for ds in datasources { +// let conn = DatabaseConnection::new(ds).await?; +// let name: &'static str = Box::leak(ds.name.clone().into_boxed_str()); +// let conn_arc = Arc::new(Mutex::new(conn)); +// +// if cache.is_empty() { +// DEFAULT_CONNECTION.set(conn_arc.clone()).ok(); // Store direct ref +// } +// +// cache.insert(name, conn_arc); +// } +// +// CACHED_DATABASE_CONN.set(cache).ok(); +// Ok(()) +// } +// +// /// Borrow a connection for read-only use (if immutable suffices) +// pub async fn get_cached_connection( +// name: &str, +// ) -> Result, DatasourceNotFound> { +// if name.is_empty() { +// let default = DEFAULT_CONNECTION +// .get() +// .ok_or_else(|| DatasourceNotFound::from(None))?; +// return Ok(default.lock().await); +// } +// +// let cache = CACHED_DATABASE_CONN +// .get() +// .expect("Connection cache not initialized"); +// +// let conn = cache +// .get(name) +// .ok_or_else(|| DatasourceNotFound::from(Some(name)))?; +// +// Ok(conn.lock().await) +// } +// +// /// Mutable access — same as above (just aliasing for clarity) +// pub async fn get_mut_cached_connection( +// name: &str, +// ) -> Result, DatasourceNotFound> { +// get_cached_connection(name).await +// } +// pub fn find_datasource_by_name_or_try_default( +// name: &str, +// ) -> Result<&DatasourceConfig, DatasourceNotFound> { +// let configs = DATASOURCES +// .get() +// .expect("Datasources cache not initialized"); +// +// if name.is_empty() { +// return configs +// .first() +// .ok_or_else(|| DatasourceNotFound::from(None)); +// } +// +// configs +// .iter() +// .find(|ds| ds.name == name) +// .ok_or_else(|| DatasourceNotFound::from(Some(name))) +// } diff --git a/canyon_macros/src/canyon_macro.rs b/canyon_macros/src/canyon_macro.rs index 1abcc05f..9feb5334 100644 --- a/canyon_macros/src/canyon_macro.rs +++ b/canyon_macros/src/canyon_macro.rs @@ -10,7 +10,7 @@ use quote::quote; pub fn main_with_queries() -> TokenStream { // TODO: migrations on main instead of main_with_queries get_canyon_tokio_runtime().block_on(async { - canyon_core::connection::init_connections_cache() + canyon_core::connection::Canyon::init() .await .expect("Error initializing the connections POOL"); Migrations::migrate().await; diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index ca067529..51ece4c7 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -65,7 +65,7 @@ pub fn main(_meta: CompilerTokenStream, input: CompilerTokenStream) -> CompilerT canyon_sql::runtime::get_canyon_tokio_runtime() .handle() .block_on( async { - canyon_sql::runtime::init_connections_cache().await + canyon_sql::core::Canyon::init().await .expect("Error initializing the connections POOL"); #migrations_tokens #(#body)* @@ -100,7 +100,7 @@ pub fn canyon_tokio_test( canyon_sql::runtime::get_canyon_tokio_runtime() .handle() .block_on( async { - canyon_sql::runtime::init_connections_cache().await + canyon_sql::core::Canyon::init().await .expect("Error initializing the connections POOL"); #(#body)* }); diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index 7463b508..0a6b0fb7 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -7,7 +7,7 @@ use crate::{ processor::MigrationsProcessor, }, }; -use canyon_core::connection::get_datasources; +use canyon_core::connection::Canyon; use canyon_core::{ column::Column, connection::db_connector::DatabaseConnection, @@ -28,7 +28,10 @@ impl Migrations { /// and the database table with the memory of Canyon to perform the /// migrations over the targeted database pub async fn migrate() { - for datasource in get_datasources() { + for datasource in Canyon::instance() + .expect("Failure getting datasources on migrations") + .datasources() + { if !datasource.has_migrations_enabled() { continue; } @@ -38,7 +41,12 @@ impl Migrations { ); let mut migrations_processor = MigrationsProcessor::default(); - let mut db_conn = canyon_core::connection::get_cached_connection(&datasource.name) + let mut db_conn = Canyon::instance() + .expect(&format!( + "Failure getting db connection: {}", + &datasource.name + )) + .get_connection(&datasource.name) .await .unwrap_or_else(|_| { panic!( diff --git a/canyon_migrations/src/migrations/memory.rs b/canyon_migrations/src/migrations/memory.rs index 27dac620..b87174ed 100644 --- a/canyon_migrations/src/migrations/memory.rs +++ b/canyon_migrations/src/migrations/memory.rs @@ -65,7 +65,12 @@ impl CanyonMemory { datasource: &DatasourceConfig, canyon_entities: &[CanyonRegisterEntity<'_>], ) -> Self { - let mut db_conn = canyon_core::connection::get_cached_connection(&datasource.name) + let mut db_conn = canyon_core::connection::Canyon::instance() + .expect(&format!( + "Failure getting db connection: {} on Canyon Memory", + &datasource.name + )) + .get_connection(&datasource.name) .await .unwrap_or_else(|_| { panic!( diff --git a/canyon_migrations/src/migrations/processor.rs b/canyon_migrations/src/migrations/processor.rs index ef1f2cd3..ea41bce4 100644 --- a/canyon_migrations/src/migrations/processor.rs +++ b/canyon_migrations/src/migrations/processor.rs @@ -583,7 +583,9 @@ impl MigrationsProcessor { pub async fn from_query_register(queries_to_execute: &HashMap<&str, Vec<&str>>) { for datasource in queries_to_execute.iter() { let datasource_name = datasource.0; - let db_conn = canyon_core::connection::get_cached_connection(datasource_name) + let db_conn = canyon_core::connection::Canyon::instance() + .expect("Error getting db connection on `from_query_register`") + .get_connection(datasource_name) .await .unwrap_or_else(|_| { panic!( diff --git a/src/lib.rs b/src/lib.rs index b7463e78..505c60ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,12 +29,12 @@ pub mod macros { pub mod connection { pub use canyon_core::connection::database_type::DatabaseType; pub use canyon_core::connection::db_connector::DatabaseConnection; - pub use canyon_core::connection::find_datasource_by_name_or_try_default; - pub use canyon_core::connection::get_cached_connection; + pub use canyon_core::connection::Canyon; } pub mod core { pub use canyon_core::connection::db_connector::DbConnection; + pub use canyon_core::connection::Canyon; pub use canyon_core::mapper::*; pub use canyon_core::query_parameters::QueryParameter; pub use canyon_core::rows::CanyonRows; @@ -68,7 +68,6 @@ pub mod db_clients { pub mod runtime { pub use canyon_core::connection::futures; pub use canyon_core::connection::get_canyon_tokio_runtime; - pub use canyon_core::connection::init_connections_cache; pub use canyon_core::connection::tokio; pub use canyon_core::connection::tokio_util; } diff --git a/tests/migrations/mod.rs b/tests/migrations/mod.rs index ad67304c..b6f6e937 100644 --- a/tests/migrations/mod.rs +++ b/tests/migrations/mod.rs @@ -1,7 +1,7 @@ #![allow(unused_imports)] use crate::constants; -use canyon_sql::connection::{find_datasource_by_name_or_try_default, get_cached_connection}; +use canyon_sql::core::Canyon; use canyon_sql::core::DbConnection; /// Integration tests for the migrations feature of `Canyon-SQL` use canyon_sql::core::Transaction; @@ -12,12 +12,14 @@ use canyon_sql::migrations::handler::Migrations; #[cfg(all(feature = "postgres", feature = "migrations"))] #[canyon_sql::macros::canyon_tokio_test] fn test_migrations_postgresql_status_query() { - let ds = find_datasource_by_name_or_try_default(constants::PSQL_DS); + let canyon = Canyon::instance().unwrap(); + + let ds = canyon.find_datasource_by_name_or_default(constants::PSQL_DS); assert!(ds.is_ok()); let ds = ds.unwrap(); let ds_name = &ds.name; - let db_conn = get_cached_connection(ds_name).await.unwrap_or_else(|_| { + let db_conn = canyon.get_connection(ds_name).await.unwrap_or_else(|_| { panic!( "Unable to get a database connection on Canyon Memory: {:?}", ds_name From 9fc3bdbc8e340f272cf30dc5c4b48745fff01b0e Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 2 May 2025 17:58:11 +0200 Subject: [PATCH 102/155] chore: legacy dead code cleanup --- bash_aliases.sh | 4 +- canyon_core/src/connection/mod.rs | 174 ++------------------ canyon_migrations/src/migrations/handler.rs | 6 +- canyon_migrations/src/migrations/memory.rs | 6 +- 4 files changed, 16 insertions(+), 174 deletions(-) diff --git a/bash_aliases.sh b/bash_aliases.sh index 348b039f..3c3aeed9 100755 --- a/bash_aliases.sh +++ b/bash_aliases.sh @@ -41,11 +41,11 @@ alias IntegrationTestsLinux='cargo test --all-features --no-fail-fast -p tests - alias ITIncludeIgnoredLinux='cargo test --all-features --no-fail-fast -p tests --target=x86_64-unknown-linux-gnu -- --show-output --test-threads=1 --nocapture --test-threads=1 --include-ignored' alias SqlServerInitializationLinux='cargo test initialize_sql_server_docker_instance -p tests --all-features --no-fail-fast --target=x86_64-unknown-linux-gnu -- --show-output --test-threads=1 --nocapture --include-ignored' ------ +# ----- # Publish Canyon-SQL to the registry with its dependencies alias PublishCanyon='cargo publish -p canyon_connection && cargo publish -p canyon_crud && cargo publish -p canyon_migrations && cargo publish -p canyon_macros && cargo publish -p canyon_sql_root' ------ +# ----- # Collects the code coverage for the project (tests must run before this) alias CcEnvVars='export CARGO_INCREMENTAL=0 export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" diff --git a/canyon_core/src/connection/mod.rs b/canyon_core/src/connection/mod.rs index 8c3224fd..df2bc14f 100644 --- a/canyon_core/src/connection/mod.rs +++ b/canyon_core/src/connection/mod.rs @@ -31,6 +31,16 @@ use tokio::runtime::Runtime; use tokio::sync::Mutex; use walkdir::WalkDir; + +// +// // TODO's: DatabaseConnection and DataSource can implement default, so there's no need to use str and &str +// // as defaults anymore, since the can load as the default the first one defined in the config file, or have more +// // complex workflows that are deferred to initialization time +// +// // TODO: Crud Operations should be split into two different derives, splitting the automagic from the _with ones + +// TODO: move Canyon struct to core + // Use OnceLock for the Tokio runtime static CANYON_TOKIO_RUNTIME: OnceLock = OnceLock::new(); @@ -172,167 +182,3 @@ impl Canyon { self.get_connection(name).await } } - -// -// // TODO's: DatabaseConnection and DataSource can implement default, so there's no need to use str and &str -// // as defaults anymore, since the can load as the default the first one defined in the config file, or have more -// // complex workflows that are deferred to initialization time -// -// // TODO: Crud Operations should be split into two different derives, splitting the automagic from the _with ones -// -// // Use OnceLock for the Tokio runtime -// static CANYON_TOKIO_RUNTIME: OnceLock = OnceLock::new(); -// -// // Function to get the runtime (lazy initialization) -// pub fn get_canyon_tokio_runtime() -> &'static Runtime { -// CANYON_TOKIO_RUNTIME -// .get_or_init(|| Runtime::new().expect("Failed initializing the Canyon-SQL Tokio Runtime")) -// } -// -// static CONFIG_FILE_PATH: OnceLock = OnceLock::new(); -// static CONFIG: OnceLock = OnceLock::new(); -// static DATASOURCES: OnceLock> = OnceLock::new(); -// -// // Safer connection wrapper: each conn has its own async mutex -// pub type SharedConnection = Arc>; -// -// static CACHED_DATABASE_CONN: OnceLock> = OnceLock::new(); -// static DEFAULT_CONNECTION: OnceLock = OnceLock::new(); -// -// /// Attempts to locate a canyon config file. -// /// Returns Ok(None) if not found, or Ok(Some(PathBuf)) if found. -// pub fn find_canyon_config_file() -> Result, std::io::Error> { -// let result = WalkDir::new(".") -// .max_depth(2) -// .into_iter() -// .filter_map(Result::ok) -// .find_map(|e| { -// let filename = e.file_name().to_string_lossy().to_lowercase(); -// if e.metadata().ok()?.is_file() -// && filename.starts_with("canyon") -// && filename.ends_with(".toml") -// { -// Some(e.path().to_path_buf()) -// } else { -// None -// } -// }); -// -// Ok(result) -// } -// -// /// Initializes shared config state by loading the config file if found. -// /// -// /// - Used by macro/automatic path to enforce config presence. -// /// - Can be used optionally in manual mode. -// /// -// /// Returns: -// /// - `Ok(Some(()))` => config loaded -// /// - `Ok(None)` => config not found -// /// - `Err` => parsing or IO error -// pub fn try_init_config() -> Result, Box> { -// let Some(path) = find_canyon_config_file()? else { -// return Ok(None); // Not an error! -// }; -// -// let content = fs::read_to_string(&path)?; -// let config: CanyonSqlConfig = toml::from_str(&content)?; -// -// CONFIG_FILE_PATH.set(path).ok(); -// CONFIG.set(config).ok(); -// -// let datasources = CONFIG -// .get() -// .map(|cfg| cfg.canyon_sql.datasources.clone()) -// .unwrap_or_default(); -// -// DATASOURCES.set(datasources).ok(); -// -// Ok(Some(())) -// } -// -// /// Required in macro mode only: forcefully load or panic -// pub fn force_init_config() { -// match try_init_config() { -// Ok(Some(())) => {} -// Ok(None) => panic!("Canyon config file not found but required in macro mode."), -// Err(e) => panic!("Failed to load Canyon config: {}", e), -// } -// } -// -// /// Public accessor for datasources, safe even if uninitialized -// pub fn get_datasources() -> &'static [DatasourceConfig] { -// DATASOURCES.get().map(Vec::as_slice).unwrap_or_default() -// } -// -// pub async fn init_connections_cache() -> Result<(), Box> { -// try_init_config()?; -// -// let datasources = get_datasources(); -// if datasources.is_empty() { -// return Err("No datasources found for connection pool".into()); -// } -// -// let mut cache = HashMap::new(); -// for ds in datasources { -// let conn = DatabaseConnection::new(ds).await?; -// let name: &'static str = Box::leak(ds.name.clone().into_boxed_str()); -// let conn_arc = Arc::new(Mutex::new(conn)); -// -// if cache.is_empty() { -// DEFAULT_CONNECTION.set(conn_arc.clone()).ok(); // Store direct ref -// } -// -// cache.insert(name, conn_arc); -// } -// -// CACHED_DATABASE_CONN.set(cache).ok(); -// Ok(()) -// } -// -// /// Borrow a connection for read-only use (if immutable suffices) -// pub async fn get_cached_connection( -// name: &str, -// ) -> Result, DatasourceNotFound> { -// if name.is_empty() { -// let default = DEFAULT_CONNECTION -// .get() -// .ok_or_else(|| DatasourceNotFound::from(None))?; -// return Ok(default.lock().await); -// } -// -// let cache = CACHED_DATABASE_CONN -// .get() -// .expect("Connection cache not initialized"); -// -// let conn = cache -// .get(name) -// .ok_or_else(|| DatasourceNotFound::from(Some(name)))?; -// -// Ok(conn.lock().await) -// } -// -// /// Mutable access — same as above (just aliasing for clarity) -// pub async fn get_mut_cached_connection( -// name: &str, -// ) -> Result, DatasourceNotFound> { -// get_cached_connection(name).await -// } -// pub fn find_datasource_by_name_or_try_default( -// name: &str, -// ) -> Result<&DatasourceConfig, DatasourceNotFound> { -// let configs = DATASOURCES -// .get() -// .expect("Datasources cache not initialized"); -// -// if name.is_empty() { -// return configs -// .first() -// .ok_or_else(|| DatasourceNotFound::from(None)); -// } -// -// configs -// .iter() -// .find(|ds| ds.name == name) -// .ok_or_else(|| DatasourceNotFound::from(Some(name))) -// } diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index 0a6b0fb7..2efbab1d 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -42,10 +42,8 @@ impl Migrations { let mut migrations_processor = MigrationsProcessor::default(); let mut db_conn = Canyon::instance() - .expect(&format!( - "Failure getting db connection: {}", - &datasource.name - )) + .unwrap_or_else(|_| panic!("Failure getting db connection: {}", + &datasource.name)) .get_connection(&datasource.name) .await .unwrap_or_else(|_| { diff --git a/canyon_migrations/src/migrations/memory.rs b/canyon_migrations/src/migrations/memory.rs index b87174ed..23885974 100644 --- a/canyon_migrations/src/migrations/memory.rs +++ b/canyon_migrations/src/migrations/memory.rs @@ -66,10 +66,8 @@ impl CanyonMemory { canyon_entities: &[CanyonRegisterEntity<'_>], ) -> Self { let mut db_conn = canyon_core::connection::Canyon::instance() - .expect(&format!( - "Failure getting db connection: {} on Canyon Memory", - &datasource.name - )) + .unwrap_or_else(|_| panic!("Failure getting db connection: {} on Canyon Memory", + &datasource.name)) .get_connection(&datasource.name) .await .unwrap_or_else(|_| { From a3d5c758d38385eb9d8175961d561cacc95cf730 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sat, 3 May 2025 11:12:12 +0200 Subject: [PATCH 103/155] feat: new macro! to reduce the code required for implement str and &str for DbConnection --- canyon_core/src/connection/db_connector.rs | 191 +++++++------------- canyon_core/src/connection/mod.rs | 1 - canyon_migrations/src/migrations/handler.rs | 3 +- canyon_migrations/src/migrations/memory.rs | 8 +- 4 files changed, 72 insertions(+), 131 deletions(-) diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index bbbbd701..9dfef6e5 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -7,7 +7,7 @@ use crate::connection::db_clients::mysql::MysqlConnection; #[cfg(feature = "postgres")] use crate::connection::db_clients::postgresql::PostgreSqlConnection; use crate::connection::db_connector::connection_helpers::{ - db_conn_launch_impl, db_conn_query_one_impl, + db_conn_query_one_impl, db_conn_query_rows_impl, }; use crate::connection::Canyon; use crate::mapper::RowMapper; @@ -65,135 +65,74 @@ pub trait DbConnection { fn get_database_type(&self) -> Result>; } -/// This impl of [` DbConnection` ] for [`&str`] allows the client to use the exposed input types -/// on the public API that works with a generic parameter to refer to a database connection -/// directly with an [`&str`] that must match one of the datasources defined -/// within the user config file -impl DbConnection for str { - async fn query_rows<'a>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - let conn = Canyon::instance()?.get_connection(self).await?; - conn.query_rows(stmt, params).await - } +macro_rules! impl_db_connection { + ($type:ty) => { + impl DbConnection for $type { + async fn query_rows<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + let conn = Canyon::instance()?.get_connection(self).await?; + conn.query_rows(stmt, params).await + } - async fn query<'a, S, R>( - &self, - stmt: S, - params: &[&'a (dyn QueryParameter<'a>)], - ) -> Result, Box<(dyn Error + Send + Sync)>> - where - S: AsRef + Display + Send, - R: RowMapper, - Vec: FromIterator<::Output>, - { - let conn = Canyon::instance()?.get_connection(self).await?; - conn.query(stmt, params).await - } + async fn query<'a, S, R>( + &self, + stmt: S, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> Result, Box<(dyn Error + Send + Sync)>> + where + S: AsRef + Display + Send, + R: RowMapper, + Vec: FromIterator<::Output>, + { + let conn = Canyon::instance()?.get_connection(self).await?; + conn.query(stmt, params).await + } - async fn query_one<'a, R>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result, Box<(dyn Error + Send + Sync)>> - where - R: RowMapper, - { - let conn = Canyon::instance()?.get_connection(self).await?; - conn.query_one::(stmt, params).await - } + async fn query_one<'a, R>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result, Box<(dyn Error + Send + Sync)>> + where + R: RowMapper, + { + let conn = Canyon::instance()?.get_connection(self).await?; + conn.query_one::(stmt, params).await + } - async fn query_one_for<'a, T: FromSqlOwnedValue>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - let conn = Canyon::instance()?.get_connection(self).await?; - conn.query_one_for(stmt, params).await - } + async fn query_one_for<'a, T: FromSqlOwnedValue>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + let conn = Canyon::instance()?.get_connection(self).await?; + conn.query_one_for(stmt, params).await + } - async fn execute<'a>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - let conn = Canyon::instance()?.get_connection(self).await?; - conn.execute(stmt, params).await - } + async fn execute<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + let conn = Canyon::instance()?.get_connection(self).await?; + conn.execute(stmt, params).await + } - fn get_database_type(&self) -> Result> { - Ok(Canyon::instance()? - .find_datasource_by_name_or_default(self)? - .get_db_type()) - } + fn get_database_type(&self) -> Result> { + Ok(Canyon::instance()? + .find_datasource_by_name_or_default(self)? + .get_db_type()) + } + } + }; } -/// This impl of [` DbConnection` ] for [`&str`] allows the client to use the exposed input types -/// on the public API that works with a generic parameter to refer to a database connection -/// directly with an [`&str`] that must match one of the datasources defined -/// within the user config file -impl DbConnection for &str { - async fn query_rows<'a>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - let conn = Canyon::instance()?.get_connection(self).await?; - conn.query_rows(stmt, params).await - } - - async fn query<'a, S, R>( - &self, - stmt: S, - params: &[&'a (dyn QueryParameter<'a>)], - ) -> Result, Box<(dyn Error + Send + Sync)>> - where - S: AsRef + Display + Send, - R: RowMapper, - Vec: FromIterator<::Output>, - { - let conn = Canyon::instance()?.get_connection(self).await?; - conn.query(stmt, params).await - } - - async fn query_one<'a, R>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result, Box<(dyn Error + Send + Sync)>> - where - R: RowMapper, - { - let conn = Canyon::instance()?.get_connection(self).await?; - conn.query_one::(stmt, params).await - } - - async fn query_one_for<'a, T: FromSqlOwnedValue>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - let conn = Canyon::instance()?.get_connection(self).await?; - conn.query_one_for(stmt, params).await - } - - async fn execute<'a>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - let conn = Canyon::instance()?.get_connection(self).await?; - conn.execute(stmt, params).await - } - - fn get_database_type(&self) -> Result> { - Ok(Canyon::instance()? - .find_datasource_by_name_or_default(self)? - .get_db_type()) - } -} +// Apply the macro to implement DbConnection for &str and str +impl_db_connection!(str); +impl_db_connection!(&str); /// The Canyon database connection handler. When the client's program /// starts, Canyon gets the information about the desired datasources, @@ -215,7 +154,7 @@ impl DbConnection for DatabaseConnection { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result> { - db_conn_launch_impl(self, stmt, params).await + db_conn_query_rows_impl(self, stmt, params).await } async fn query<'a, S, R>( @@ -295,7 +234,7 @@ impl DbConnection for &mut DatabaseConnection { stmt: &str, params: &[&'a dyn QueryParameter<'a>], ) -> Result> { - db_conn_launch_impl(self, stmt, params).await + db_conn_query_rows_impl(self, stmt, params).await } async fn query<'a, S, R>( &self, @@ -517,7 +456,7 @@ mod connection_helpers { ) } - pub(crate) async fn db_conn_launch_impl<'a>( + pub(crate) async fn db_conn_query_rows_impl<'a>( c: &DatabaseConnection, stmt: &str, params: &[&'a (dyn QueryParameter<'a> + 'a)], diff --git a/canyon_core/src/connection/mod.rs b/canyon_core/src/connection/mod.rs index df2bc14f..5e99c13b 100644 --- a/canyon_core/src/connection/mod.rs +++ b/canyon_core/src/connection/mod.rs @@ -31,7 +31,6 @@ use tokio::runtime::Runtime; use tokio::sync::Mutex; use walkdir::WalkDir; - // // // TODO's: DatabaseConnection and DataSource can implement default, so there's no need to use str and &str // // as defaults anymore, since the can load as the default the first one defined in the config file, or have more diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index 2efbab1d..b8d7ab5a 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -42,8 +42,7 @@ impl Migrations { let mut migrations_processor = MigrationsProcessor::default(); let mut db_conn = Canyon::instance() - .unwrap_or_else(|_| panic!("Failure getting db connection: {}", - &datasource.name)) + .unwrap_or_else(|_| panic!("Failure getting db connection: {}", &datasource.name)) .get_connection(&datasource.name) .await .unwrap_or_else(|_| { diff --git a/canyon_migrations/src/migrations/memory.rs b/canyon_migrations/src/migrations/memory.rs index 23885974..c4772bd1 100644 --- a/canyon_migrations/src/migrations/memory.rs +++ b/canyon_migrations/src/migrations/memory.rs @@ -66,8 +66,12 @@ impl CanyonMemory { canyon_entities: &[CanyonRegisterEntity<'_>], ) -> Self { let mut db_conn = canyon_core::connection::Canyon::instance() - .unwrap_or_else(|_| panic!("Failure getting db connection: {} on Canyon Memory", - &datasource.name)) + .unwrap_or_else(|_| { + panic!( + "Failure getting db connection: {} on Canyon Memory", + &datasource.name + ) + }) .get_connection(&datasource.name) .await .unwrap_or_else(|_| { From d9b6e7c89c720be5b3c15060f1c124840c4cae91 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sat, 3 May 2025 12:26:39 +0200 Subject: [PATCH 104/155] docs: canyon_core::connection --- canyon_core/src/canyon.rs | 186 ++++++++++ .../connection/{db_clients => clients}/mod.rs | 0 .../{db_clients => clients}/mssql.rs | 59 ---- .../{db_clients => clients}/mysql.rs | 57 --- .../{db_clients => clients}/postgresql.rs | 58 --- .../contracts/impl/database_connection.rs | 213 +++++++++++ .../src/connection/contracts/impl/mod.rs | 12 + .../src/connection/contracts/impl/mssql.rs | 65 ++++ .../src/connection/contracts/impl/mysql.rs | 64 ++++ .../connection/contracts/impl/postgresql.rs | 65 ++++ .../src/connection/contracts/impl/str.rs | 81 +++++ canyon_core/src/connection/contracts/mod.rs | 121 +++++++ canyon_core/src/connection/datasources.rs | 6 + canyon_core/src/connection/db_connector.rs | 333 +----------------- canyon_core/src/connection/mod.rs | 155 +------- canyon_core/src/connection/types/mod.rs | 0 canyon_core/src/lib.rs | 8 + canyon_core/src/mapper.rs | 5 + canyon_core/src/rows.rs | 5 + canyon_core/src/transaction.rs | 40 ++- canyon_crud/src/crud.rs | 2 +- .../src/query_elements/query_builder.rs | 2 +- canyon_macros/src/canyon_macro.rs | 2 +- canyon_migrations/src/migrations/handler.rs | 2 +- canyon_migrations/src/migrations/memory.rs | 6 +- canyon_migrations/src/migrations/processor.rs | 5 +- src/lib.rs | 5 +- 27 files changed, 898 insertions(+), 659 deletions(-) create mode 100644 canyon_core/src/canyon.rs rename canyon_core/src/connection/{db_clients => clients}/mod.rs (100%) rename canyon_core/src/connection/{db_clients => clients}/mssql.rs (71%) rename canyon_core/src/connection/{db_clients => clients}/mysql.rs (77%) rename canyon_core/src/connection/{db_clients => clients}/postgresql.rs (66%) create mode 100644 canyon_core/src/connection/contracts/impl/database_connection.rs create mode 100644 canyon_core/src/connection/contracts/impl/mod.rs create mode 100644 canyon_core/src/connection/contracts/impl/mssql.rs create mode 100644 canyon_core/src/connection/contracts/impl/mysql.rs create mode 100644 canyon_core/src/connection/contracts/impl/postgresql.rs create mode 100644 canyon_core/src/connection/contracts/impl/str.rs create mode 100644 canyon_core/src/connection/contracts/mod.rs create mode 100644 canyon_core/src/connection/types/mod.rs diff --git a/canyon_core/src/canyon.rs b/canyon_core/src/canyon.rs new file mode 100644 index 00000000..3e42b552 --- /dev/null +++ b/canyon_core/src/canyon.rs @@ -0,0 +1,186 @@ +// ...existing code... + +use crate::connection::conn_errors::DatasourceNotFound; +use crate::connection::datasources::{CanyonSqlConfig, DatasourceConfig, Datasources}; +use crate::connection::{db_connector, get_canyon_tokio_runtime, CANYON_INSTANCE}; +use db_connector::DatabaseConnection; +use std::collections::HashMap; +use std::path::PathBuf; +use std::sync::Arc; +use std::{error::Error, fs}; +use tokio::sync::Mutex; +use walkdir::WalkDir; + +pub type SharedConnection = Arc>; + +/// The `Canyon` struct provides the main entry point for interacting with the Canyon-SQL context. +/// +/// This struct is responsible for managing database connections, configuration, and datasources. +/// It acts as a singleton, ensuring that only one instance of the Canyon context exists throughout +/// the application lifecycle. The `Canyon` struct provides methods for initializing the context, +/// accessing datasources, and retrieving database connections. +/// +/// # Features +/// - Singleton access to the Canyon context. +/// - Automatic discovery and loading of configuration files. +/// - Management of multiple database connections. +/// - Support for retrieving connections by name or default. +/// +/// # Examples +/// ```rust +/// use canyon_core::Canyon; +/// +/// #[tokio::main] +/// async fn main() -> Result<(), Box> { +/// // Initialize the Canyon context +/// let canyon = Canyon::init().await?; +/// +/// // Access datasources +/// let datasources = canyon.datasources(); +/// for ds in datasources { +/// println!("Datasource: {}", ds.name); +/// } +/// +/// // Retrieve a connection by name +/// let connection = canyon.get_connection("MyDatasource").await?; +/// // Use the connection... +/// +/// Ok(()) +/// } +/// ``` +/// +/// # Methods +/// - `init`: Initializes the Canyon context by loading configuration and setting up connections. +/// - `instance`: Provides singleton access to the Canyon context. +/// - `datasources`: Returns a list of configured datasources. +/// - `find_datasource_by_name_or_default`: Finds a datasource by name or returns the default. +/// - `get_connection`: Retrieves a read-only connection from the cache. +/// - `get_mut_connection`: Retrieves a mutable connection from the cache. +pub struct Canyon { + config: Datasources, + connections: HashMap<&'static str, SharedConnection>, + default: Option, +} + +impl Canyon { + // Singleton access + pub fn instance() -> Result<&'static Self, Box> { + Ok(CANYON_INSTANCE.get().ok_or_else(|| { + Box::new(std::io::Error::new( + std::io::ErrorKind::Other, + "Canyon not initialized. Call `Canyon::init()` first.", + )) + })?) + } + + // Initializes Canyon instance + pub async fn init() -> Result<&'static Self, Box> { + if CANYON_INSTANCE.get().is_some() { + return Canyon::instance(); // Already initialized, no need to do it again + } + + let path = Canyon::find_config_path()?; + let config_content = fs::read_to_string(&path)?; + let config: Datasources = toml::from_str::(&config_content)?.canyon_sql; + + let mut connections = HashMap::new(); + let mut default = None; + + for ds in config.datasources.iter() { + let conn = DatabaseConnection::new(ds).await?; + let name: &'static str = Box::leak(ds.name.clone().into_boxed_str()); + let conn = Arc::new(Mutex::new(conn)); + + if default.is_none() { + default = Some(conn.clone()); // Only cloning the smart pointer + } + + connections.insert(name, conn); + } + + let canyon = Canyon { + config, + connections, + default, + }; + + get_canyon_tokio_runtime(); // Just ensuring that is initialized in manual-mode + Ok(CANYON_INSTANCE.get_or_init(|| canyon)) + } + + // Internal helper to locate the config file + fn find_config_path() -> Result { + WalkDir::new(".") + .max_depth(2) + .into_iter() + .filter_map(Result::ok) + .find_map(|e| { + let filename = e.file_name().to_string_lossy().to_lowercase(); + if e.metadata().ok()?.is_file() + && filename.starts_with("canyon") + && filename.ends_with(".toml") + { + Some(e.path().to_path_buf()) + } else { + None + } + }) + .ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::NotFound, "No Canyon config found") + }) + } + + // Public accessor for datasources + pub fn datasources(&self) -> &[DatasourceConfig] { + &self.config.datasources + } + + // Retrieve a datasource by name or default to the first + pub fn find_datasource_by_name_or_default( + &self, + name: &str, + ) -> Result<&DatasourceConfig, DatasourceNotFound> { + if name.is_empty() { + self.config + .datasources + .first() + .ok_or_else(|| DatasourceNotFound::from(None)) + } else { + self.config + .datasources + .iter() + .find(|ds| ds.name == name) + .ok_or_else(|| DatasourceNotFound::from(Some(name))) + } + } + + // Retrieve a read-only connection from the cache + pub async fn get_connection( + &self, + name: &str, + ) -> Result, DatasourceNotFound> { + if name.is_empty() { + return Ok(self + .default + .as_ref() + .ok_or_else(|| DatasourceNotFound::from(None))? + .lock() + .await); + } + + let conn = self + .connections + .get(name) + .ok_or_else(|| DatasourceNotFound::from(Some(name)))?; + + Ok(conn.lock().await) + } + + // Retrieve a mutable connection from the cache + pub async fn get_mut_connection( + &self, + name: &str, + ) -> Result, DatasourceNotFound> { + self.get_connection(name).await + } +} diff --git a/canyon_core/src/connection/db_clients/mod.rs b/canyon_core/src/connection/clients/mod.rs similarity index 100% rename from canyon_core/src/connection/db_clients/mod.rs rename to canyon_core/src/connection/clients/mod.rs diff --git a/canyon_core/src/connection/db_clients/mssql.rs b/canyon_core/src/connection/clients/mssql.rs similarity index 71% rename from canyon_core/src/connection/db_clients/mssql.rs rename to canyon_core/src/connection/clients/mssql.rs index 3fbedfe7..ec6df852 100644 --- a/canyon_core/src/connection/db_clients/mssql.rs +++ b/canyon_core/src/connection/clients/mssql.rs @@ -1,13 +1,8 @@ -use crate::connection::database_type::DatabaseType; -use crate::connection::db_connector::DbConnection; -use crate::mapper::RowMapper; -use crate::rows::FromSqlOwnedValue; use crate::{query_parameters::QueryParameter, rows::CanyonRows}; #[cfg(feature = "mssql")] use async_std::net::TcpStream; use std::error::Error; use std::fmt::Display; -use std::future::Future; use tiberius::Query; /// A connection with a `SqlServer` database @@ -16,60 +11,6 @@ pub struct SqlServerConnection { pub client: &'static mut tiberius::Client, } -impl DbConnection for SqlServerConnection { - fn query_rows<'a>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send { - sqlserver_query_launcher::query_rows(stmt, params, self) - } - - fn query<'a, S, R>( - &self, - stmt: S, - params: &[&'a (dyn QueryParameter<'a>)], - ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send - where - S: AsRef + Display + Send, - R: RowMapper, - Vec: FromIterator<::Output>, - { - sqlserver_query_launcher::query(stmt, params, self) - } - - fn query_one<'a, R>( - &self, - stmt: &str, - params: &[&'a (dyn QueryParameter<'a>)], - ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send - where - R: RowMapper, - { - sqlserver_query_launcher::query_one::(stmt, params, self) - } - - fn query_one_for<'a, T: FromSqlOwnedValue>( - &self, - stmt: &str, - params: &[&'a (dyn QueryParameter<'a>)], - ) -> impl Future>> + Send { - sqlserver_query_launcher::query_one_for(stmt, params, self) - } - - fn execute<'a>( - &self, - stmt: &str, - params: &[&'a (dyn QueryParameter<'a>)], - ) -> impl Future>> + Send { - sqlserver_query_launcher::execute(stmt, params, self) - } - - fn get_database_type(&self) -> Result> { - Ok(DatabaseType::SqlServer) - } -} - #[cfg(feature = "mssql")] pub(crate) mod sqlserver_query_launcher { use super::*; diff --git a/canyon_core/src/connection/db_clients/mysql.rs b/canyon_core/src/connection/clients/mysql.rs similarity index 77% rename from canyon_core/src/connection/db_clients/mysql.rs rename to canyon_core/src/connection/clients/mysql.rs index 3de45a08..2318e0dc 100644 --- a/canyon_core/src/connection/db_clients/mysql.rs +++ b/canyon_core/src/connection/clients/mysql.rs @@ -1,5 +1,3 @@ -use crate::connection::database_type::DatabaseType; -use crate::connection::db_connector::DbConnection; use crate::mapper::RowMapper; use crate::rows::FromSqlOwnedValue; use crate::{query_parameters::QueryParameter, rows::CanyonRows}; @@ -10,7 +8,6 @@ use mysql_common::constants::ColumnType; use mysql_common::row; use std::error::Error; use std::fmt::Display; -use std::future::Future; /// A connection with a `Mysql` database #[cfg(feature = "mysql")] @@ -18,60 +15,6 @@ pub struct MysqlConnection { pub client: Pool, } -impl DbConnection for MysqlConnection { - fn query_rows<'a>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send { - mysql_query_launcher::query_rows(stmt, params, self) - } - - fn query<'a, S, R>( - &self, - stmt: S, - params: &[&'a (dyn QueryParameter<'a>)], - ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send - where - S: AsRef + Display + Send, - R: RowMapper, - Vec: FromIterator<::Output>, - { - mysql_query_launcher::query(stmt, params, self) - } - - fn query_one<'a, R>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send - where - R: RowMapper, - { - mysql_query_launcher::query_one::(stmt, params, self) - } - - fn query_one_for<'a, T: FromSqlOwnedValue>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send { - mysql_query_launcher::query_one_for(stmt, params, self) - } - - fn execute<'a>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send { - mysql_query_launcher::execute(stmt, params, self) - } - - fn get_database_type(&self) -> Result> { - Ok(DatabaseType::MySQL) - } -} - #[cfg(feature = "mysql")] pub(crate) mod mysql_query_launcher { #[cfg(feature = "mysql")] diff --git a/canyon_core/src/connection/db_clients/postgresql.rs b/canyon_core/src/connection/clients/postgresql.rs similarity index 66% rename from canyon_core/src/connection/db_clients/postgresql.rs rename to canyon_core/src/connection/clients/postgresql.rs index 8f5f61da..562f2cb8 100644 --- a/canyon_core/src/connection/db_clients/postgresql.rs +++ b/canyon_core/src/connection/clients/postgresql.rs @@ -1,11 +1,7 @@ -use crate::connection::database_type::DatabaseType; -use crate::connection::db_connector::DbConnection; use crate::mapper::RowMapper; -use crate::rows::FromSqlOwnedValue; use crate::{query_parameters::QueryParameter, rows::CanyonRows}; use std::error::Error; use std::fmt::Display; -use std::future::Future; #[cfg(feature = "postgres")] use tokio_postgres::Client; @@ -16,60 +12,6 @@ pub struct PostgreSqlConnection { // pub connection: Connection, // TODO Hold it, or not to hold it... that's the question! } -impl DbConnection for PostgreSqlConnection { - fn query_rows<'a>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send { - postgres_query_launcher::query_rows(stmt, params, self) - } - - fn query<'a, S, R>( - &self, - stmt: S, - params: &[&'a (dyn QueryParameter<'a>)], - ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send - where - S: AsRef + Display + Send, - R: RowMapper, - Vec: FromIterator<::Output>, - { - postgres_query_launcher::query(stmt, params, self) - } - - fn query_one<'a, R>( - &self, - stmt: &str, - params: &[&'a (dyn QueryParameter<'a>)], - ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send - where - R: RowMapper, - { - postgres_query_launcher::query_one::(stmt, params, self) - } - - fn query_one_for<'a, T: FromSqlOwnedValue>( - &self, - stmt: &str, - params: &[&'a (dyn QueryParameter<'a>)], - ) -> impl Future>> + Send { - postgres_query_launcher::query_one_for(stmt, params, self) - } - - fn execute<'a>( - &self, - stmt: &str, - params: &[&'a (dyn QueryParameter<'a>)], - ) -> impl Future>> + Send { - postgres_query_launcher::execute(stmt, params, self) - } - - fn get_database_type(&self) -> Result> { - Ok(DatabaseType::PostgreSql) - } -} - #[cfg(feature = "postgres")] pub(crate) mod postgres_query_launcher { diff --git a/canyon_core/src/connection/contracts/impl/database_connection.rs b/canyon_core/src/connection/contracts/impl/database_connection.rs new file mode 100644 index 00000000..bd12abcd --- /dev/null +++ b/canyon_core/src/connection/contracts/impl/database_connection.rs @@ -0,0 +1,213 @@ +use crate::{ + connection::{ + contracts::DbConnection, database_type::DatabaseType, db_connector::DatabaseConnection, + }, + mapper::RowMapper, + query_parameters::QueryParameter, + rows::{CanyonRows, FromSqlOwnedValue}, +}; +use std::{error::Error, fmt::Display}; + +impl DbConnection for DatabaseConnection { + async fn query_rows<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + db_conn_query_rows_impl(self, stmt, params).await + } + + async fn query<'a, S, R>( + &self, + stmt: S, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> Result, Box<(dyn Error + Send + Sync)>> + where + S: AsRef + Display + Send, + R: RowMapper, + Vec: FromIterator<::Output>, + { + db_conn_query_impl(self, stmt, params).await + } + + async fn query_one<'a, R>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result, Box<(dyn Error + Send + Sync)>> + where + R: RowMapper, + { + db_conn_query_one_impl::(self, stmt, params).await + } + + async fn query_one_for<'a, T: FromSqlOwnedValue>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + db_conn_query_one_for_impl::(self, stmt, params).await + } + + async fn execute<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + db_conn_execute_impl(self, stmt, params).await + } + + fn get_database_type(&self) -> Result> { + Ok(self.get_db_type()) + } +} + +impl DbConnection for &mut DatabaseConnection { + async fn query_rows<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + db_conn_query_rows_impl(self, stmt, params).await + } + + async fn query<'a, S, R>( + &self, + stmt: S, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> Result, Box<(dyn Error + Send + Sync)>> + where + S: AsRef + Display + Send, + R: RowMapper, + Vec: FromIterator<::Output>, + { + db_conn_query_impl(self, stmt, params).await + } + + async fn query_one<'a, R>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result, Box<(dyn Error + Send + Sync)>> + where + R: RowMapper, + { + db_conn_query_one_impl::(self, stmt, params).await + } + + async fn query_one_for<'a, T: FromSqlOwnedValue>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + db_conn_query_one_for_impl::(self, stmt, params).await + } + + async fn execute<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + db_conn_execute_impl(self, stmt, params).await + } + + fn get_database_type(&self) -> Result> { + Ok(self.get_db_type()) + } +} + +pub(crate) async fn db_conn_query_rows_impl<'a>( + c: &DatabaseConnection, + stmt: &str, + params: &[&'a (dyn QueryParameter<'a> + 'a)], +) -> Result> { + match c { + #[cfg(feature = "postgres")] + DatabaseConnection::Postgres(client) => client.query_rows(stmt, params).await, + + #[cfg(feature = "mssql")] + DatabaseConnection::SqlServer(client) => client.query_rows(stmt, params).await, + + #[cfg(feature = "mysql")] + DatabaseConnection::MySQL(client) => client.query_rows(stmt, params).await, + } +} + +pub(crate) async fn db_conn_query_one_impl<'a, R>( + c: &DatabaseConnection, + stmt: &str, + params: &[&'a (dyn QueryParameter<'a> + 'a)], +) -> Result, Box> +where + R: RowMapper, +{ + match c { + #[cfg(feature = "postgres")] + DatabaseConnection::Postgres(client) => client.query_one::(stmt, params).await, + + #[cfg(feature = "mssql")] + DatabaseConnection::SqlServer(client) => client.query_one::(stmt, params).await, + + #[cfg(feature = "mysql")] + DatabaseConnection::MySQL(client) => client.query_one::(stmt, params).await, + } +} + +pub(crate) async fn db_conn_query_impl<'a, S, R>( + c: &DatabaseConnection, + stmt: S, + params: &[&'a (dyn QueryParameter<'a>)], +) -> Result, Box<(dyn Error + Send + Sync)>> +where + S: AsRef + Display + Send, + R: RowMapper, + Vec: FromIterator<::Output>, +{ + match c { + #[cfg(feature = "postgres")] + DatabaseConnection::Postgres(client) => client.query(stmt, params).await, + + #[cfg(feature = "mssql")] + DatabaseConnection::SqlServer(client) => client.query(stmt, params).await, + + #[cfg(feature = "mysql")] + DatabaseConnection::MySQL(client) => client.query(stmt, params).await, + } +} + +pub(crate) async fn db_conn_query_one_for_impl<'a, T>( + c: &DatabaseConnection, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], +) -> Result> +where + T: FromSqlOwnedValue, +{ + match c { + #[cfg(feature = "postgres")] + DatabaseConnection::Postgres(client) => client.query_one_for(stmt, params).await, + + #[cfg(feature = "mssql")] + DatabaseConnection::SqlServer(client) => client.query_one_for(stmt, params).await, + + #[cfg(feature = "mysql")] + DatabaseConnection::MySQL(client) => client.query_one_for(stmt, params).await, + } +} + +pub(crate) async fn db_conn_execute_impl<'a>( + c: &DatabaseConnection, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], +) -> Result> { + match c { + #[cfg(feature = "postgres")] + DatabaseConnection::Postgres(client) => client.execute(stmt, params).await, + + #[cfg(feature = "mssql")] + DatabaseConnection::SqlServer(client) => client.execute(stmt, params).await, + + #[cfg(feature = "mysql")] + DatabaseConnection::MySQL(client) => client.execute(stmt, params).await, + } +} diff --git a/canyon_core/src/connection/contracts/impl/mod.rs b/canyon_core/src/connection/contracts/impl/mod.rs new file mode 100644 index 00000000..44c4d2ee --- /dev/null +++ b/canyon_core/src/connection/contracts/impl/mod.rs @@ -0,0 +1,12 @@ +pub mod database_connection; + +pub mod mssql; +pub mod mysql; +pub mod postgresql; + +#[macro_use] +pub mod str; + +// Apply the macro to implement DbConnection for &str and str +impl_db_connection!(str); +impl_db_connection!(&str); diff --git a/canyon_core/src/connection/contracts/impl/mssql.rs b/canyon_core/src/connection/contracts/impl/mssql.rs new file mode 100644 index 00000000..e77386b1 --- /dev/null +++ b/canyon_core/src/connection/contracts/impl/mssql.rs @@ -0,0 +1,65 @@ +use crate::{ + connection::{ + clients::mssql::{sqlserver_query_launcher, SqlServerConnection}, + contracts::DbConnection, + database_type::DatabaseType, + }, + mapper::RowMapper, + query_parameters::QueryParameter, + rows::{CanyonRows, FromSqlOwnedValue}, +}; +use std::{error::Error, fmt::Display, future::Future}; + +impl DbConnection for SqlServerConnection { + fn query_rows<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> impl Future>> + Send { + sqlserver_query_launcher::query_rows(stmt, params, self) + } + + fn query<'a, S, R>( + &self, + stmt: S, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send + where + S: AsRef + Display + Send, + R: RowMapper, + Vec: FromIterator<::Output>, + { + sqlserver_query_launcher::query(stmt, params, self) + } + + fn query_one<'a, R>( + &self, + stmt: &str, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send + where + R: RowMapper, + { + sqlserver_query_launcher::query_one::(stmt, params, self) + } + + fn query_one_for<'a, T: FromSqlOwnedValue>( + &self, + stmt: &str, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> impl Future>> + Send { + sqlserver_query_launcher::query_one_for(stmt, params, self) + } + + fn execute<'a>( + &self, + stmt: &str, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> impl Future>> + Send { + sqlserver_query_launcher::execute(stmt, params, self) + } + + fn get_database_type(&self) -> Result> { + Ok(DatabaseType::SqlServer) + } +} diff --git a/canyon_core/src/connection/contracts/impl/mysql.rs b/canyon_core/src/connection/contracts/impl/mysql.rs new file mode 100644 index 00000000..0f469001 --- /dev/null +++ b/canyon_core/src/connection/contracts/impl/mysql.rs @@ -0,0 +1,64 @@ +use crate::connection::clients::mysql::mysql_query_launcher; +use crate::{ + connection::{ + clients::mysql::MysqlConnection, contracts::DbConnection, database_type::DatabaseType, + }, + mapper::RowMapper, + query_parameters::QueryParameter, + rows::{CanyonRows, FromSqlOwnedValue}, +}; +use std::{error::Error, fmt::Display, future::Future}; + +impl DbConnection for MysqlConnection { + fn query_rows<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> impl Future>> + Send { + mysql_query_launcher::query_rows(stmt, params, self) + } + + fn query<'a, S, R>( + &self, + stmt: S, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send + where + S: AsRef + Display + Send, + R: RowMapper, + Vec: FromIterator<::Output>, + { + mysql_query_launcher::query(stmt, params, self) + } + + fn query_one<'a, R>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send + where + R: RowMapper, + { + mysql_query_launcher::query_one::(stmt, params, self) + } + + fn query_one_for<'a, T: FromSqlOwnedValue>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> impl Future>> + Send { + mysql_query_launcher::query_one_for(stmt, params, self) + } + + fn execute<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> impl Future>> + Send { + mysql_query_launcher::execute(stmt, params, self) + } + + fn get_database_type(&self) -> Result> { + Ok(DatabaseType::MySQL) + } +} diff --git a/canyon_core/src/connection/contracts/impl/postgresql.rs b/canyon_core/src/connection/contracts/impl/postgresql.rs new file mode 100644 index 00000000..158a95c4 --- /dev/null +++ b/canyon_core/src/connection/contracts/impl/postgresql.rs @@ -0,0 +1,65 @@ +use crate::connection::clients::postgresql::PostgreSqlConnection; +use crate::{ + connection::{ + clients::postgresql::postgres_query_launcher, contracts::DbConnection, + database_type::DatabaseType, + }, + mapper::RowMapper, + query_parameters::QueryParameter, + rows::{CanyonRows, FromSqlOwnedValue}, +}; +use std::{error::Error, fmt::Display, future::Future}; + +impl DbConnection for PostgreSqlConnection { + fn query_rows<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> impl Future>> + Send { + postgres_query_launcher::query_rows(stmt, params, self) + } + + fn query<'a, S, R>( + &self, + stmt: S, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send + where + S: AsRef + Display + Send, + R: RowMapper, + Vec: FromIterator<::Output>, + { + postgres_query_launcher::query(stmt, params, self) + } + + fn query_one<'a, R>( + &self, + stmt: &str, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send + where + R: RowMapper, + { + postgres_query_launcher::query_one::(stmt, params, self) + } + + fn query_one_for<'a, T: FromSqlOwnedValue>( + &self, + stmt: &str, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> impl Future>> + Send { + postgres_query_launcher::query_one_for(stmt, params, self) + } + + fn execute<'a>( + &self, + stmt: &str, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> impl Future>> + Send { + postgres_query_launcher::execute(stmt, params, self) + } + + fn get_database_type(&self) -> Result> { + Ok(DatabaseType::PostgreSql) + } +} diff --git a/canyon_core/src/connection/contracts/impl/str.rs b/canyon_core/src/connection/contracts/impl/str.rs new file mode 100644 index 00000000..ee2fabcc --- /dev/null +++ b/canyon_core/src/connection/contracts/impl/str.rs @@ -0,0 +1,81 @@ +//! This module contains the implementation of the `DbConnection` trait for the `&str` type. + +macro_rules! impl_db_connection { + ($type:ty) => { + impl crate::connection::contracts::DbConnection for $type { + async fn query_rows<'a>( + &self, + stmt: &str, + params: &[&'a dyn crate::query_parameters::QueryParameter<'a>], + ) -> Result> { + let conn = crate::connection::Canyon::instance()? + .get_connection(self) + .await?; + conn.query_rows(stmt, params).await + } + + async fn query<'a, S, R>( + &self, + stmt: S, + params: &[&'a (dyn crate::query_parameters::QueryParameter<'a>)], + ) -> Result, Box<(dyn std::error::Error + Send + Sync)>> + where + S: AsRef + std::fmt::Display + Send, + R: crate::mapper::RowMapper, + Vec: std::iter::FromIterator<::Output>, + { + let conn = crate::connection::Canyon::instance()? + .get_connection(self) + .await?; + conn.query(stmt, params).await + } + + async fn query_one<'a, R>( + &self, + stmt: &str, + params: &[&'a dyn crate::query_parameters::QueryParameter<'a>], + ) -> Result, Box<(dyn std::error::Error + Send + Sync)>> + where + R: crate::mapper::RowMapper, + { + let conn = crate::connection::Canyon::instance()? + .get_connection(self) + .await?; + conn.query_one::(stmt, params).await + } + + async fn query_one_for<'a, T: crate::rows::FromSqlOwnedValue>( + &self, + stmt: &str, + params: &[&'a dyn crate::query_parameters::QueryParameter<'a>], + ) -> Result> { + let conn = crate::connection::Canyon::instance()? + .get_connection(self) + .await?; + conn.query_one_for(stmt, params).await + } + + async fn execute<'a>( + &self, + stmt: &str, + params: &[&'a dyn crate::query_parameters::QueryParameter<'a>], + ) -> Result> { + let conn = crate::connection::Canyon::instance()? + .get_connection(self) + .await?; + conn.execute(stmt, params).await + } + + fn get_database_type( + &self, + ) -> Result< + crate::connection::database_type::DatabaseType, + Box<(dyn std::error::Error + Send + Sync)>, + > { + Ok(crate::connection::Canyon::instance()? + .find_datasource_by_name_or_default(self)? + .get_db_type()) + } + } + }; +} diff --git a/canyon_core/src/connection/contracts/mod.rs b/canyon_core/src/connection/contracts/mod.rs new file mode 100644 index 00000000..c2e20b5c --- /dev/null +++ b/canyon_core/src/connection/contracts/mod.rs @@ -0,0 +1,121 @@ +use crate::connection::database_type::DatabaseType; +use crate::mapper::RowMapper; +use crate::query_parameters::QueryParameter; +use crate::rows::{CanyonRows, FromSqlOwnedValue}; +use std::error::Error; +use std::fmt::Display; +use std::future::Future; + +mod r#impl; // contains the implementation details for the trait + +/// The `DbConnection` trait defines the core functionality required for interacting with a database connection. +/// It provides methods for executing queries, retrieving rows, and obtaining metadata about the database type. +/// +/// This trait is designed to be implemented by various database connection types, enabling a unified interface +/// for database operations. Each method is asynchronous and returns a `Future` to support non-blocking operations. +/// +/// # Examples +/// +/// ```rust +/// use crate::connection::DbConnection; +/// +/// async fn execute_query(conn: &C) { +/// let result = conn.execute("INSERT INTO users (name) VALUES ($1)", &[&"John"]).await; +/// match result { +/// Ok(rows_affected) => println!("Rows affected: {}", rows_affected), +/// Err(e) => eprintln!("Error executing query: {}", e), +/// } +/// } +/// ``` +/// +/// # Required Methods +/// Each method in this trait must be implemented by the implementor. +pub trait DbConnection { + /// Executes a query and retrieves multiple rows from the database. + /// + /// # Arguments + /// * `stmt` - A SQL statement to execute. + /// * `params` - A slice of query parameters to bind to the statement. + /// + /// # Returns + /// A `Future` that resolves to a `Result` containing [`CanyonRows`](crate::rows::CanyonRows) on success or an error on failure. + fn query_rows<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> impl Future>> + Send; + + /// Executes a query and maps the result to a collection of rows of type `R`. + /// + /// # Arguments + /// * `stmt` - A SQL statement to execute. + /// * `params` - A slice of query parameters to bind to the statement. + /// + /// # Returns + /// A `Future` that resolves to a `Result` containing a `Vec` on success or an error on failure. + /// + /// The `R` type must implement the [`RowMapper`](crate::mapper::RowMapper) trait. + fn query<'a, S, R>( + &self, + stmt: S, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send + where + S: AsRef + Display + Send, + R: RowMapper, + Vec: FromIterator<::Output>; + + /// Executes a query and retrieves a single row mapped to type `R`. + /// + /// # Arguments + /// * `stmt` - A SQL statement to execute. + /// * `params` - A slice of query parameters to bind to the statement. + /// + /// # Returns + /// A `Future` that resolves to a `Result` containing an `Option` on success or an error on failure. + /// + /// The `R` type must implement the [`RowMapper`](crate::mapper::RowMapper) trait. + fn query_one<'a, R>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send + where + R: RowMapper; + + /// Executes a query and retrieves a single value of type `T`. + /// + /// # Arguments + /// * `stmt` - A SQL statement to execute. + /// * `params` - A slice of query parameters to bind to the statement. + /// + /// # Returns + /// A `Future` that resolves to a `Result` containing the value of type `T` on success or an error on failure. + /// + /// The `T` type must implement the [`FromSqlOwnedValue`](crate::rows::FromSqlOwnedValue) trait. + fn query_one_for<'a, T: FromSqlOwnedValue>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> impl Future>> + Send; + + /// Executes a SQL statement and returns the number of affected rows. + /// + /// # Arguments + /// * `stmt` - A SQL statement to execute. + /// * `params` - A slice of query parameters to bind to the statement. + /// + /// # Returns + /// A `Future` that resolves to a `Result` containing the number of affected rows on success or an error on failure. + fn execute<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> impl Future>> + Send; + + /// Retrieves the type of the database associated with the connection. + /// + /// # Returns + /// A `Result` containing the [`DatabaseType`](crate::connection::database_type::DatabaseType) on success or an error on failure. + fn get_database_type(&self) -> Result>; +} diff --git a/canyon_core/src/connection/datasources.rs b/canyon_core/src/connection/datasources.rs index 13de6e14..a3fca6a7 100644 --- a/canyon_core/src/connection/datasources.rs +++ b/canyon_core/src/connection/datasources.rs @@ -1,3 +1,9 @@ +//! The datasources module of Canyon-SQL. +//! +//! This module defines the configuration and authentication mechanisms for database datasources. +//! It includes support for multiple database backends and provides utilities for managing +//! datasource properties. + use serde::Deserialize; use super::database_type::DatabaseType; diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index 9dfef6e5..be7f6942 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -1,138 +1,12 @@ -use crate::connection::database_type::DatabaseType; -use crate::connection::datasources::DatasourceConfig; #[cfg(feature = "mssql")] -use crate::connection::db_clients::mssql::SqlServerConnection; +use crate::connection::clients::mssql::SqlServerConnection; #[cfg(feature = "mysql")] -use crate::connection::db_clients::mysql::MysqlConnection; +use crate::connection::clients::mysql::MysqlConnection; #[cfg(feature = "postgres")] -use crate::connection::db_clients::postgresql::PostgreSqlConnection; -use crate::connection::db_connector::connection_helpers::{ - db_conn_query_one_impl, db_conn_query_rows_impl, -}; -use crate::connection::Canyon; -use crate::mapper::RowMapper; -use crate::query_parameters::QueryParameter; -use crate::rows::{CanyonRows, FromSqlOwnedValue}; +use crate::connection::clients::postgresql::PostgreSqlConnection; +use crate::connection::database_type::DatabaseType; +use crate::connection::datasources::DatasourceConfig; use std::error::Error; -use std::fmt::Display; -use std::future::Future; - -pub trait DbConnection { - fn query_rows<'a>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send; - - fn query<'a, S, R>( - &self, - stmt: S, - params: &[&'a (dyn QueryParameter<'a>)], - ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send - where - S: AsRef + Display + Send, - R: RowMapper, - Vec: FromIterator<::Output>; - - fn query_one<'a, R>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send - where - R: RowMapper; - - /// Flexible and general method that queries the target database for a concrete instance - /// of some type T. - /// - /// This is useful on statements that won't be mapped to user types (impl RowMapper) but - /// there's a need for more flexibility on the return type. Ex: SELECT COUNT(*) from , - /// where there will be a single result of some numerical type - fn query_one_for<'a, T: FromSqlOwnedValue>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send; - - /// Executes the given SQL statement against the target database, being any implementor of self, - /// returning only a numerical positive integer number reflecting the number of affected rows - fn execute<'a>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> impl Future>> + Send; - - fn get_database_type(&self) -> Result>; -} - -macro_rules! impl_db_connection { - ($type:ty) => { - impl DbConnection for $type { - async fn query_rows<'a>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - let conn = Canyon::instance()?.get_connection(self).await?; - conn.query_rows(stmt, params).await - } - - async fn query<'a, S, R>( - &self, - stmt: S, - params: &[&'a (dyn QueryParameter<'a>)], - ) -> Result, Box<(dyn Error + Send + Sync)>> - where - S: AsRef + Display + Send, - R: RowMapper, - Vec: FromIterator<::Output>, - { - let conn = Canyon::instance()?.get_connection(self).await?; - conn.query(stmt, params).await - } - - async fn query_one<'a, R>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result, Box<(dyn Error + Send + Sync)>> - where - R: RowMapper, - { - let conn = Canyon::instance()?.get_connection(self).await?; - conn.query_one::(stmt, params).await - } - - async fn query_one_for<'a, T: FromSqlOwnedValue>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - let conn = Canyon::instance()?.get_connection(self).await?; - conn.query_one_for(stmt, params).await - } - - async fn execute<'a>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - let conn = Canyon::instance()?.get_connection(self).await?; - conn.execute(stmt, params).await - } - - fn get_database_type(&self) -> Result> { - Ok(Canyon::instance()? - .find_datasource_by_name_or_default(self)? - .get_db_type()) - } - } - }; -} - -// Apply the macro to implement DbConnection for &str and str -impl_db_connection!(str); -impl_db_connection!(&str); /// The Canyon database connection handler. When the client's program /// starts, Canyon gets the information about the desired datasources, @@ -148,166 +22,6 @@ pub enum DatabaseConnection { MySQL(MysqlConnection), } -impl DbConnection for DatabaseConnection { - async fn query_rows<'a>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - db_conn_query_rows_impl(self, stmt, params).await - } - - async fn query<'a, S, R>( - &self, - stmt: S, - params: &[&'a (dyn QueryParameter<'a>)], - ) -> Result, Box<(dyn Error + Send + Sync)>> - where - S: AsRef + Display + Send, - R: RowMapper, - Vec: FromIterator<::Output>, - { - match self { - #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(client) => client.query(stmt, params).await, - - #[cfg(feature = "mssql")] - DatabaseConnection::SqlServer(client) => client.query(stmt, params).await, - - #[cfg(feature = "mysql")] - DatabaseConnection::MySQL(client) => client.query(stmt, params).await, - } - } - - async fn query_one<'a, R>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result, Box<(dyn Error + Send + Sync)>> - where - R: RowMapper, - { - db_conn_query_one_impl::(self, stmt, params).await - } - - async fn query_one_for<'a, T: FromSqlOwnedValue>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - match self { - #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(client) => client.query_one_for(stmt, params).await, - - #[cfg(feature = "mssql")] - DatabaseConnection::SqlServer(client) => client.query_one_for(stmt, params).await, - - #[cfg(feature = "mysql")] - DatabaseConnection::MySQL(client) => client.query_one_for(stmt, params).await, - } - } - async fn execute<'a>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - match self { - #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(client) => client.execute(stmt, params).await, - - #[cfg(feature = "mssql")] - DatabaseConnection::SqlServer(client) => client.execute(stmt, params).await, - - #[cfg(feature = "mysql")] - DatabaseConnection::MySQL(client) => client.execute(stmt, params).await, - } - } - - fn get_database_type(&self) -> Result> { - Ok(self.get_db_type()) - } -} - -impl DbConnection for &mut DatabaseConnection { - async fn query_rows<'a>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - db_conn_query_rows_impl(self, stmt, params).await - } - async fn query<'a, S, R>( - &self, - stmt: S, - params: &[&'a (dyn QueryParameter<'a>)], - ) -> Result, Box<(dyn Error + Send + Sync)>> - where - S: AsRef + Display + Send, - R: RowMapper, - Vec: FromIterator<::Output>, - { - match self { - #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(client) => client.query(stmt, params).await, - - #[cfg(feature = "mssql")] - DatabaseConnection::SqlServer(client) => client.query(stmt, params).await, - - #[cfg(feature = "mysql")] - DatabaseConnection::MySQL(client) => client.query(stmt, params).await, - } - } - - async fn query_one<'a, R>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result, Box<(dyn Error + Send + Sync)>> - where - R: RowMapper, - { - db_conn_query_one_impl::(self, stmt, params).await - } - - async fn query_one_for<'a, T: FromSqlOwnedValue>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - match self { - #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(client) => client.query_one_for(stmt, params).await, - - #[cfg(feature = "mssql")] - DatabaseConnection::SqlServer(client) => client.query_one_for(stmt, params).await, - - #[cfg(feature = "mysql")] - DatabaseConnection::MySQL(client) => client.query_one_for(stmt, params).await, - } - } - - async fn execute<'a>( - &self, - stmt: &str, - params: &[&'a dyn QueryParameter<'a>], - ) -> Result> { - match self { - #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(client) => client.execute(stmt, params).await, - - #[cfg(feature = "mssql")] - DatabaseConnection::SqlServer(client) => client.execute(stmt, params).await, - - #[cfg(feature = "mysql")] - DatabaseConnection::MySQL(client) => client.execute(stmt, params).await, - } - } - - fn get_database_type(&self) -> Result> { - Ok(self.get_db_type()) - } -} - unsafe impl Send for DatabaseConnection {} unsafe impl Sync for DatabaseConnection {} @@ -455,43 +169,6 @@ mod connection_helpers { db = datasource.properties.db_name ) } - - pub(crate) async fn db_conn_query_rows_impl<'a>( - c: &DatabaseConnection, - stmt: &str, - params: &[&'a (dyn QueryParameter<'a> + 'a)], - ) -> Result> { - match c { - #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(client) => client.query_rows(stmt, params).await, - - #[cfg(feature = "mssql")] - DatabaseConnection::SqlServer(client) => client.query_rows(stmt, params).await, - - #[cfg(feature = "mysql")] - DatabaseConnection::MySQL(client) => client.query_rows(stmt, params).await, - } - } - - pub(crate) async fn db_conn_query_one_impl<'a, R>( - c: &DatabaseConnection, - stmt: &str, - params: &[&'a (dyn QueryParameter<'a> + 'a)], - ) -> Result, Box> - where - R: RowMapper, - { - match c { - #[cfg(feature = "postgres")] - DatabaseConnection::Postgres(client) => client.query_one::(stmt, params).await, - - #[cfg(feature = "mssql")] - DatabaseConnection::SqlServer(client) => client.query_one::(stmt, params).await, - - #[cfg(feature = "mysql")] - DatabaseConnection::MySQL(client) => client.query_one::(stmt, params).await, - } - } } mod auth { diff --git a/canyon_core/src/connection/mod.rs b/canyon_core/src/connection/mod.rs index 5e99c13b..d58bf6b5 100644 --- a/canyon_core/src/connection/mod.rs +++ b/canyon_core/src/connection/mod.rs @@ -1,3 +1,8 @@ +//! The connection module of Canyon-SQL. +//! +//! This module handles database connections, including connection pooling and configuration. +//! It provides abstractions for managing multiple datasources and supports asynchronous operations. + #[cfg(feature = "postgres")] pub extern crate tokio_postgres; @@ -13,23 +18,16 @@ pub extern crate futures; pub extern crate tokio; pub extern crate tokio_util; +pub mod clients; pub mod conn_errors; +pub mod contracts; pub mod database_type; pub mod datasources; -pub mod db_clients; pub mod db_connector; -use crate::connection::datasources::Datasources; -use conn_errors::DatasourceNotFound; -use datasources::{CanyonSqlConfig, DatasourceConfig}; -use db_connector::DatabaseConnection; -use std::collections::HashMap; -use std::path::PathBuf; -use std::sync::{Arc, OnceLock}; -use std::{error::Error, fs}; +use crate::canyon::Canyon; +use std::sync::OnceLock; use tokio::runtime::Runtime; -use tokio::sync::Mutex; -use walkdir::WalkDir; // // // TODO's: DatabaseConnection and DataSource can implement default, so there's no need to use str and &str @@ -38,7 +36,7 @@ use walkdir::WalkDir; // // // TODO: Crud Operations should be split into two different derives, splitting the automagic from the _with ones -// TODO: move Canyon struct to core +pub(crate) static CANYON_INSTANCE: OnceLock = OnceLock::new(); // Use OnceLock for the Tokio runtime static CANYON_TOKIO_RUNTIME: OnceLock = OnceLock::new(); @@ -48,136 +46,3 @@ pub fn get_canyon_tokio_runtime() -> &'static Runtime { CANYON_TOKIO_RUNTIME .get_or_init(|| Runtime::new().expect("Failed initializing the Canyon-SQL Tokio Runtime")) } - -pub type SharedConnection = Arc>; - -pub struct Canyon { - config: Datasources, - connections: HashMap<&'static str, SharedConnection>, - default: Option, -} - -static CANYON_INSTANCE: OnceLock = OnceLock::new(); - -impl Canyon { - // Singleton access - pub fn instance() -> Result<&'static Self, Box> { - Ok(CANYON_INSTANCE.get().ok_or_else(|| { - Box::new(std::io::Error::new( - std::io::ErrorKind::Other, - "Canyon not initialized. Call `Canyon::init()` first.", - )) - })?) - } - - // Initializes Canyon instance - pub async fn init() -> Result<&'static Self, Box> { - if CANYON_INSTANCE.get().is_some() { - return Canyon::instance(); // Already initialized, no need to do it again - } - - let path = Canyon::find_config_path()?; - let config_content = fs::read_to_string(&path)?; - let config: Datasources = toml::from_str::(&config_content)?.canyon_sql; - - let mut connections = HashMap::new(); - let mut default = None; - - for ds in config.datasources.iter() { - let conn = DatabaseConnection::new(ds).await?; - let name: &'static str = Box::leak(ds.name.clone().into_boxed_str()); - let conn = Arc::new(Mutex::new(conn)); - - if default.is_none() { - default = Some(conn.clone()); // Only cloning the smart pointer - } - - connections.insert(name, conn); - } - - let canyon = Canyon { - config, - connections, - default, - }; - - get_canyon_tokio_runtime(); // Just ensuring that is initialized in manual-mode - Ok(CANYON_INSTANCE.get_or_init(|| canyon)) - } - - // Internal helper to locate the config file - fn find_config_path() -> Result { - WalkDir::new(".") - .max_depth(2) - .into_iter() - .filter_map(Result::ok) - .find_map(|e| { - let filename = e.file_name().to_string_lossy().to_lowercase(); - if e.metadata().ok()?.is_file() - && filename.starts_with("canyon") - && filename.ends_with(".toml") - { - Some(e.path().to_path_buf()) - } else { - None - } - }) - .ok_or_else(|| { - std::io::Error::new(std::io::ErrorKind::NotFound, "No Canyon config found") - }) - } - - // Public accessor for datasources - pub fn datasources(&self) -> &[DatasourceConfig] { - &self.config.datasources - } - - // Retrieve a datasource by name or default to the first - pub fn find_datasource_by_name_or_default( - &self, - name: &str, - ) -> Result<&DatasourceConfig, DatasourceNotFound> { - if name.is_empty() { - self.config - .datasources - .first() - .ok_or_else(|| DatasourceNotFound::from(None)) - } else { - self.config - .datasources - .iter() - .find(|ds| ds.name == name) - .ok_or_else(|| DatasourceNotFound::from(Some(name))) - } - } - - // Retrieve a read-only connection from the cache - pub async fn get_connection( - &self, - name: &str, - ) -> Result, DatasourceNotFound> { - if name.is_empty() { - return Ok(self - .default - .as_ref() - .ok_or_else(|| DatasourceNotFound::from(None))? - .lock() - .await); - } - - let conn = self - .connections - .get(name) - .ok_or_else(|| DatasourceNotFound::from(Some(name)))?; - - Ok(conn.lock().await) - } - - // Retrieve a mutable connection from the cache - pub async fn get_mut_connection( - &self, - name: &str, - ) -> Result, DatasourceNotFound> { - self.get_connection(name).await - } -} diff --git a/canyon_core/src/connection/types/mod.rs b/canyon_core/src/connection/types/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/canyon_core/src/lib.rs b/canyon_core/src/lib.rs index f4ffda29..c705c910 100644 --- a/canyon_core/src/lib.rs +++ b/canyon_core/src/lib.rs @@ -1,3 +1,9 @@ +//! The core module of Canyon-SQL. +//! +//! This module provides the foundational components for database connections, query execution, +//! and data mapping. It includes support for multiple database backends such as PostgreSQL, +//! MySQL, and SQL Server, and defines traits and utilities for interacting with these databases. + #[cfg(feature = "postgres")] pub extern crate tokio_postgres; @@ -11,6 +17,8 @@ pub extern crate mysql_async; extern crate core; +pub mod canyon; + pub mod column; pub mod connection; pub mod mapper; diff --git a/canyon_core/src/mapper.rs b/canyon_core/src/mapper.rs index 925e7522..4e999485 100644 --- a/canyon_core/src/mapper.rs +++ b/canyon_core/src/mapper.rs @@ -1,3 +1,8 @@ +//! The mapper module of Canyon-SQL. +//! +//! This module defines traits and utilities for mapping database query results to user-defined +//! types. It includes the `RowMapper` trait and related functionality for deserialization. + /// Declares functions that takes care to deserialize data incoming /// from some supported database in Canyon-SQL into a user's defined /// type `T` diff --git a/canyon_core/src/rows.rs b/canyon_core/src/rows.rs index c8947dab..75430d05 100644 --- a/canyon_core/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -1,3 +1,8 @@ +//! The rows module of Canyon-SQL. +//! +//! This module defines the `CanyonRows` enum, which wraps database query results for supported +//! databases. It also provides traits and utilities for mapping rows to user-defined types. + #[cfg(feature = "mysql")] use mysql_async::{self}; #[cfg(feature = "mssql")] diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index 3a37b5bd..43c990e6 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -1,10 +1,48 @@ -use crate::connection::db_connector::DbConnection; +use crate::connection::contracts::DbConnection; use crate::mapper::RowMapper; use crate::rows::FromSqlOwnedValue; use crate::{query_parameters::QueryParameter, rows::CanyonRows}; use std::error::Error; use std::{fmt::Display, future::Future}; +/// The `Transaction` trait serves as a proxy for types implementing CRUD operations. +/// +/// This trait provides a set of static methods that mirror the functionality of CRUD operations, +/// allowing implementors to be coerced into `<#ty as Transaction>::...` usage patterns. +/// It is primarily used by the generated macros of `CrudOperations` to simplify interaction +/// with database entities by abstracting common operations such as querying rows, executing +/// statements, and retrieving single results. +/// +/// # Purpose +/// The `Transaction` trait is typically used to provide a unified interface for CRUD operations +/// on database entities. It enables developers to work with any type that implements the required +/// CRUD traits, abstracting away the underlying database connection details. +/// +/// # Features +/// - Acts as a proxy for CRUD operations. +/// - Provides static methods for common database entity operations. +/// - Simplifies interaction with database entities. +/// +/// # Examples +/// ```rust +/// use canyon_core::transaction::Transaction; +/// use canyon_core::crud::CrudOperations; +/// +/// async fn perform_query(entity: E) { +/// let result = ::query("SELECT * FROM users", &[], entity).await; +/// match result { +/// Ok(rows) => println!("Retrieved {} rows", rows.len()), +/// Err(e) => eprintln!("Error: {}", e), +/// } +/// } +/// ``` +/// +/// # Methods +/// - `query`: Executes a query and retrieves multiple rows mapped to a user-defined type. +/// - `query_one`: Executes a query and retrieves a single row mapped to a user-defined type. +/// - `query_one_for`: Executes a query and retrieves a single value of a specific type. +/// - `query_rows`: Executes a query and retrieves the raw rows wrapped in `CanyonRows`. +/// - `execute`: Executes a SQL statement and returns the number of affected rows. pub trait Transaction { fn query<'a, S, R>( stmt: S, diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 703ee813..f86d3389 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -1,7 +1,7 @@ use crate::query_elements::query_builder::{ DeleteQueryBuilder, SelectQueryBuilder, UpdateQueryBuilder, }; -use canyon_core::connection::db_connector::DbConnection; +use canyon_core::connection::contracts::DbConnection; use canyon_core::mapper::RowMapper; use canyon_core::query_parameters::QueryParameter; use std::error::Error; diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs index 73781f6b..29af22c8 100644 --- a/canyon_crud/src/query_elements/query_builder.rs +++ b/canyon_crud/src/query_elements/query_builder.rs @@ -2,8 +2,8 @@ use crate::{ bounds::{FieldIdentifier, FieldValueIdentifier}, Operator, }; +use canyon_core::connection::contracts::DbConnection; use canyon_core::connection::database_type::DatabaseType; -use canyon_core::connection::db_connector::DbConnection; use canyon_core::{mapper::RowMapper, query_parameters::QueryParameter}; use std::error::Error; use std::marker::PhantomData; diff --git a/canyon_macros/src/canyon_macro.rs b/canyon_macros/src/canyon_macro.rs index 9feb5334..b005e10a 100644 --- a/canyon_macros/src/canyon_macro.rs +++ b/canyon_macros/src/canyon_macro.rs @@ -10,7 +10,7 @@ use quote::quote; pub fn main_with_queries() -> TokenStream { // TODO: migrations on main instead of main_with_queries get_canyon_tokio_runtime().block_on(async { - canyon_core::connection::Canyon::init() + canyon_core::canyon::Canyon::init() .await .expect("Error initializing the connections POOL"); Migrations::migrate().await; diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index b8d7ab5a..2c924fb8 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -7,7 +7,7 @@ use crate::{ processor::MigrationsProcessor, }, }; -use canyon_core::connection::Canyon; +use canyon_core::canyon::Canyon; use canyon_core::{ column::Column, connection::db_connector::DatabaseConnection, diff --git a/canyon_migrations/src/migrations/memory.rs b/canyon_migrations/src/migrations/memory.rs index c4772bd1..38d7c14f 100644 --- a/canyon_migrations/src/migrations/memory.rs +++ b/canyon_migrations/src/migrations/memory.rs @@ -1,5 +1,7 @@ use crate::constants; -use canyon_core::connection::db_connector::{DatabaseConnection, DbConnection}; +use canyon_core::canyon::Canyon; +use canyon_core::connection::contracts::DbConnection; +use canyon_core::connection::db_connector::DatabaseConnection; use canyon_core::transaction::Transaction; use canyon_crud::{DatabaseType, DatasourceConfig}; use regex::Regex; @@ -65,7 +67,7 @@ impl CanyonMemory { datasource: &DatasourceConfig, canyon_entities: &[CanyonRegisterEntity<'_>], ) -> Self { - let mut db_conn = canyon_core::connection::Canyon::instance() + let mut db_conn = Canyon::instance() .unwrap_or_else(|_| { panic!( "Failure getting db connection: {} on Canyon Memory", diff --git a/canyon_migrations/src/migrations/processor.rs b/canyon_migrations/src/migrations/processor.rs index ea41bce4..0ee224c1 100644 --- a/canyon_migrations/src/migrations/processor.rs +++ b/canyon_migrations/src/migrations/processor.rs @@ -3,7 +3,8 @@ use crate::canyon_crud::DatasourceConfig; use crate::constants::regex_patterns; use crate::save_migrations_query_to_execute; -use canyon_core::connection::db_connector::DbConnection; +use canyon_core::canyon::Canyon; +use canyon_core::connection::contracts::DbConnection; use canyon_core::transaction::Transaction; use canyon_crud::DatabaseType; use regex::Regex; @@ -583,7 +584,7 @@ impl MigrationsProcessor { pub async fn from_query_register(queries_to_execute: &HashMap<&str, Vec<&str>>) { for datasource in queries_to_execute.iter() { let datasource_name = datasource.0; - let db_conn = canyon_core::connection::Canyon::instance() + let db_conn = Canyon::instance() .expect("Error getting db connection on `from_query_register`") .get_connection(datasource_name) .await diff --git a/src/lib.rs b/src/lib.rs index 505c60ae..fc6ad028 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,12 +29,11 @@ pub mod macros { pub mod connection { pub use canyon_core::connection::database_type::DatabaseType; pub use canyon_core::connection::db_connector::DatabaseConnection; - pub use canyon_core::connection::Canyon; } pub mod core { - pub use canyon_core::connection::db_connector::DbConnection; - pub use canyon_core::connection::Canyon; + pub use canyon_core::canyon::Canyon; + pub use canyon_core::connection::contracts::DbConnection; pub use canyon_core::mapper::*; pub use canyon_core::query_parameters::QueryParameter; pub use canyon_core::rows::CanyonRows; From da91aef704f38b0d3856dcf788adda3b38f8374c Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sat, 3 May 2025 12:46:49 +0200 Subject: [PATCH 105/155] fix: doc-comments --- canyon_core/src/canyon.rs | 4 +--- canyon_core/src/connection/contracts/mod.rs | 20 ++++++++++---------- canyon_core/src/transaction.rs | 5 +---- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/canyon_core/src/canyon.rs b/canyon_core/src/canyon.rs index 3e42b552..af5c2bf1 100644 --- a/canyon_core/src/canyon.rs +++ b/canyon_core/src/canyon.rs @@ -27,9 +27,7 @@ pub type SharedConnection = Arc>; /// - Support for retrieving connections by name or default. /// /// # Examples -/// ```rust -/// use canyon_core::Canyon; -/// +/// ```no_run /// #[tokio::main] /// async fn main() -> Result<(), Box> { /// // Initialize the Canyon context diff --git a/canyon_core/src/connection/contracts/mod.rs b/canyon_core/src/connection/contracts/mod.rs index c2e20b5c..ddc03a52 100644 --- a/canyon_core/src/connection/contracts/mod.rs +++ b/canyon_core/src/connection/contracts/mod.rs @@ -16,7 +16,7 @@ mod r#impl; // contains the implementation details for the trait /// /// # Examples /// -/// ```rust +/// ```no_run /// use crate::connection::DbConnection; /// /// async fn execute_query(conn: &C) { @@ -38,7 +38,7 @@ pub trait DbConnection { /// * `params` - A slice of query parameters to bind to the statement. /// /// # Returns - /// A `Future` that resolves to a `Result` containing [`CanyonRows`](crate::rows::CanyonRows) on success or an error on failure. + /// A [Future] that resolves to a [Result] containing [`CanyonRows`] on success or an error on failure. fn query_rows<'a>( &self, stmt: &str, @@ -52,9 +52,9 @@ pub trait DbConnection { /// * `params` - A slice of query parameters to bind to the statement. /// /// # Returns - /// A `Future` that resolves to a `Result` containing a `Vec` on success or an error on failure. + /// A [Future] that resolves to a [Result] containing a `Vec` on success or an error on failure. /// - /// The `R` type must implement the [`RowMapper`](crate::mapper::RowMapper) trait. + /// The `R` type must implement the [`RowMapper`] trait. fn query<'a, S, R>( &self, stmt: S, @@ -72,9 +72,9 @@ pub trait DbConnection { /// * `params` - A slice of query parameters to bind to the statement. /// /// # Returns - /// A `Future` that resolves to a `Result` containing an `Option` on success or an error on failure. + /// A [Future] that resolves to a [Result] containing an `Option` on success or an error on failure. /// - /// The `R` type must implement the [`RowMapper`](crate::mapper::RowMapper) trait. + /// The `R` type must implement the [`RowMapper`] trait. fn query_one<'a, R>( &self, stmt: &str, @@ -90,9 +90,9 @@ pub trait DbConnection { /// * `params` - A slice of query parameters to bind to the statement. /// /// # Returns - /// A `Future` that resolves to a `Result` containing the value of type `T` on success or an error on failure. + /// A [Future] that resolves to a [Result] containing the value of type `T` on success or an error on failure. /// - /// The `T` type must implement the [`FromSqlOwnedValue`](crate::rows::FromSqlOwnedValue) trait. + /// The `T` type must implement the [`FromSqlOwnedValue`] trait. fn query_one_for<'a, T: FromSqlOwnedValue>( &self, stmt: &str, @@ -106,7 +106,7 @@ pub trait DbConnection { /// * `params` - A slice of query parameters to bind to the statement. /// /// # Returns - /// A `Future` that resolves to a `Result` containing the number of affected rows on success or an error on failure. + /// A [Future] that resolves to a [Result] containing the number of affected rows on success or an error on failure. fn execute<'a>( &self, stmt: &str, @@ -116,6 +116,6 @@ pub trait DbConnection { /// Retrieves the type of the database associated with the connection. /// /// # Returns - /// A `Result` containing the [`DatabaseType`](crate::connection::database_type::DatabaseType) on success or an error on failure. + /// A `Result` containing the [`DatabaseType`] on success or an error on failure. fn get_database_type(&self) -> Result>; } diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index 43c990e6..0c7e7bed 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -24,10 +24,7 @@ use std::{fmt::Display, future::Future}; /// - Simplifies interaction with database entities. /// /// # Examples -/// ```rust -/// use canyon_core::transaction::Transaction; -/// use canyon_core::crud::CrudOperations; -/// +/// ```no_run /// async fn perform_query(entity: E) { /// let result = ::query("SELECT * FROM users", &[], entity).await; /// match result { From b576499698a40c3272b39edaa9dcf7a941f816b9 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sat, 3 May 2025 12:51:32 +0200 Subject: [PATCH 106/155] fix: doc-comments examples with no_run to ignore --- canyon_core/src/canyon.rs | 2 +- canyon_core/src/connection/contracts/mod.rs | 2 +- canyon_core/src/transaction.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/canyon_core/src/canyon.rs b/canyon_core/src/canyon.rs index af5c2bf1..1ae62824 100644 --- a/canyon_core/src/canyon.rs +++ b/canyon_core/src/canyon.rs @@ -27,7 +27,7 @@ pub type SharedConnection = Arc>; /// - Support for retrieving connections by name or default. /// /// # Examples -/// ```no_run +/// ```ignore /// #[tokio::main] /// async fn main() -> Result<(), Box> { /// // Initialize the Canyon context diff --git a/canyon_core/src/connection/contracts/mod.rs b/canyon_core/src/connection/contracts/mod.rs index ddc03a52..3f1a4732 100644 --- a/canyon_core/src/connection/contracts/mod.rs +++ b/canyon_core/src/connection/contracts/mod.rs @@ -16,7 +16,7 @@ mod r#impl; // contains the implementation details for the trait /// /// # Examples /// -/// ```no_run +/// ```ignore /// use crate::connection::DbConnection; /// /// async fn execute_query(conn: &C) { diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index 0c7e7bed..3e53ea25 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -24,7 +24,7 @@ use std::{fmt::Display, future::Future}; /// - Simplifies interaction with database entities. /// /// # Examples -/// ```no_run +/// ```ignore /// async fn perform_query(entity: E) { /// let result = ::query("SELECT * FROM users", &[], entity).await; /// match result { From d3d388ea7d5f4fbb16a099b774d8cf30fbfdf506 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 7 May 2025 16:19:47 +0200 Subject: [PATCH 107/155] feat: splitting the behaviour of the implementors of QueryBuilder into a small subset of contracts, according each one functionality --- canyon_core/src/connection/clients/mssql.rs | 2 +- canyon_core/src/connection/clients/mysql.rs | 2 +- .../src/connection/clients/postgresql.rs | 2 +- .../contracts/impl/database_connection.rs | 2 +- .../src/connection/contracts/impl/mod.rs | 3 +- .../src/connection/contracts/impl/mssql.rs | 2 +- .../src/connection/contracts/impl/mysql.rs | 2 +- .../connection/contracts/impl/postgresql.rs | 2 +- .../src/connection/contracts/impl/str.rs | 10 +- canyon_core/src/connection/contracts/mod.rs | 5 +- canyon_core/src/lib.rs | 2 +- .../src => canyon_core/src/query}/bounds.rs | 2 +- canyon_core/src/query/mod.rs | 5 + .../src/query}/operators.rs | 2 +- .../parameters.rs} | 0 canyon_core/src/query/query.rs | 25 + .../src/query/querybuilder/contracts/mod.rs | 160 +++++ .../src/query/querybuilder/impl/delete.rs | 70 ++ .../src/query/querybuilder/impl/mod.rs | 106 +++ .../src/query/querybuilder/impl/select.rs | 97 +++ .../src/query/querybuilder/impl/update.rs | 109 ++++ canyon_core/src/query/querybuilder/mod.rs | 5 + .../src/query/querybuilder/types/delete.rs | 34 + .../src/query/querybuilder/types/mod.rs | 46 ++ .../src/query/querybuilder/types/select.rs | 29 + .../src/query/querybuilder/types/update.rs | 33 + canyon_core/src/transaction.rs | 2 +- canyon_crud/src/crud.rs | 8 +- canyon_crud/src/lib.rs | 5 +- canyon_crud/src/query_elements/mod.rs | 3 - canyon_crud/src/query_elements/query.rs | 19 - .../src/query_elements/query_builder.rs | 602 ------------------ canyon_entities/src/manager_builder.rs | 4 +- canyon_macros/src/foreignkeyable_macro.rs | 4 +- canyon_macros/src/query_operations/delete.rs | 12 +- .../src/query_operations/doc_comments.rs | 2 +- .../src/query_operations/foreign_key.rs | 4 +- canyon_macros/src/query_operations/read.rs | 13 +- canyon_macros/src/query_operations/update.rs | 12 +- src/lib.rs | 10 +- tests/crud/querybuilder_operations.rs | 11 +- 41 files changed, 785 insertions(+), 683 deletions(-) rename {canyon_crud/src => canyon_core/src/query}/bounds.rs (97%) create mode 100644 canyon_core/src/query/mod.rs rename {canyon_crud/src/query_elements => canyon_core/src/query}/operators.rs (97%) rename canyon_core/src/{query_parameters.rs => query/parameters.rs} (100%) create mode 100644 canyon_core/src/query/query.rs create mode 100644 canyon_core/src/query/querybuilder/contracts/mod.rs create mode 100644 canyon_core/src/query/querybuilder/impl/delete.rs create mode 100644 canyon_core/src/query/querybuilder/impl/mod.rs create mode 100644 canyon_core/src/query/querybuilder/impl/select.rs create mode 100644 canyon_core/src/query/querybuilder/impl/update.rs create mode 100644 canyon_core/src/query/querybuilder/mod.rs create mode 100644 canyon_core/src/query/querybuilder/types/delete.rs create mode 100644 canyon_core/src/query/querybuilder/types/mod.rs create mode 100644 canyon_core/src/query/querybuilder/types/select.rs create mode 100644 canyon_core/src/query/querybuilder/types/update.rs delete mode 100644 canyon_crud/src/query_elements/mod.rs delete mode 100644 canyon_crud/src/query_elements/query.rs delete mode 100644 canyon_crud/src/query_elements/query_builder.rs diff --git a/canyon_core/src/connection/clients/mssql.rs b/canyon_core/src/connection/clients/mssql.rs index ec6df852..70ec24db 100644 --- a/canyon_core/src/connection/clients/mssql.rs +++ b/canyon_core/src/connection/clients/mssql.rs @@ -1,4 +1,4 @@ -use crate::{query_parameters::QueryParameter, rows::CanyonRows}; +use crate::{query::parameters::QueryParameter, rows::CanyonRows}; #[cfg(feature = "mssql")] use async_std::net::TcpStream; use std::error::Error; diff --git a/canyon_core/src/connection/clients/mysql.rs b/canyon_core/src/connection/clients/mysql.rs index 2318e0dc..905f6405 100644 --- a/canyon_core/src/connection/clients/mysql.rs +++ b/canyon_core/src/connection/clients/mysql.rs @@ -1,6 +1,6 @@ use crate::mapper::RowMapper; use crate::rows::FromSqlOwnedValue; -use crate::{query_parameters::QueryParameter, rows::CanyonRows}; +use crate::{query::parameters::QueryParameter, rows::CanyonRows}; #[cfg(feature = "mysql")] use mysql_async::Pool; use mysql_async::Row; diff --git a/canyon_core/src/connection/clients/postgresql.rs b/canyon_core/src/connection/clients/postgresql.rs index 562f2cb8..a01774df 100644 --- a/canyon_core/src/connection/clients/postgresql.rs +++ b/canyon_core/src/connection/clients/postgresql.rs @@ -1,5 +1,5 @@ use crate::mapper::RowMapper; -use crate::{query_parameters::QueryParameter, rows::CanyonRows}; +use crate::{query::parameters::QueryParameter, rows::CanyonRows}; use std::error::Error; use std::fmt::Display; #[cfg(feature = "postgres")] diff --git a/canyon_core/src/connection/contracts/impl/database_connection.rs b/canyon_core/src/connection/contracts/impl/database_connection.rs index bd12abcd..87f0adf9 100644 --- a/canyon_core/src/connection/contracts/impl/database_connection.rs +++ b/canyon_core/src/connection/contracts/impl/database_connection.rs @@ -3,7 +3,7 @@ use crate::{ contracts::DbConnection, database_type::DatabaseType, db_connector::DatabaseConnection, }, mapper::RowMapper, - query_parameters::QueryParameter, + query::parameters::QueryParameter, rows::{CanyonRows, FromSqlOwnedValue}, }; use std::{error::Error, fmt::Display}; diff --git a/canyon_core/src/connection/contracts/impl/mod.rs b/canyon_core/src/connection/contracts/impl/mod.rs index 44c4d2ee..27550f06 100644 --- a/canyon_core/src/connection/contracts/impl/mod.rs +++ b/canyon_core/src/connection/contracts/impl/mod.rs @@ -1,11 +1,10 @@ -pub mod database_connection; - pub mod mssql; pub mod mysql; pub mod postgresql; #[macro_use] pub mod str; +pub mod database_connection; // Apply the macro to implement DbConnection for &str and str impl_db_connection!(str); diff --git a/canyon_core/src/connection/contracts/impl/mssql.rs b/canyon_core/src/connection/contracts/impl/mssql.rs index e77386b1..01153121 100644 --- a/canyon_core/src/connection/contracts/impl/mssql.rs +++ b/canyon_core/src/connection/contracts/impl/mssql.rs @@ -5,7 +5,7 @@ use crate::{ database_type::DatabaseType, }, mapper::RowMapper, - query_parameters::QueryParameter, + query::parameters::QueryParameter, rows::{CanyonRows, FromSqlOwnedValue}, }; use std::{error::Error, fmt::Display, future::Future}; diff --git a/canyon_core/src/connection/contracts/impl/mysql.rs b/canyon_core/src/connection/contracts/impl/mysql.rs index 0f469001..04f332c1 100644 --- a/canyon_core/src/connection/contracts/impl/mysql.rs +++ b/canyon_core/src/connection/contracts/impl/mysql.rs @@ -4,7 +4,7 @@ use crate::{ clients::mysql::MysqlConnection, contracts::DbConnection, database_type::DatabaseType, }, mapper::RowMapper, - query_parameters::QueryParameter, + query::parameters::QueryParameter, rows::{CanyonRows, FromSqlOwnedValue}, }; use std::{error::Error, fmt::Display, future::Future}; diff --git a/canyon_core/src/connection/contracts/impl/postgresql.rs b/canyon_core/src/connection/contracts/impl/postgresql.rs index 158a95c4..d9b061a2 100644 --- a/canyon_core/src/connection/contracts/impl/postgresql.rs +++ b/canyon_core/src/connection/contracts/impl/postgresql.rs @@ -5,7 +5,7 @@ use crate::{ database_type::DatabaseType, }, mapper::RowMapper, - query_parameters::QueryParameter, + query::parameters::QueryParameter, rows::{CanyonRows, FromSqlOwnedValue}, }; use std::{error::Error, fmt::Display, future::Future}; diff --git a/canyon_core/src/connection/contracts/impl/str.rs b/canyon_core/src/connection/contracts/impl/str.rs index ee2fabcc..8c8974e1 100644 --- a/canyon_core/src/connection/contracts/impl/str.rs +++ b/canyon_core/src/connection/contracts/impl/str.rs @@ -6,7 +6,7 @@ macro_rules! impl_db_connection { async fn query_rows<'a>( &self, stmt: &str, - params: &[&'a dyn crate::query_parameters::QueryParameter<'a>], + params: &[&'a dyn crate::query::parameters::QueryParameter<'a>], ) -> Result> { let conn = crate::connection::Canyon::instance()? .get_connection(self) @@ -17,7 +17,7 @@ macro_rules! impl_db_connection { async fn query<'a, S, R>( &self, stmt: S, - params: &[&'a (dyn crate::query_parameters::QueryParameter<'a>)], + params: &[&'a (dyn crate::query::parameters::QueryParameter<'a>)], ) -> Result, Box<(dyn std::error::Error + Send + Sync)>> where S: AsRef + std::fmt::Display + Send, @@ -33,7 +33,7 @@ macro_rules! impl_db_connection { async fn query_one<'a, R>( &self, stmt: &str, - params: &[&'a dyn crate::query_parameters::QueryParameter<'a>], + params: &[&'a dyn crate::query::parameters::QueryParameter<'a>], ) -> Result, Box<(dyn std::error::Error + Send + Sync)>> where R: crate::mapper::RowMapper, @@ -47,7 +47,7 @@ macro_rules! impl_db_connection { async fn query_one_for<'a, T: crate::rows::FromSqlOwnedValue>( &self, stmt: &str, - params: &[&'a dyn crate::query_parameters::QueryParameter<'a>], + params: &[&'a dyn crate::query::parameters::QueryParameter<'a>], ) -> Result> { let conn = crate::connection::Canyon::instance()? .get_connection(self) @@ -58,7 +58,7 @@ macro_rules! impl_db_connection { async fn execute<'a>( &self, stmt: &str, - params: &[&'a dyn crate::query_parameters::QueryParameter<'a>], + params: &[&'a dyn crate::query::parameters::QueryParameter<'a>], ) -> Result> { let conn = crate::connection::Canyon::instance()? .get_connection(self) diff --git a/canyon_core/src/connection/contracts/mod.rs b/canyon_core/src/connection/contracts/mod.rs index 3f1a4732..934961a5 100644 --- a/canyon_core/src/connection/contracts/mod.rs +++ b/canyon_core/src/connection/contracts/mod.rs @@ -1,12 +1,13 @@ use crate::connection::database_type::DatabaseType; use crate::mapper::RowMapper; -use crate::query_parameters::QueryParameter; +use crate::query::parameters::QueryParameter; use crate::rows::{CanyonRows, FromSqlOwnedValue}; use std::error::Error; use std::fmt::Display; use std::future::Future; -mod r#impl; // contains the implementation details for the trait +mod r#impl; +// contains the implementation details for the trait /// The `DbConnection` trait defines the core functionality required for interacting with a database connection. /// It provides methods for executing queries, retrieving rows, and obtaining metadata about the database type. diff --git a/canyon_core/src/lib.rs b/canyon_core/src/lib.rs index c705c910..01a90728 100644 --- a/canyon_core/src/lib.rs +++ b/canyon_core/src/lib.rs @@ -22,7 +22,7 @@ pub mod canyon; pub mod column; pub mod connection; pub mod mapper; -pub mod query_parameters; +pub mod query; pub mod row; pub mod rows; pub mod transaction; diff --git a/canyon_crud/src/bounds.rs b/canyon_core/src/query/bounds.rs similarity index 97% rename from canyon_crud/src/bounds.rs rename to canyon_core/src/query/bounds.rs index 1d3fed58..58aeca01 100644 --- a/canyon_crud/src/bounds.rs +++ b/canyon_core/src/query/bounds.rs @@ -1,4 +1,4 @@ -use canyon_core::query_parameters::QueryParameter; +use crate::query::parameters::QueryParameter; /// Created for retrieve the field's name of a field of a struct, giving /// the Canyon's autogenerated enum with the variants that maps this diff --git a/canyon_core/src/query/mod.rs b/canyon_core/src/query/mod.rs new file mode 100644 index 00000000..8c796d1e --- /dev/null +++ b/canyon_core/src/query/mod.rs @@ -0,0 +1,5 @@ +pub mod bounds; +pub mod operators; +pub mod parameters; +pub mod query; +pub mod querybuilder; diff --git a/canyon_crud/src/query_elements/operators.rs b/canyon_core/src/query/operators.rs similarity index 97% rename from canyon_crud/src/query_elements/operators.rs rename to canyon_core/src/query/operators.rs index 17b5c58d..eaa6c5fb 100644 --- a/canyon_crud/src/query_elements/operators.rs +++ b/canyon_core/src/query/operators.rs @@ -1,4 +1,4 @@ -use canyon_core::connection::database_type::DatabaseType; +use crate::connection::database_type::DatabaseType; pub trait Operator { fn as_str(&self, placeholder_counter: usize, datasource_type: &DatabaseType) -> String; diff --git a/canyon_core/src/query_parameters.rs b/canyon_core/src/query/parameters.rs similarity index 100% rename from canyon_core/src/query_parameters.rs rename to canyon_core/src/query/parameters.rs diff --git a/canyon_core/src/query/query.rs b/canyon_core/src/query/query.rs new file mode 100644 index 00000000..c6bd753c --- /dev/null +++ b/canyon_core/src/query/query.rs @@ -0,0 +1,25 @@ +use std::fmt::Debug; + +use crate::query::parameters::QueryParameter; + +// TODO: all the query works here +// TODO: exports things like Select::... where receives the table +// name and prepares the raw query (maybe with const_format!) for improved performance + +// TODO: query should implement ToStatement (as the drivers underneath Canyon) or similar +// to be usable directly in the input of Transaction and DbConnenction +/// Holds a sql sentence details +#[derive(Debug, Clone)] +pub struct Query<'a> { + pub sql: String, + pub params: Vec<&'a dyn QueryParameter<'a>>, +} + +impl<'a> Query<'a> { + pub fn new(sql: String) -> Query<'a> { + Self { + sql, + params: vec![], + } + } +} diff --git a/canyon_core/src/query/querybuilder/contracts/mod.rs b/canyon_core/src/query/querybuilder/contracts/mod.rs new file mode 100644 index 00000000..5b33af86 --- /dev/null +++ b/canyon_core/src/query/querybuilder/contracts/mod.rs @@ -0,0 +1,160 @@ +//! Contains the elements that makes part of the formal declaration +//! of the behaviour of the Canyon-SQL QueryBuilder + +use crate::query::bounds::{FieldIdentifier, FieldValueIdentifier}; +use crate::query::operators::Operator; +use crate::query::parameters::QueryParameter; + +pub trait DeleteQueryBuilderOps<'a>: QueryBuilderOps<'a> {} + +pub trait UpdateQueryBuilderOps<'a>: QueryBuilderOps<'a> { + /// Creates an SQL `SET` clause to specify the columns that must be updated in the sentence + fn set(self, columns: &'a [(Z, Q)]) -> Self + where + Z: FieldIdentifier + Clone, + Q: QueryParameter<'a>; +} + +pub trait SelectQueryBuilderOps<'a>: QueryBuilderOps<'a> { + // TODO: cols on the statement must be generics to use &str and fieldvalue (the enum) + // TODO: could we introduce const_format! for the construction of the components of the query? + + /// Adds a *LEFT JOIN* SQL statement to the underlying + /// `Sql Statement` held by the [`QueryBuilder`], where: + /// + /// * `join_table` - The table target of the join operation + /// * `col1` - The left side of the ON operator for the join + /// * `col2` - The right side of the ON operator for the join + /// + /// > Note: The order on the column parameters is irrelevant + fn left_join(self, join_table: &str, col1: &str, col2: &str) -> Self; + + /// Adds a *INNER JOIN* SQL statement to the underlying + /// `Sql Statement` held by the [`QueryBuilder`], where: + /// + /// * `join_table` - The table target of the join operation + /// * `col1` - The left side of the ON operator for the join + /// * `col2` - The right side of the ON operator for the join + /// + /// > Note: The order on the column parameters is irrelevant + fn inner_join(self, join_table: &str, col1: &str, col2: &str) -> Self; + + /// Adds a *RIGHT JOIN* SQL statement to the underlying + /// `Sql Statement` held by the [`QueryBuilder`], where: + /// + /// * `join_table` - The table target of the join operation + /// * `col1` - The left side of the ON operator for the join + /// * `col2` - The right side of the ON operator for the join + /// + /// > Note: The order on the column parameters is irrelevant + fn right_join(self, join_table: &str, col1: &str, col2: &str) -> Self; + + /// Adds a *FULL JOIN* SQL statement to the underlying + /// `Sql Statement` held by the [`QueryBuilder`], where: + /// + /// * `join_table` - The table target of the join operation + /// * `col1` - The left side of the ON operator for the join + /// * `col2` - The right side of the ON operator for the join + /// + /// > Note: The order on the column parameters is irrelevant + fn full_join(self, join_table: &str, col1: &str, col2: &str) -> Self; +} + +/// The [`QueryBuilder`] trait is the root of a kind of hierarchy +/// on more specific [`super::QueryBuilder`], that are: +/// +/// * [`super::SelectQueryBuilder`] +/// * [`super::UpdateQueryBuilder`] +/// * [`super::DeleteQueryBuilder`] +/// +/// This trait provides the formal declaration of the behaviour that the +/// implementors must provide in their public interfaces, grouping +/// the common elements between every element down in that +/// hierarchy. +/// +/// For example, the [`super::QueryBuilder`] type holds the data +/// necessary for track the SQL sentence while it's being generated +/// thought the fluent builder, and provides the behaviour of +/// the common elements defined in this trait. +/// +/// The more concrete types represents a wrapper over a raw +/// [`super::QueryBuilder`], offering all the elements declared +/// in this trait in its public interface, and which implementation +/// only consists of call the same method on the wrapped +/// [`super::QueryBuilder`]. +/// +/// This allows us to declare in their public interface their +/// specific operations, like, for example, join operations +/// on the [`super::SelectQueryBuilder`], and the usage +/// of the `SET` clause on a [`super::UpdateQueryBuilder`], +/// without mixing types or polluting everything into +/// just one type. +pub trait QueryBuilderOps<'a> { + /// Returns a read-only reference to the underlying SQL sentence, + /// with the same lifetime as self + fn read_sql(&'a self) -> &'a str; + + /// Public interface for append the content of a slice to the end of + /// the underlying SQL sentence. + /// + /// This mutator will allow the user to wire SQL code to the already + /// generated one + /// + /// * `sql` - The [`&str`] to be wired in the SQL + fn push_sql(self, sql: &str); + + /// Generates a `WHERE` SQL clause for constraint the query. + /// + /// * `column` - A [`FieldValueIdentifier`] that will provide the target + /// column name and the value for the filter + /// * `op` - Any element that implements [`Operator`] for create the comparison + /// or equality binary operator + fn r#where>(self, column: Z, op: impl Operator) -> Self; + + /// Generates an `AND` SQL clause for constraint the query. + /// + /// * `column` - A [`FieldValueIdentifier`] that will provide the target + /// column name and the value for the filter + /// * `op` - Any element that implements [`Operator`] for create the comparison + /// or equality binary operator + fn and>(self, column: Z, op: impl Operator) -> Self; + + /// Generates an `AND` SQL clause for constraint the query that's being constructed + /// + /// * `column` - A [`FieldIdentifier`] that will provide the target + /// column name for the filter, based on the variant that represents + /// the field name that maps the targeted column name + /// * `values` - An array of [`QueryParameter`] with the values to filter + /// inside the `IN` operator + fn and_values_in(self, column: Z, values: &'a [Q]) -> Self + where + Z: FieldIdentifier, + Q: QueryParameter<'a>; + + /// Generates an `OR` SQL clause for constraint the query that will create + /// the filter in conjunction with an `IN` operator that will ac + /// + /// * `column` - A [`FieldIdentifier`] that will provide the target + /// column name for the filter, based on the variant that represents + /// the field name that maps the targeted column name + /// * `values` - An array of [`QueryParameter`] with the values to filter + /// inside the `IN` operator + fn or_values_in(self, r#or: Z, values: &'a [Q]) -> Self + where + Z: FieldIdentifier, + Q: QueryParameter<'a>; + + /// Generates an `OR` SQL clause for constraint the query. + /// + /// * `column` - A [`FieldValueIdentifier`] that will provide the target + /// column name and the value for the filter + /// * `op` - Any element that implements [`Operator`] for create the comparison + /// or equality binary operator + fn or>(self, column: Z, op: impl Operator) -> Self; + + /// Generates a `ORDER BY` SQL clause for constraint the query. + /// + /// * `order_by` - A [`FieldIdentifier`] that will provide the target column name + /// * `desc` - a boolean indicating if the generated `ORDER_BY` must be in ascending or descending order + fn order_by(self, order_by: Z, desc: bool) -> Self; +} diff --git a/canyon_core/src/query/querybuilder/impl/delete.rs b/canyon_core/src/query/querybuilder/impl/delete.rs new file mode 100644 index 00000000..7e8b48fc --- /dev/null +++ b/canyon_core/src/query/querybuilder/impl/delete.rs @@ -0,0 +1,70 @@ +use crate::connection::contracts::DbConnection; +use crate::mapper::RowMapper; +use crate::query::bounds::{FieldIdentifier, FieldValueIdentifier}; +use crate::query::operators::Operator; +use crate::query::parameters::QueryParameter; +use crate::query::querybuilder::contracts::{DeleteQueryBuilderOps, QueryBuilderOps}; +use crate::query::querybuilder::types::delete::DeleteQueryBuilder; + +impl<'a, I: DbConnection + ?Sized, R: RowMapper> DeleteQueryBuilderOps<'a> + for DeleteQueryBuilder<'a, I, R> +{ +} // NOTE: for now, this is just a type formalism + +impl<'a, I: DbConnection + ?Sized, R: RowMapper> QueryBuilderOps<'a> + for DeleteQueryBuilder<'a, I, R> +{ + #[inline] + fn read_sql(&'a self) -> &'a str { + self._inner.sql.as_str() + } + + #[inline(always)] + fn push_sql(mut self, sql: &str) { + self._inner.sql.push_str(sql); + } + + #[inline] + fn r#where>(mut self, r#where: Z, op: impl Operator) -> Self { + self._inner.r#where(r#where, op); + self + } + + #[inline] + fn and>(mut self, column: Z, op: impl Operator) -> Self { + self._inner.and(column, op); + self + } + + #[inline] + fn and_values_in(mut self, r#and: Z, values: &'a [Q]) -> Self + where + Z: FieldIdentifier, + Q: QueryParameter<'a>, + { + self._inner.or_values_in(and, values); + self + } + + #[inline] + fn or_values_in(mut self, r#or: Z, values: &'a [Q]) -> Self + where + Z: FieldIdentifier, + Q: QueryParameter<'a>, + { + self._inner.or_values_in(or, values); + self + } + + #[inline] + fn or>(mut self, column: Z, op: impl Operator) -> Self { + self._inner.or(column, op); + self + } + + #[inline] + fn order_by(mut self, order_by: Z, desc: bool) -> Self { + self._inner.order_by(order_by, desc); + self + } +} diff --git a/canyon_core/src/query/querybuilder/impl/mod.rs b/canyon_core/src/query/querybuilder/impl/mod.rs new file mode 100644 index 00000000..dab7aeb5 --- /dev/null +++ b/canyon_core/src/query/querybuilder/impl/mod.rs @@ -0,0 +1,106 @@ +use crate::connection::contracts::DbConnection; +use crate::mapper::RowMapper; +use crate::query::bounds::{FieldIdentifier, FieldValueIdentifier}; +use crate::query::operators::Operator; +use crate::query::parameters::QueryParameter; +pub(crate) use crate::query::querybuilder::QueryBuilder; + +mod delete; +mod select; +mod update; + +impl<'a, I: DbConnection + ?Sized, R: RowMapper> QueryBuilder<'a, I, R> { + fn r#where>(&mut self, r#where: Z, op: impl Operator) { + let (column_name, value) = r#where.value(); + + let where_ = String::from(" WHERE ") + + column_name + + &op.as_str(self.params.len() + 1, &self.database_type); + + self.sql.push_str(&where_); + self.params.push(value); + } + + fn and>(&mut self, r#and: Z, op: impl Operator) { + let (column_name, value) = r#and.value(); + + let and_ = String::from(" AND ") + + column_name + + &op.as_str(self.params.len() + 1, &self.database_type); + + self.sql.push_str(&and_); + self.params.push(value); + } + + fn and_values_in(&mut self, r#and: Z, values: &'a [Q]) + where + Z: FieldIdentifier, + Q: QueryParameter<'a>, + { + if values.is_empty() { + return; + } + + self.sql.push_str(&format!(" AND {} IN (", r#and.as_str())); + + let mut counter = 1; + values.iter().for_each(|qp| { + if values.len() != counter { + self.sql.push_str(&format!("${}, ", self.params.len())); + counter += 1; + } else { + self.sql.push_str(&format!("${}", self.params.len())); + } + self.params.push(qp) + }); + + self.sql.push(')'); + } + + fn or_values_in(&mut self, r#or: Z, values: &'a [Q]) + where + Z: FieldIdentifier, + Q: QueryParameter<'a>, + { + if values.is_empty() { + return; + } + + self.sql.push_str(&format!(" OR {} IN (", r#or.as_str())); + + let mut counter = 1; + values.iter().for_each(|qp| { + if values.len() != counter { + self.sql.push_str(&format!("${}, ", self.params.len())); + counter += 1; + } else { + self.sql.push_str(&format!("${}", self.params.len())); + } + self.params.push(qp) + }); + + self.sql.push(')'); + } + + fn or>(&mut self, r#and: Z, op: impl Operator) { + let (column_name, value) = r#and.value(); + + let and_ = String::from(" OR ") + + column_name + + &op.as_str(self.params.len() + 1, &self.database_type); + + self.sql.push_str(&and_); + self.params.push(value); + } + + #[inline] + fn order_by(&mut self, order_by: Z, desc: bool) { + self.sql.push_str( + &(format!( + " ORDER BY {}{}", + order_by.as_str(), + if desc { " DESC " } else { "" } + )), + ); + } +} diff --git a/canyon_core/src/query/querybuilder/impl/select.rs b/canyon_core/src/query/querybuilder/impl/select.rs new file mode 100644 index 00000000..647fd055 --- /dev/null +++ b/canyon_core/src/query/querybuilder/impl/select.rs @@ -0,0 +1,97 @@ +use crate::connection::contracts::DbConnection; +use crate::mapper::RowMapper; +use crate::query::bounds::{FieldIdentifier, FieldValueIdentifier}; +use crate::query::operators::Operator; +use crate::query::parameters::QueryParameter; +use crate::query::querybuilder::contracts::{QueryBuilderOps, SelectQueryBuilderOps}; +use crate::query::querybuilder::types::select::SelectQueryBuilder; + +impl<'a, I: DbConnection + ?Sized, R: RowMapper> SelectQueryBuilderOps<'a> + for SelectQueryBuilder<'a, I, R> +{ + fn left_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { + self._inner + .sql + .push_str(&format!(" LEFT JOIN {join_table} ON {col1} = {col2}")); + self + } + + fn inner_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { + self._inner + .sql + .push_str(&format!(" INNER JOIN {join_table} ON {col1} = {col2}")); + self + } + + fn right_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { + self._inner + .sql + .push_str(&format!(" RIGHT JOIN {join_table} ON {col1} = {col2}")); + self + } + + fn full_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { + self._inner + .sql + .push_str(&format!(" FULL JOIN {join_table} ON {col1} = {col2}")); + self + } +} + +impl<'a, I: DbConnection + ?Sized, R: RowMapper> QueryBuilderOps<'a> + for SelectQueryBuilder<'a, I, R> +{ + #[inline] + fn read_sql(&'a self) -> &'a str { + self._inner.sql.as_str() + } + + #[inline(always)] + fn push_sql(mut self, sql: &str) { + self._inner.sql.push_str(sql); + } + + #[inline] + fn r#where>(mut self, r#where: Z, op: impl Operator) -> Self { + self._inner.r#where(r#where, op); + self + } + + #[inline] + fn and>(mut self, column: Z, op: impl Operator) -> Self { + self._inner.and(column, op); + self + } + + #[inline] + fn and_values_in(mut self, r#and: Z, values: &'a [Q]) -> Self + where + Z: FieldIdentifier, + Q: QueryParameter<'a>, + { + self._inner.and_values_in(and, values); + self + } + + #[inline] + fn or_values_in(mut self, r#and: Z, values: &'a [Q]) -> Self + where + Z: FieldIdentifier, + Q: QueryParameter<'a>, + { + self._inner.or_values_in(and, values); + self + } + + #[inline] + fn or>(mut self, column: Z, op: impl Operator) -> Self { + self._inner.or(column, op); + self + } + + #[inline] + fn order_by(mut self, order_by: Z, desc: bool) -> Self { + self._inner.order_by(order_by, desc); + self + } +} diff --git a/canyon_core/src/query/querybuilder/impl/update.rs b/canyon_core/src/query/querybuilder/impl/update.rs new file mode 100644 index 00000000..e2c0aa5d --- /dev/null +++ b/canyon_core/src/query/querybuilder/impl/update.rs @@ -0,0 +1,109 @@ +use crate::connection::contracts::DbConnection; +use crate::mapper::RowMapper; +use crate::query::bounds::{FieldIdentifier, FieldValueIdentifier}; +use crate::query::operators::Operator; +use crate::query::parameters::QueryParameter; +use crate::query::querybuilder::contracts::{QueryBuilderOps, UpdateQueryBuilderOps}; +use crate::query::querybuilder::types::update::UpdateQueryBuilder; + +impl<'a, I: DbConnection + ?Sized, R: RowMapper> UpdateQueryBuilderOps<'a> + for UpdateQueryBuilder<'a, I, R> +{ + /// Creates an SQL `SET` clause to specify the columns that must be updated in the sentence + fn set(mut self, columns: &'a [(Z, Q)]) -> Self + where + Z: FieldIdentifier + Clone, + Q: QueryParameter<'a>, + { + if columns.is_empty() { + // TODO: this is an err as well + return self; + } + if self._inner.sql.contains("SET") { + panic!( + // TODO: this should return an Err and not panic! + "\n{}", + String::from("\t[PANIC!] - Don't use chained calls of the .set(...) method. ") + + "\n\tPass all the values in a unique call within the 'columns' " + + "array of tuples parameter\n" + ) + } + + let mut set_clause = String::new(); + set_clause.push_str(" SET "); + + for (idx, column) in columns.iter().enumerate() { + set_clause.push_str(&format!( + "{} = ${}", + column.0.as_str(), + self._inner.params.len() + 1 + )); + + if idx < columns.len() - 1 { + set_clause.push_str(", "); + } + self._inner.params.push(&column.1); + } + + self._inner.sql.push_str(&set_clause); + self + } +} + +impl<'a, I: DbConnection + ?Sized, R: RowMapper> QueryBuilderOps<'a> + for UpdateQueryBuilder<'a, I, R> +{ + #[inline] + fn read_sql(&'a self) -> &'a str { + self._inner.sql.as_str() + } + + #[inline(always)] + fn push_sql(mut self, sql: &str) { + self._inner.sql.push_str(sql); + } + + #[inline] + fn r#where>(mut self, r#where: Z, op: impl Operator) -> Self { + self._inner.r#where(r#where, op); + self + } + + #[inline] + fn and>(mut self, column: Z, op: impl Operator) -> Self { + self._inner.and(column, op); + self + } + + #[inline] + fn and_values_in(mut self, r#and: Z, values: &'a [Q]) -> Self + where + Z: FieldIdentifier, + Q: QueryParameter<'a>, + { + self._inner.and_values_in(and, values); + self + } + + #[inline] + fn or_values_in(mut self, r#or: Z, values: &'a [Q]) -> Self + where + Z: FieldIdentifier, + Q: QueryParameter<'a>, + { + self._inner.or_values_in(or, values); + self + } + + #[inline] + fn or>(mut self, column: Z, op: impl Operator) -> Self { + self._inner.or(column, op); + self + } + + #[inline] + fn order_by(mut self, order_by: Z, desc: bool) -> Self { + self._inner.order_by(order_by, desc); + self + } +} diff --git a/canyon_core/src/query/querybuilder/mod.rs b/canyon_core/src/query/querybuilder/mod.rs new file mode 100644 index 00000000..fd259067 --- /dev/null +++ b/canyon_core/src/query/querybuilder/mod.rs @@ -0,0 +1,5 @@ +pub mod contracts; +mod r#impl; +pub mod types; + +pub use self::{contracts::*, types::*}; diff --git a/canyon_core/src/query/querybuilder/types/delete.rs b/canyon_core/src/query/querybuilder/types/delete.rs new file mode 100644 index 00000000..8bffd2a3 --- /dev/null +++ b/canyon_core/src/query/querybuilder/types/delete.rs @@ -0,0 +1,34 @@ +use crate::connection::contracts::DbConnection; +use crate::mapper::RowMapper; +use crate::query::querybuilder::r#impl::QueryBuilder; +use std::error::Error; + +/// Contains the specific database operations associated with the +/// *DELETE* SQL statements. +/// +/// * `set` - To construct a new `SET` clause to determine the columns to +/// update with the provided values +pub struct DeleteQueryBuilder<'a, I: DbConnection + ?Sized, R: RowMapper> { + pub(crate) _inner: QueryBuilder<'a, I, R>, +} + +impl<'a, I: DbConnection + ?Sized, R: RowMapper> DeleteQueryBuilder<'a, I, R> { + /// Generates a new public instance of the [`DeleteQueryBuilder`] + pub fn new( + table_schema_data: &str, + input: &'a I, + ) -> Result> { + Ok(Self { + _inner: QueryBuilder::new(format!("DELETE FROM {table_schema_data}"), input)?, + }) + } + + /// Launches the generated query to the database pointed by the selected datasource + #[inline] + pub async fn query(self) -> Result, Box<(dyn Error + Send + Sync + 'a)>> + where + Vec: FromIterator<::Output>, + { + self._inner.query().await + } +} diff --git a/canyon_core/src/query/querybuilder/types/mod.rs b/canyon_core/src/query/querybuilder/types/mod.rs new file mode 100644 index 00000000..b8774e11 --- /dev/null +++ b/canyon_core/src/query/querybuilder/types/mod.rs @@ -0,0 +1,46 @@ +pub mod delete; +pub mod select; +pub mod update; + +pub use self::{delete::*, select::*, update::*}; +use crate::connection::contracts::DbConnection; +use crate::connection::database_type::DatabaseType; +use crate::mapper::RowMapper; +use crate::query::parameters::QueryParameter; +use std::error::Error; +use std::marker::PhantomData; + +/// Type for construct more complex queries than the classical CRUD ones. +pub struct QueryBuilder<'a, I: DbConnection + ?Sized, R: RowMapper> { + // query: Query<'a>, + pub(crate) sql: String, + pub(crate) params: Vec<&'a dyn QueryParameter<'a>>, + pub(crate) database_type: DatabaseType, + pub(crate) input: &'a I, + pd: PhantomData, +} + +unsafe impl Send for QueryBuilder<'_, I, R> {} +unsafe impl Sync for QueryBuilder<'_, I, R> {} + +impl<'a, I: DbConnection + ?Sized, R: RowMapper> QueryBuilder<'a, I, R> { + pub fn new(sql: String, input: &'a I) -> Result> { + Ok(Self { + sql, + params: vec![], + database_type: input.get_database_type()?, + input, + pd: Default::default(), + }) + } + + /// Launches the generated query against the database targeted + /// by the selected datasource + pub async fn query(mut self) -> Result, Box<(dyn Error + Send + Sync + 'a)>> + where + Vec: FromIterator<::Output>, + { + self.sql.push(';'); + self.input.query(&self.sql, &self.params).await + } +} diff --git a/canyon_core/src/query/querybuilder/types/select.rs b/canyon_core/src/query/querybuilder/types/select.rs new file mode 100644 index 00000000..25b21aea --- /dev/null +++ b/canyon_core/src/query/querybuilder/types/select.rs @@ -0,0 +1,29 @@ +use crate::connection::contracts::DbConnection; +use crate::mapper::RowMapper; +use crate::query::querybuilder::r#impl::QueryBuilder; +use std::error::Error; + +pub struct SelectQueryBuilder<'a, I: DbConnection + ?Sized, R: RowMapper> { + pub(crate) _inner: QueryBuilder<'a, I, R>, +} + +impl<'a, I: DbConnection + ?Sized, R: RowMapper> SelectQueryBuilder<'a, I, R> { + /// Generates a new public instance of the [`SelectQueryBuilder`] + pub fn new( + table_schema_data: &str, + input: &'a I, + ) -> Result> { + Ok(Self { + _inner: QueryBuilder::new(format!("SELECT * FROM {table_schema_data}"), input)?, + }) + } + + /// Launches the generated query to the database pointed by the selected datasource + #[inline] + pub async fn query(self) -> Result, Box<(dyn Error + Send + Sync + 'a)>> + where + Vec: FromIterator<::Output>, + { + self._inner.query().await + } +} diff --git a/canyon_core/src/query/querybuilder/types/update.rs b/canyon_core/src/query/querybuilder/types/update.rs new file mode 100644 index 00000000..eae2491d --- /dev/null +++ b/canyon_core/src/query/querybuilder/types/update.rs @@ -0,0 +1,33 @@ +use crate::connection::contracts::DbConnection; +use crate::mapper::RowMapper; +use crate::query::querybuilder::r#impl::QueryBuilder; +use std::error::Error; + +/// Contains the specific database operations of the *UPDATE* SQL statements. +/// +/// * `set` - To construct a new `SET` clause to determine the columns to +/// update with the provided values +pub struct UpdateQueryBuilder<'a, I: DbConnection + ?Sized, R: RowMapper> { + pub(crate) _inner: QueryBuilder<'a, I, R>, +} + +impl<'a, I: DbConnection + ?Sized, R: RowMapper> UpdateQueryBuilder<'a, I, R> { + /// Generates a new public instance of the [`UpdateQueryBuilder`] + pub fn new( + table_schema_data: &str, + input: &'a I, + ) -> Result> { + Ok(Self { + _inner: QueryBuilder::new(format!("UPDATE {table_schema_data}"), input)?, + }) + } + + /// Launches the generated query to the database pointed by the selected datasource + #[inline] + pub async fn query(self) -> Result, Box<(dyn Error + Send + Sync + 'a)>> + where + Vec: FromIterator<::Output>, + { + self._inner.query().await + } +} diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index 3e53ea25..6397e9e6 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -1,7 +1,7 @@ use crate::connection::contracts::DbConnection; use crate::mapper::RowMapper; use crate::rows::FromSqlOwnedValue; -use crate::{query_parameters::QueryParameter, rows::CanyonRows}; +use crate::{query::parameters::QueryParameter, rows::CanyonRows}; use std::error::Error; use std::{fmt::Display, future::Future}; diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index f86d3389..237e7c58 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -1,9 +1,9 @@ -use crate::query_elements::query_builder::{ - DeleteQueryBuilder, SelectQueryBuilder, UpdateQueryBuilder, -}; use canyon_core::connection::contracts::DbConnection; use canyon_core::mapper::RowMapper; -use canyon_core::query_parameters::QueryParameter; +use canyon_core::query::parameters::QueryParameter; +use canyon_core::query::querybuilder::{ + DeleteQueryBuilder, SelectQueryBuilder, UpdateQueryBuilder, +}; use std::error::Error; use std::future::Future; diff --git a/canyon_crud/src/lib.rs b/canyon_crud/src/lib.rs index 89695fc7..c4ae5e53 100644 --- a/canyon_crud/src/lib.rs +++ b/canyon_crud/src/lib.rs @@ -1,8 +1,5 @@ -pub mod bounds; pub mod crud; -pub mod query_elements; - -pub use query_elements::operators::*; +pub use canyon_core::query::operators::*; pub use canyon_core::connection::{database_type::DatabaseType, datasources::*}; pub use chrono; diff --git a/canyon_crud/src/query_elements/mod.rs b/canyon_crud/src/query_elements/mod.rs deleted file mode 100644 index e319d4a4..00000000 --- a/canyon_crud/src/query_elements/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod operators; -pub mod query; -pub mod query_builder; diff --git a/canyon_crud/src/query_elements/query.rs b/canyon_crud/src/query_elements/query.rs deleted file mode 100644 index 2f01638b..00000000 --- a/canyon_crud/src/query_elements/query.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::fmt::Debug; - -use canyon_core::query_parameters::QueryParameter; - -/// Holds a sql sentence details -#[derive(Debug, Clone)] -pub struct Query<'a> { - pub sql: String, - pub params: Vec<&'a dyn QueryParameter<'a>>, -} - -impl<'a> Query<'a> { - pub fn new(sql: String) -> Query<'a> { - Self { - sql, - params: vec![], - } - } -} diff --git a/canyon_crud/src/query_elements/query_builder.rs b/canyon_crud/src/query_elements/query_builder.rs deleted file mode 100644 index 29af22c8..00000000 --- a/canyon_crud/src/query_elements/query_builder.rs +++ /dev/null @@ -1,602 +0,0 @@ -use crate::{ - bounds::{FieldIdentifier, FieldValueIdentifier}, - Operator, -}; -use canyon_core::connection::contracts::DbConnection; -use canyon_core::connection::database_type::DatabaseType; -use canyon_core::{mapper::RowMapper, query_parameters::QueryParameter}; -use std::error::Error; -use std::marker::PhantomData; - -/// Contains the elements that makes part of the formal declaration -/// of the behaviour of the Canyon-SQL QueryBuilder -pub mod ops { - use canyon_core::query_parameters::QueryParameter; - - pub use super::*; - - /// The [`QueryBuilder`] trait is the root of a kind of hierarchy - /// on more specific [`super::QueryBuilder`], that are: - /// - /// * [`super::SelectQueryBuilder`] - /// * [`super::UpdateQueryBuilder`] - /// * [`super::DeleteQueryBuilder`] - /// - /// This trait provides the formal declaration of the behaviour that the - /// implementors must provide in their public interfaces, groping - /// the common elements between every element down in that - /// hierarchy. - /// - /// For example, the [`super::QueryBuilder`] type holds the data - /// necessary for track the SQL sentence while it's being generated - /// thought the fluent builder, and provides the behaviour of - /// the common elements defined in this trait. - /// - /// The more concrete types represents a wrapper over a raw - /// [`super::QueryBuilder`], offering all the elements declared - /// in this trait in its public interface, and which implementation - /// only consists of call the same method on the wrapped - /// [`super::QueryBuilder`]. - /// - /// This allows us to declare in their public interface their - /// specific operations, like, for example, join operations - /// on the [`super::SelectQueryBuilder`], and the usage - /// of the `SET` clause on a [`super::UpdateQueryBuilder`], - /// without mixing types or polluting everything into - /// just one type. - pub trait QueryBuilder<'a> { - /// Returns a read-only reference to the underlying SQL sentence, - /// with the same lifetime as self - fn read_sql(&'a self) -> &'a str; - - /// Public interface for append the content of an slice to the end of - /// the underlying SQL sentence. - /// - /// This mutator will allow the user to wire SQL code to the already - /// generated one - /// - /// * `sql` - The [`&str`] to be wired in the SQL - fn push_sql(self, sql: &str); - - /// Generates a `WHERE` SQL clause for constraint the query. - /// - /// * `column` - A [`FieldValueIdentifier`] that will provide the target - /// column name and the value for the filter - /// * `op` - Any element that implements [`Operator`] for create the comparison - /// or equality binary operator - fn r#where>(self, column: Z, op: impl Operator) -> Self; - - /// Generates an `AND` SQL clause for constraint the query. - /// - /// * `column` - A [`FieldValueIdentifier`] that will provide the target - /// column name and the value for the filter - /// * `op` - Any element that implements [`Operator`] for create the comparison - /// or equality binary operator - fn and>(self, column: Z, op: impl Operator) -> Self; - - /// Generates an `AND` SQL clause for constraint the query that will create - /// the filter in conjunction with an `IN` operator that will ac - /// - /// * `column` - A [`FieldIdentifier`] that will provide the target - /// column name for the filter, based on the variant that represents - /// the field name that maps the targeted column name - /// * `values` - An array of [`QueryParameter`] with the values to filter - /// inside the `IN` operator - fn and_values_in(self, column: Z, values: &'a [Q]) -> Self - where - Z: FieldIdentifier, - Q: QueryParameter<'a>; - - /// Generates an `OR` SQL clause for constraint the query that will create - /// the filter in conjunction with an `IN` operator that will ac - /// - /// * `column` - A [`FieldIdentifier`] that will provide the target - /// column name for the filter, based on the variant that represents - /// the field name that maps the targeted column name - /// * `values` - An array of [`QueryParameter`] with the values to filter - /// inside the `IN` operator - fn or_values_in(self, r#or: Z, values: &'a [Q]) -> Self - where - Z: FieldIdentifier, - Q: QueryParameter<'a>; - - /// Generates an `OR` SQL clause for constraint the query. - /// - /// * `column` - A [`FieldValueIdentifier`] that will provide the target - /// column name and the value for the filter - /// * `op` - Any element that implements [`Operator`] for create the comparison - /// or equality binary operator - fn or>(self, column: Z, op: impl Operator) -> Self; - - /// Generates a `ORDER BY` SQL clause for constraint the query. - /// - /// * `order_by` - A [`FieldIdentifier`] that will provide the target column name - /// * `desc` - a boolean indicating if the generated `ORDER_BY` must be in ascending or descending order - fn order_by(self, order_by: Z, desc: bool) -> Self; - } -} - -/// Type for construct more complex queries than the classical CRUD ones. -pub struct QueryBuilder<'a, I: DbConnection + ?Sized, R: RowMapper> { - // query: Query<'a>, - sql: String, - params: Vec<&'a dyn QueryParameter<'a>>, - database_type: DatabaseType, - input: &'a I, - pd: PhantomData, -} - -unsafe impl Sync for QueryBuilder<'_, I, R> {} - -impl<'a, I: DbConnection + ?Sized, R: RowMapper> QueryBuilder<'a, I, R> { - pub fn new(sql: String, input: &'a I) -> Result> { - Ok(Self { - sql, - params: vec![], - database_type: input.get_database_type()?, - input, - pd: Default::default(), - }) - } - - /// Launches the generated query against the database targeted - /// by the selected datasource - pub async fn query(mut self) -> Result, Box<(dyn Error + Send + Sync + 'a)>> - where - Vec: FromIterator<::Output>, - { - self.sql.push(';'); - self.input.query(&self.sql, &self.params).await - } - - pub fn r#where>(&mut self, r#where: Z, op: impl Operator) { - let (column_name, value) = r#where.value(); - - let where_ = String::from(" WHERE ") - + column_name - + &op.as_str(self.params.len() + 1, &self.database_type); - - self.sql.push_str(&where_); - self.params.push(value); - } - - pub fn and>(&mut self, r#and: Z, op: impl Operator) { - let (column_name, value) = r#and.value(); - - let and_ = String::from(" AND ") - + column_name - + &op.as_str(self.params.len() + 1, &self.database_type); - - self.sql.push_str(&and_); - self.params.push(value); - } - - pub fn or>(&mut self, r#and: Z, op: impl Operator) { - let (column_name, value) = r#and.value(); - - let and_ = String::from(" OR ") - + column_name - + &op.as_str(self.params.len() + 1, &self.database_type); - - self.sql.push_str(&and_); - self.params.push(value); - } - - pub fn and_values_in(&mut self, r#and: Z, values: &'a [Q]) - where - Z: FieldIdentifier, - Q: QueryParameter<'a>, - { - if values.is_empty() { - return; - } - - self.sql.push_str(&format!(" AND {} IN (", r#and.as_str())); - - let mut counter = 1; - values.iter().for_each(|qp| { - if values.len() != counter { - self.sql.push_str(&format!("${}, ", self.params.len())); - counter += 1; - } else { - self.sql.push_str(&format!("${}", self.params.len())); - } - self.params.push(qp) - }); - - self.sql.push(')') - } - - fn or_values_in(&mut self, r#or: Z, values: &'a [Q]) - where - Z: FieldIdentifier, - Q: QueryParameter<'a>, - { - if values.is_empty() { - return; - } - - self.sql.push_str(&format!(" OR {} IN (", r#or.as_str())); - - let mut counter = 1; - values.iter().for_each(|qp| { - if values.len() != counter { - self.sql.push_str(&format!("${}, ", self.params.len())); - counter += 1; - } else { - self.sql.push_str(&format!("${}", self.params.len())); - } - self.params.push(qp) - }); - - self.sql.push(')') - } - - #[inline] - pub fn order_by(&mut self, order_by: Z, desc: bool) { - self.sql.push_str( - &(format!( - " ORDER BY {}{}", - order_by.as_str(), - if desc { " DESC " } else { "" } - )), - ); - } -} - -pub struct SelectQueryBuilder<'a, I: DbConnection + ?Sized, R: RowMapper> { - _inner: QueryBuilder<'a, I, R>, -} - -impl<'a, I: DbConnection + ?Sized, R: RowMapper> SelectQueryBuilder<'a, I, R> { - /// Generates a new public instance of the [`SelectQueryBuilder`] - pub fn new( - table_schema_data: &str, - input: &'a I, - ) -> Result> { - Ok(Self { - _inner: QueryBuilder::new(format!("SELECT * FROM {table_schema_data}"), input)?, - }) - } - - /// Launches the generated query to the database pointed by the selected datasource - #[inline] - pub async fn query(self) -> Result, Box<(dyn Error + Send + Sync + 'a)>> - where - Vec: FromIterator<::Output>, - { - self._inner.query().await - } - - /// Adds a *LEFT JOIN* SQL statement to the underlying - /// `Sql Statement` held by the [`QueryBuilder`], where: - /// - /// * `join_table` - The table target of the join operation - /// * `col1` - The left side of the ON operator for the join - /// * `col2` - The right side of the ON operator for the join - /// - /// > Note: The order on the column parameters is irrelevant - pub fn left_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { - self._inner - .sql - .push_str(&format!(" LEFT JOIN {join_table} ON {col1} = {col2}")); - self - } - - /// Adds a *INNER JOIN* SQL statement to the underlying - /// `Sql Statement` held by the [`QueryBuilder`], where: - /// - /// * `join_table` - The table target of the join operation - /// * `col1` - The left side of the ON operator for the join - /// * `col2` - The right side of the ON operator for the join - /// - /// > Note: The order on the column parameters is irrelevant - pub fn inner_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { - self._inner - .sql - .push_str(&format!(" INNER JOIN {join_table} ON {col1} = {col2}")); - self - } - - /// Adds a *RIGHT JOIN* SQL statement to the underlying - /// `Sql Statement` held by the [`QueryBuilder`], where: - /// - /// * `join_table` - The table target of the join operation - /// * `col1` - The left side of the ON operator for the join - /// * `col2` - The right side of the ON operator for the join - /// - /// > Note: The order on the column parameters is irrelevant - pub fn right_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { - self._inner - .sql - .push_str(&format!(" RIGHT JOIN {join_table} ON {col1} = {col2}")); - self - } - - /// Adds a *FULL JOIN* SQL statement to the underlying - /// `Sql Statement` held by the [`QueryBuilder`], where: - /// - /// * `join_table` - The table target of the join operation - /// * `col1` - The left side of the ON operator for the join - /// * `col2` - The right side of the ON operator for the join - /// - /// > Note: The order on the column parameters is irrelevant - pub fn full_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { - self._inner - .sql - .push_str(&format!(" FULL JOIN {join_table} ON {col1} = {col2}")); - self - } -} - -impl<'a, I: DbConnection + ?Sized, R: RowMapper> ops::QueryBuilder<'a> - for SelectQueryBuilder<'a, I, R> -{ - #[inline] - fn read_sql(&'a self) -> &'a str { - self._inner.sql.as_str() - } - - #[inline(always)] - fn push_sql(mut self, sql: &str) { - self._inner.sql.push_str(sql); - } - - #[inline] - fn r#where>(mut self, r#where: Z, op: impl Operator) -> Self { - self._inner.r#where(r#where, op); - self - } - - #[inline] - fn and>(mut self, column: Z, op: impl Operator) -> Self { - self._inner.and(column, op); - self - } - - #[inline] - fn and_values_in(mut self, r#and: Z, values: &'a [Q]) -> Self - where - Z: FieldIdentifier, - Q: QueryParameter<'a>, - { - self._inner.and_values_in(and, values); - self - } - - #[inline] - fn or_values_in(mut self, r#and: Z, values: &'a [Q]) -> Self - where - Z: FieldIdentifier, - Q: QueryParameter<'a>, - { - self._inner.or_values_in(and, values); - self - } - - #[inline] - fn or>(mut self, column: Z, op: impl Operator) -> Self { - self._inner.or(column, op); - self - } - - #[inline] - fn order_by(mut self, order_by: Z, desc: bool) -> Self { - self._inner.order_by(order_by, desc); - self - } -} - -/// Contains the specific database operations of the *UPDATE* SQL statements. -/// -/// * `set` - To construct a new `SET` clause to determine the columns to -/// update with the provided values -pub struct UpdateQueryBuilder<'a, I: DbConnection + ?Sized, R: RowMapper> { - _inner: QueryBuilder<'a, I, R>, -} - -impl<'a, I: DbConnection + ?Sized, R: RowMapper> UpdateQueryBuilder<'a, I, R> { - /// Generates a new public instance of the [`UpdateQueryBuilder`] - pub fn new( - table_schema_data: &str, - input: &'a I, - ) -> Result> { - Ok(Self { - _inner: QueryBuilder::new(format!("UPDATE {table_schema_data}"), input)?, - }) - } - - /// Launches the generated query to the database pointed by the selected datasource - #[inline] - pub async fn query(self) -> Result, Box<(dyn Error + Send + Sync + 'a)>> - where - Vec: FromIterator<::Output>, - { - self._inner.query().await - } - - /// Creates an SQL `SET` clause to specify the columns that must be updated in the sentence - pub fn set(mut self, columns: &'a [(Z, Q)]) -> Self - where - Z: FieldIdentifier + Clone, - Q: QueryParameter<'a>, - { - if columns.is_empty() { - return self; - } - if self._inner.sql.contains("SET") { - panic!( - // TODO: this should return an Err and not panic! - "\n{}", - String::from("\t[PANIC!] - Don't use chained calls of the .set(...) method. ") - + "\n\tPass all the values in a unique call within the 'columns' " - + "array of tuples parameter\n" - ) - } - - let mut set_clause = String::new(); - set_clause.push_str(" SET "); - - for (idx, column) in columns.iter().enumerate() { - set_clause.push_str(&format!( - "{} = ${}", - column.0.as_str(), - self._inner.params.len() + 1 - )); - - if idx < columns.len() - 1 { - set_clause.push_str(", "); - } - self._inner.params.push(&column.1); - } - - self._inner.sql.push_str(&set_clause); - self - } -} - -impl<'a, I: DbConnection + ?Sized, R: RowMapper> ops::QueryBuilder<'a> - for UpdateQueryBuilder<'a, I, R> -{ - #[inline] - fn read_sql(&'a self) -> &'a str { - self._inner.sql.as_str() - } - - #[inline(always)] - fn push_sql(mut self, sql: &str) { - self._inner.sql.push_str(sql); - } - - #[inline] - fn r#where>(mut self, r#where: Z, op: impl Operator) -> Self { - self._inner.r#where(r#where, op); - self - } - - #[inline] - fn and>(mut self, column: Z, op: impl Operator) -> Self { - self._inner.and(column, op); - self - } - - #[inline] - fn and_values_in(mut self, r#and: Z, values: &'a [Q]) -> Self - where - Z: FieldIdentifier, - Q: QueryParameter<'a>, - { - self._inner.and_values_in(and, values); - self - } - - #[inline] - fn or_values_in(mut self, r#or: Z, values: &'a [Q]) -> Self - where - Z: FieldIdentifier, - Q: QueryParameter<'a>, - { - self._inner.or_values_in(or, values); - self - } - - #[inline] - fn or>(mut self, column: Z, op: impl Operator) -> Self { - self._inner.or(column, op); - self - } - - #[inline] - fn order_by(mut self, order_by: Z, desc: bool) -> Self { - self._inner.order_by(order_by, desc); - self - } -} - -/// Contains the specific database operations associated with the -/// *DELETE* SQL statements. -/// -/// * `set` - To construct a new `SET` clause to determine the columns to -/// update with the provided values -pub struct DeleteQueryBuilder<'a, I: DbConnection + ?Sized, R: RowMapper> { - _inner: QueryBuilder<'a, I, R>, -} - -impl<'a, I: DbConnection + ?Sized, R: RowMapper> DeleteQueryBuilder<'a, I, R> { - /// Generates a new public instance of the [`DeleteQueryBuilder`] - pub fn new( - table_schema_data: &str, - input: &'a I, - ) -> Result> { - Ok(Self { - _inner: QueryBuilder::new(format!("DELETE FROM {table_schema_data}"), input)?, - }) - } - - /// Launches the generated query to the database pointed by the selected datasource - #[inline] - pub async fn query(self) -> Result, Box<(dyn Error + Send + Sync + 'a)>> - where - Vec: FromIterator<::Output>, - { - self._inner.query().await - } -} - -impl<'a, I: DbConnection + ?Sized, R: RowMapper> ops::QueryBuilder<'a> - for DeleteQueryBuilder<'a, I, R> -{ - #[inline] - fn read_sql(&'a self) -> &'a str { - self._inner.sql.as_str() - } - - #[inline(always)] - fn push_sql(mut self, sql: &str) { - self._inner.sql.push_str(sql); - } - - #[inline] - fn r#where>(mut self, r#where: Z, op: impl Operator) -> Self { - self._inner.r#where(r#where, op); - self - } - - #[inline] - fn and>(mut self, column: Z, op: impl Operator) -> Self { - self._inner.and(column, op); - self - } - - #[inline] - fn and_values_in(mut self, r#and: Z, values: &'a [Q]) -> Self - where - Z: FieldIdentifier, - Q: QueryParameter<'a>, - { - self._inner.or_values_in(and, values); - self - } - - #[inline] - fn or_values_in(mut self, r#or: Z, values: &'a [Q]) -> Self - where - Z: FieldIdentifier, - Q: QueryParameter<'a>, - { - self._inner.or_values_in(or, values); - self - } - - #[inline] - fn or>(mut self, column: Z, op: impl Operator) -> Self { - self._inner.or(column, op); - self - } - - #[inline] - fn order_by(mut self, order_by: Z, desc: bool) -> Self { - self._inner.order_by(order_by, desc); - self - } -} diff --git a/canyon_entities/src/manager_builder.rs b/canyon_entities/src/manager_builder.rs index 7362268a..858a1a5f 100644 --- a/canyon_entities/src/manager_builder.rs +++ b/canyon_entities/src/manager_builder.rs @@ -76,7 +76,7 @@ pub fn generate_enum_with_fields(canyon_entity: &CanyonEntity) -> TokenStream { #(#fields_names),* } - impl #generics canyon_sql::crud::bounds::FieldIdentifier for #generics #enum_name #generics { + impl #generics canyon_sql::query::bounds::FieldIdentifier for #generics #enum_name #generics { fn as_str(&self) -> &'static str { match *self { #(#match_arms_str),* @@ -127,7 +127,7 @@ pub fn generate_enum_with_fields_values(canyon_entity: &CanyonEntity) -> TokenSt #(#fields_names),* } - impl<'a> canyon_sql::crud::bounds::FieldValueIdentifier<'a> for #enum_name<'a> { + impl<'a> canyon_sql::query::bounds::FieldValueIdentifier<'a> for #enum_name<'a> { fn value(self) -> (&'static str, &'a dyn QueryParameter<'a>) { match self { #(#match_arms),* diff --git a/canyon_macros/src/foreignkeyable_macro.rs b/canyon_macros/src/foreignkeyable_macro.rs index a3415ec5..72251d18 100644 --- a/canyon_macros/src/foreignkeyable_macro.rs +++ b/canyon_macros/src/foreignkeyable_macro.rs @@ -26,7 +26,7 @@ pub fn foreignkeyable_impl_tokens(ast: DeriveInput) -> TokenStream { quote! { /// Implementation of the trait `ForeignKeyable` for the type /// calling this derive proc macro - impl canyon_sql::crud::bounds::ForeignKeyable for #ty { + impl canyon_sql::query::bounds::ForeignKeyable for #ty { fn get_fk_column(&self, column: &str) -> Option<&dyn canyon_sql::core::QueryParameter<'_>> { match column { #(#field_idents),*, @@ -36,7 +36,7 @@ pub fn foreignkeyable_impl_tokens(ast: DeriveInput) -> TokenStream { } /// Implementation of the trait `ForeignKeyable` for a reference of this type /// calling this derive proc macro - impl canyon_sql::crud::bounds::ForeignKeyable<&Self> for &#ty { + impl canyon_sql::query::bounds::ForeignKeyable<&Self> for &#ty { fn get_fk_column<'a>(&self, column: &'a str) -> Option<&dyn canyon_sql::core::QueryParameter<'_>> { match column { #(#field_idents_cloned),*, diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index 904adff7..92a9cc14 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -72,7 +72,7 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri /// [`query_elements::query_builder::QueryBuilder<'a, #ty>`] fn generate_delete_querybuilder_tokens(ty: &Ident, table_schema_data: &str) -> TokenStream { quote! { - /// Generates a [`canyon_sql::query::DeleteQueryBuilder`] + /// Generates a [`canyon_sql::query::querybuilder::DeleteQueryBuilder`] /// that allows you to customize the query by adding parameters and constrains dynamically. /// /// It performs an `DELETE FROM table_name`, where `table_name` it's the name of your @@ -80,12 +80,12 @@ fn generate_delete_querybuilder_tokens(ty: &Ident, table_schema_data: &str) -> T /// unless concrete values are set on the available parameters of the /// `canyon_macro(table_name = "table_name", schema = "schema")` fn delete_query<'a>() -> Result< - canyon_sql::query::DeleteQueryBuilder<'a, str, #ty>, + canyon_sql::query::querybuilder::DeleteQueryBuilder<'a, str, #ty>, Box<(dyn std::error::Error + Send + Sync + 'a)>> { - canyon_sql::query::DeleteQueryBuilder::new(#table_schema_data, "") + canyon_sql::query::querybuilder::DeleteQueryBuilder::new(#table_schema_data, "") } - /// Generates a [`canyon_sql::query::DeleteQueryBuilder`] + /// Generates a [`canyon_sql::query::querybuilder::DeleteQueryBuilder`] /// that allows you to customize the query by adding parameters and constrains dynamically. /// /// It performs an `DELETE FROM table_name`, where `table_name` it's the name of your @@ -96,11 +96,11 @@ fn generate_delete_querybuilder_tokens(ty: &Ident, table_schema_data: &str) -> T /// The query it's made against the database with the configured datasource /// described in the configuration file, selected with the input parameter fn delete_query_with<'a, I>(input: &'a I) -> Result< - canyon_sql::query::DeleteQueryBuilder<'a, I, #ty>, + canyon_sql::query::querybuilder::DeleteQueryBuilder<'a, I, #ty>, Box<(dyn std::error::Error + Send + Sync + 'a)> > where I: canyon_sql::core::DbConnection + Send + 'a + ?Sized { - canyon_sql::query::DeleteQueryBuilder::new(#table_schema_data, input) + canyon_sql::query::querybuilder::DeleteQueryBuilder::new(#table_schema_data, input) } } } diff --git a/canyon_macros/src/query_operations/doc_comments.rs b/canyon_macros/src/query_operations/doc_comments.rs index 14f8dd7a..6f6e5131 100644 --- a/canyon_macros/src/query_operations/doc_comments.rs +++ b/canyon_macros/src/query_operations/doc_comments.rs @@ -7,7 +7,7 @@ pub const SELECT_ALL_BASE_DOC_COMMENT: &str = with snake_case identifiers."; pub const SELECT_QUERYBUILDER_DOC_COMMENT: &str = - "Generates a [`canyon_sql::query::SelectQueryBuilder`] \ + "Generates a [`canyon_sql::query::querybuilder::SelectQueryBuilder`] \ that allows you to customize the query by adding parameters and constrains dynamically. \ \ It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your \ diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index d7586a9f..441d1915 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -160,13 +160,13 @@ fn generate_find_by_reverse_foreign_key_tokens( async fn #method_name_ident<'a, F>(value: &F) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> where - F: canyon_sql::crud::bounds::ForeignKeyable + Send + Sync + F: canyon_sql::query::bounds::ForeignKeyable + Send + Sync }; let quoted_with_method_signature: TokenStream = quote! { async fn #method_name_ident_with<'a, F, I> (value: &F, input: I) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> where - F: canyon_sql::crud::bounds::ForeignKeyable + Send + Sync, + F: canyon_sql::query::bounds::ForeignKeyable + Send + Sync, I: canyon_sql::core::DbConnection + Send + 'a }; diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 2d8261f4..adcf1f06 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -34,6 +34,7 @@ fn generate_find_all_operations_tokens( table_schema_data: &String, ) -> TokenStream { let fa_stmt = format!("SELECT * FROM {table_schema_data}"); + // TODO: bring the helper and convert the SELECT * into the SELECT col_name, col_name2...? // TODO: remember that this queries statements must be autogenerated by some automatic procedure @@ -52,7 +53,7 @@ fn generate_select_querybuilder_tokens( table_schema_data: &String, ) -> TokenStream { quote! { - /// Generates a [`canyon_sql::query::SelectQueryBuilder`] + /// Generates a [`canyon_sql::query::querybuilder::SelectQueryBuilder`] /// that allows you to customize the query by adding parameters and constrains dynamically. /// /// It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your @@ -61,14 +62,14 @@ fn generate_select_querybuilder_tokens( /// `canyon_macro(table_name = "table_name", schema = "schema")` fn select_query<'a>() -> Result< - canyon_sql::query::SelectQueryBuilder<'a, str, #mapper_ty>, + canyon_sql::query::querybuilder::SelectQueryBuilder<'a, str, #mapper_ty>, Box<(dyn std::error::Error + Send + Sync + 'a)> > { - canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, &"") + canyon_sql::query::querybuilder::SelectQueryBuilder::new(#table_schema_data, &"") } - /// Generates a [`canyon_sql::query::SelectQueryBuilder`] + /// Generates a [`canyon_sql::query::querybuilder::SelectQueryBuilder`] /// that allows you to customize the query by adding parameters and constrains dynamically. /// /// It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your @@ -81,11 +82,11 @@ fn generate_select_querybuilder_tokens( /// passed as parameter. fn select_query_with<'a, I>(input: &'a I) -> Result< - canyon_sql::query::SelectQueryBuilder<'a, I, #mapper_ty>, + canyon_sql::query::querybuilder::SelectQueryBuilder<'a, I, #mapper_ty>, Box<(dyn std::error::Error + Send + Sync + 'a)> > where I: canyon_sql::core::DbConnection + Send + 'a + ?Sized { - canyon_sql::query::SelectQueryBuilder::new(#table_schema_data, input) + canyon_sql::query::querybuilder::SelectQueryBuilder::new(#table_schema_data, input) } } } diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index a27a4068..777a55c3 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -83,7 +83,7 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri /// being the query generated with the [`QueryBuilder`] fn generate_update_querybuilder_tokens(ty: &Ident, table_schema_data: &String) -> TokenStream { quote! { - /// Generates a [`canyon_sql::query::UpdateQueryBuilder`] + /// Generates a [`canyon_sql::query::querybuilder::UpdateQueryBuilder`] /// that allows you to customize the query by adding parameters and constrains dynamically. /// /// It performs an `UPDATE table_name`, where `table_name` it's the name of your @@ -91,12 +91,12 @@ fn generate_update_querybuilder_tokens(ty: &Ident, table_schema_data: &String) - /// unless concrete values are set on the available parameters of the /// `canyon_macro(table_name = "table_name", schema = "schema")` fn update_query<'a>() -> Result< - canyon_sql::query::UpdateQueryBuilder<'a, str, #ty>, + canyon_sql::query::querybuilder::UpdateQueryBuilder<'a, str, #ty>, Box<(dyn std::error::Error + Send + Sync + 'a)>> { - canyon_sql::query::UpdateQueryBuilder::new(#table_schema_data, "") + canyon_sql::query::querybuilder::UpdateQueryBuilder::new(#table_schema_data, "") } - /// Generates a [`canyon_sql::query::UpdateQueryBuilder`] + /// Generates a [`canyon_sql::query::querybuilder::UpdateQueryBuilder`] /// that allows you to customize the query by adding parameters and constrains dynamically. /// /// It performs an `UPDATE table_name`, where `table_name` it's the name of your @@ -107,11 +107,11 @@ fn generate_update_querybuilder_tokens(ty: &Ident, table_schema_data: &String) - /// The query it's made against the database with the configured datasource /// described in the configuration file, and selected with the input parameter fn update_query_with<'a, I>(input: &'a I) -> Result< - canyon_sql::query::UpdateQueryBuilder<'a, I, #ty>, + canyon_sql::query::querybuilder::UpdateQueryBuilder<'a, I, #ty>, Box<(dyn std::error::Error + Send + Sync + 'a)> > where I: canyon_sql::core::DbConnection + Send + 'a + ?Sized { - canyon_sql::query::UpdateQueryBuilder::new(#table_schema_data, input) + canyon_sql::query::querybuilder::UpdateQueryBuilder::new(#table_schema_data, input) } } } diff --git a/src/lib.rs b/src/lib.rs index fc6ad028..dc8cae8d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,9 +33,9 @@ pub mod connection { pub mod core { pub use canyon_core::canyon::Canyon; - pub use canyon_core::connection::contracts::DbConnection; + pub use canyon_core::connection::contracts::DbConnection; // TODO: Available only via connection? pub use canyon_core::mapper::*; - pub use canyon_core::query_parameters::QueryParameter; + pub use canyon_core::query::parameters::QueryParameter; // TODO: this re-export must be only available on pub mod query pub use canyon_core::rows::CanyonRows; pub use canyon_core::transaction::Transaction; } @@ -43,14 +43,14 @@ pub mod core { /// Crud module serves to reexport the public elements of the `canyon_crud` crate, /// exposing them through the public API pub mod crud { - pub use canyon_crud::bounds; pub use canyon_crud::crud::*; } /// Re-exports the query elements from the `crud`crate pub mod query { - pub use canyon_crud::query_elements::operators; - pub use canyon_crud::query_elements::{query::*, query_builder::*}; + pub use canyon_core::query::bounds; + pub use canyon_core::query::operators; + pub use canyon_core::query::*; } /// Reexport the available database clients within Canyon diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index a8dd56c3..6dcb1409 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -2,6 +2,13 @@ use crate::constants::MYSQL_DS; #[cfg(feature = "mssql")] use crate::constants::SQL_SERVER_DS; +/// Tests for the QueryBuilder available operations within Canyon. +/// +/// QueryBuilder are the way of obtain more flexibility that with +/// the default generated queries, essentially for build the queries +/// with the SQL filters +/// +use canyon_sql::query::operators::{Comp, Like}; /// Tests for the QueryBuilder available operations within Canyon. /// @@ -11,7 +18,9 @@ use crate::constants::SQL_SERVER_DS; /// use canyon_sql::{ crud::CrudOperations, - query::{operators::Comp, operators::Like, ops::QueryBuilder}, + query::querybuilder::{ + QueryBuilder, QueryBuilderOps, SelectQueryBuilderOps, UpdateQueryBuilderOps, + }, }; use crate::tests_models::league::*; From e8c8a3c4b8ab65079045f0974eaa182656e7b57d Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 8 May 2025 16:02:50 +0200 Subject: [PATCH 108/155] feat: new type Query for having a fluent builder process from QueryBuilder -> Query Query is the DbConnection type now --- canyon_core/src/canyon.rs | 81 ++++++++--- canyon_core/src/connection/database_type.rs | 12 ++ canyon_core/src/query/query.rs | 40 +++++- .../src/query/querybuilder/impl/delete.rs | 11 +- .../src/query/querybuilder/impl/mod.rs | 101 ------------- .../src/query/querybuilder/impl/select.rs | 10 +- .../src/query/querybuilder/impl/update.rs | 10 +- .../src/query/querybuilder/types/delete.rs | 23 ++- .../src/query/querybuilder/types/mod.rs | 133 +++++++++++++++--- .../src/query/querybuilder/types/select.rs | 23 ++- .../src/query/querybuilder/types/update.rs | 26 ++-- canyon_crud/src/crud.rs | 34 ++--- canyon_entities/src/manager_builder.rs | 4 +- canyon_macros/src/query_operations/delete.rs | 23 +-- canyon_macros/src/query_operations/read.rs | 28 ++-- canyon_macros/src/query_operations/update.rs | 22 +-- tests/crud/querybuilder_operations.rs | 95 ++++++++----- 17 files changed, 364 insertions(+), 312 deletions(-) diff --git a/canyon_core/src/canyon.rs b/canyon_core/src/canyon.rs index 1ae62824..431e723e 100644 --- a/canyon_core/src/canyon.rs +++ b/canyon_core/src/canyon.rs @@ -1,6 +1,5 @@ -// ...existing code... - use crate::connection::conn_errors::DatasourceNotFound; +use crate::connection::database_type::DatabaseType; use crate::connection::datasources::{CanyonSqlConfig, DatasourceConfig, Datasources}; use crate::connection::{db_connector, get_canyon_tokio_runtime, CANYON_INSTANCE}; use db_connector::DatabaseConnection; @@ -58,6 +57,7 @@ pub struct Canyon { config: Datasources, connections: HashMap<&'static str, SharedConnection>, default: Option, + default_db_type: Option, } impl Canyon { @@ -83,23 +83,23 @@ impl Canyon { let mut connections = HashMap::new(); let mut default = None; + let mut default_db_type = None; for ds in config.datasources.iter() { - let conn = DatabaseConnection::new(ds).await?; - let name: &'static str = Box::leak(ds.name.clone().into_boxed_str()); - let conn = Arc::new(Mutex::new(conn)); - - if default.is_none() { - default = Some(conn.clone()); // Only cloning the smart pointer - } - - connections.insert(name, conn); + __impl::process_new_conn_by_datasource( + ds, + &mut connections, + &mut default, + &mut default_db_type, + ) + .await?; } let canyon = Canyon { config, connections, default, + default_db_type, }; get_canyon_tokio_runtime(); // Just ensuring that is initialized in manual-mode @@ -152,18 +152,30 @@ impl Canyon { } } + pub fn get_default_db_type(&self) -> Result { + self.default_db_type + .ok_or_else(|| DatasourceNotFound::from(None)) + } + + // Retrieve a read-only connection from the cache + pub async fn get_default_connection( + &self, + ) -> Result, DatasourceNotFound> { + Ok(self + .default + .as_ref() + .ok_or_else(|| DatasourceNotFound::from(None))? + .lock() + .await) + } + // Retrieve a read-only connection from the cache pub async fn get_connection( &self, name: &str, ) -> Result, DatasourceNotFound> { if name.is_empty() { - return Ok(self - .default - .as_ref() - .ok_or_else(|| DatasourceNotFound::from(None))? - .lock() - .await); + return self.get_default_connection().await; } let conn = self @@ -182,3 +194,38 @@ impl Canyon { self.get_connection(name).await } } + +mod __impl { + use crate::canyon::SharedConnection; + use crate::connection::database_type::DatabaseType; + use crate::connection::datasources::DatasourceConfig; + use crate::connection::db_connector::DatabaseConnection; + use std::collections::HashMap; + use std::error::Error; + use std::sync::Arc; + use tokio::sync::Mutex; + + pub(crate) async fn process_new_conn_by_datasource( + ds: &DatasourceConfig, + connections: &mut HashMap<&str, SharedConnection>, + default: &mut Option, + default_db_type: &mut Option, + ) -> Result<(), Box> { + let conn = DatabaseConnection::new(ds).await?; + let name: &'static str = Box::leak(ds.name.clone().into_boxed_str()); + + if default_db_type.is_none() { + *default_db_type = Some(conn.get_db_type()); + } + + let connection_sp = Arc::new(Mutex::new(conn)); + + if default.is_none() { + *default = Some(connection_sp.clone()); // Only cloning the smart pointer + } + + connections.insert(name, connection_sp); + + Ok(()) + } +} diff --git a/canyon_core/src/connection/database_type.rs b/canyon_core/src/connection/database_type.rs index 251e1af1..d7cb595f 100644 --- a/canyon_core/src/connection/database_type.rs +++ b/canyon_core/src/connection/database_type.rs @@ -1,5 +1,7 @@ use super::datasources::Auth; +use crate::canyon::Canyon; use serde::Deserialize; +use std::error::Error; /// Holds the current supported databases by Canyon-SQL #[derive(Deserialize, Debug, Eq, PartialEq, Clone, Copy)] @@ -20,3 +22,13 @@ impl From<&Auth> for DatabaseType { value.get_db_type() } } + +/// The default implementation for [`DatabaseType`] returns the database type for the first +/// datasource configured +impl DatabaseType { + pub fn default_type() -> Result> { + Canyon::instance()? + .get_default_db_type() + .map_err(|err| Box::new(err) as Box) + } +} diff --git a/canyon_core/src/query/query.rs b/canyon_core/src/query/query.rs index c6bd753c..76ac9678 100644 --- a/canyon_core/src/query/query.rs +++ b/canyon_core/src/query/query.rs @@ -1,7 +1,11 @@ -use std::fmt::Debug; - +use crate::canyon::Canyon; +use crate::connection::contracts::DbConnection; +use crate::mapper::RowMapper; use crate::query::parameters::QueryParameter; - +use crate::transaction::Transaction; +use std::error::Error; +use std::fmt::Debug; +use std::ops::DerefMut; // TODO: all the query works here // TODO: exports things like Select::... where receives the table // name and prepares the raw query (maybe with const_format!) for improved performance @@ -9,7 +13,11 @@ use crate::query::parameters::QueryParameter; // TODO: query should implement ToStatement (as the drivers underneath Canyon) or similar // to be usable directly in the input of Transaction and DbConnenction /// Holds a sql sentence details -#[derive(Debug, Clone)] +/// +/// Plan: The MacroTokens struct gets some generic bounds to retrieve the fields names at compile +/// time (already does it) and the querybuilder uses it with const_format to introduce the names of the +/// columns instead of just using * (in this case, is the same, unless we introduce new annotations like #[skip_mapping] +#[derive(Debug)] pub struct Query<'a> { pub sql: String, pub params: Vec<&'a dyn QueryParameter<'a>>, @@ -22,4 +30,28 @@ impl<'a> Query<'a> { params: vec![], } } + + /// Launches the generated query against the database assuming the default + /// [`DbConnection`] + pub async fn launch_default( + self, + ) -> Result, Box<(dyn Error + Send + Sync + 'a)>> + where + Vec: FromIterator<::Output>, + { + let mut input = Canyon::instance()?.get_default_connection().await?; + ::query(&self.sql, &self.params, input.deref_mut()).await + } + + /// Launches the generated query against the database with the selected [`DbConnection`] + pub async fn launch_with( + self, + input: I, + ) -> Result, Box<(dyn Error + Send + Sync + 'a)>> + where + Vec: FromIterator<::Output>, + { + input.query(&self.sql, &self.params).await + } } + diff --git a/canyon_core/src/query/querybuilder/impl/delete.rs b/canyon_core/src/query/querybuilder/impl/delete.rs index 7e8b48fc..ceda2349 100644 --- a/canyon_core/src/query/querybuilder/impl/delete.rs +++ b/canyon_core/src/query/querybuilder/impl/delete.rs @@ -1,19 +1,12 @@ -use crate::connection::contracts::DbConnection; -use crate::mapper::RowMapper; use crate::query::bounds::{FieldIdentifier, FieldValueIdentifier}; use crate::query::operators::Operator; use crate::query::parameters::QueryParameter; use crate::query::querybuilder::contracts::{DeleteQueryBuilderOps, QueryBuilderOps}; use crate::query::querybuilder::types::delete::DeleteQueryBuilder; -impl<'a, I: DbConnection + ?Sized, R: RowMapper> DeleteQueryBuilderOps<'a> - for DeleteQueryBuilder<'a, I, R> -{ -} // NOTE: for now, this is just a type formalism +impl<'a> DeleteQueryBuilderOps<'a> for DeleteQueryBuilder<'a> {} // NOTE: for now, this is just a type formalism -impl<'a, I: DbConnection + ?Sized, R: RowMapper> QueryBuilderOps<'a> - for DeleteQueryBuilder<'a, I, R> -{ +impl<'a> QueryBuilderOps<'a> for DeleteQueryBuilder<'a> { #[inline] fn read_sql(&'a self) -> &'a str { self._inner.sql.as_str() diff --git a/canyon_core/src/query/querybuilder/impl/mod.rs b/canyon_core/src/query/querybuilder/impl/mod.rs index dab7aeb5..e3d1b76e 100644 --- a/canyon_core/src/query/querybuilder/impl/mod.rs +++ b/canyon_core/src/query/querybuilder/impl/mod.rs @@ -1,106 +1,5 @@ -use crate::connection::contracts::DbConnection; -use crate::mapper::RowMapper; -use crate::query::bounds::{FieldIdentifier, FieldValueIdentifier}; -use crate::query::operators::Operator; -use crate::query::parameters::QueryParameter; pub(crate) use crate::query::querybuilder::QueryBuilder; mod delete; mod select; mod update; - -impl<'a, I: DbConnection + ?Sized, R: RowMapper> QueryBuilder<'a, I, R> { - fn r#where>(&mut self, r#where: Z, op: impl Operator) { - let (column_name, value) = r#where.value(); - - let where_ = String::from(" WHERE ") - + column_name - + &op.as_str(self.params.len() + 1, &self.database_type); - - self.sql.push_str(&where_); - self.params.push(value); - } - - fn and>(&mut self, r#and: Z, op: impl Operator) { - let (column_name, value) = r#and.value(); - - let and_ = String::from(" AND ") - + column_name - + &op.as_str(self.params.len() + 1, &self.database_type); - - self.sql.push_str(&and_); - self.params.push(value); - } - - fn and_values_in(&mut self, r#and: Z, values: &'a [Q]) - where - Z: FieldIdentifier, - Q: QueryParameter<'a>, - { - if values.is_empty() { - return; - } - - self.sql.push_str(&format!(" AND {} IN (", r#and.as_str())); - - let mut counter = 1; - values.iter().for_each(|qp| { - if values.len() != counter { - self.sql.push_str(&format!("${}, ", self.params.len())); - counter += 1; - } else { - self.sql.push_str(&format!("${}", self.params.len())); - } - self.params.push(qp) - }); - - self.sql.push(')'); - } - - fn or_values_in(&mut self, r#or: Z, values: &'a [Q]) - where - Z: FieldIdentifier, - Q: QueryParameter<'a>, - { - if values.is_empty() { - return; - } - - self.sql.push_str(&format!(" OR {} IN (", r#or.as_str())); - - let mut counter = 1; - values.iter().for_each(|qp| { - if values.len() != counter { - self.sql.push_str(&format!("${}, ", self.params.len())); - counter += 1; - } else { - self.sql.push_str(&format!("${}", self.params.len())); - } - self.params.push(qp) - }); - - self.sql.push(')'); - } - - fn or>(&mut self, r#and: Z, op: impl Operator) { - let (column_name, value) = r#and.value(); - - let and_ = String::from(" OR ") - + column_name - + &op.as_str(self.params.len() + 1, &self.database_type); - - self.sql.push_str(&and_); - self.params.push(value); - } - - #[inline] - fn order_by(&mut self, order_by: Z, desc: bool) { - self.sql.push_str( - &(format!( - " ORDER BY {}{}", - order_by.as_str(), - if desc { " DESC " } else { "" } - )), - ); - } -} diff --git a/canyon_core/src/query/querybuilder/impl/select.rs b/canyon_core/src/query/querybuilder/impl/select.rs index 647fd055..3a1c4a64 100644 --- a/canyon_core/src/query/querybuilder/impl/select.rs +++ b/canyon_core/src/query/querybuilder/impl/select.rs @@ -1,14 +1,10 @@ -use crate::connection::contracts::DbConnection; -use crate::mapper::RowMapper; use crate::query::bounds::{FieldIdentifier, FieldValueIdentifier}; use crate::query::operators::Operator; use crate::query::parameters::QueryParameter; use crate::query::querybuilder::contracts::{QueryBuilderOps, SelectQueryBuilderOps}; use crate::query::querybuilder::types::select::SelectQueryBuilder; -impl<'a, I: DbConnection + ?Sized, R: RowMapper> SelectQueryBuilderOps<'a> - for SelectQueryBuilder<'a, I, R> -{ +impl<'a> SelectQueryBuilderOps<'a> for SelectQueryBuilder<'a> { fn left_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { self._inner .sql @@ -38,9 +34,7 @@ impl<'a, I: DbConnection + ?Sized, R: RowMapper> SelectQueryBuilderOps<'a> } } -impl<'a, I: DbConnection + ?Sized, R: RowMapper> QueryBuilderOps<'a> - for SelectQueryBuilder<'a, I, R> -{ +impl<'a> QueryBuilderOps<'a> for SelectQueryBuilder<'a> { #[inline] fn read_sql(&'a self) -> &'a str { self._inner.sql.as_str() diff --git a/canyon_core/src/query/querybuilder/impl/update.rs b/canyon_core/src/query/querybuilder/impl/update.rs index e2c0aa5d..6e684c48 100644 --- a/canyon_core/src/query/querybuilder/impl/update.rs +++ b/canyon_core/src/query/querybuilder/impl/update.rs @@ -1,14 +1,10 @@ -use crate::connection::contracts::DbConnection; -use crate::mapper::RowMapper; use crate::query::bounds::{FieldIdentifier, FieldValueIdentifier}; use crate::query::operators::Operator; use crate::query::parameters::QueryParameter; use crate::query::querybuilder::contracts::{QueryBuilderOps, UpdateQueryBuilderOps}; use crate::query::querybuilder::types::update::UpdateQueryBuilder; -impl<'a, I: DbConnection + ?Sized, R: RowMapper> UpdateQueryBuilderOps<'a> - for UpdateQueryBuilder<'a, I, R> -{ +impl<'a> UpdateQueryBuilderOps<'a> for UpdateQueryBuilder<'a> { /// Creates an SQL `SET` clause to specify the columns that must be updated in the sentence fn set(mut self, columns: &'a [(Z, Q)]) -> Self where @@ -50,9 +46,7 @@ impl<'a, I: DbConnection + ?Sized, R: RowMapper> UpdateQueryBuilderOps<'a> } } -impl<'a, I: DbConnection + ?Sized, R: RowMapper> QueryBuilderOps<'a> - for UpdateQueryBuilder<'a, I, R> -{ +impl<'a> QueryBuilderOps<'a> for UpdateQueryBuilder<'a> { #[inline] fn read_sql(&'a self) -> &'a str { self._inner.sql.as_str() diff --git a/canyon_core/src/query/querybuilder/types/delete.rs b/canyon_core/src/query/querybuilder/types/delete.rs index 8bffd2a3..dd0b9e86 100644 --- a/canyon_core/src/query/querybuilder/types/delete.rs +++ b/canyon_core/src/query/querybuilder/types/delete.rs @@ -1,5 +1,5 @@ -use crate::connection::contracts::DbConnection; -use crate::mapper::RowMapper; +use crate::connection::database_type::DatabaseType; +use crate::query::query::Query; use crate::query::querybuilder::r#impl::QueryBuilder; use std::error::Error; @@ -8,27 +8,22 @@ use std::error::Error; /// /// * `set` - To construct a new `SET` clause to determine the columns to /// update with the provided values -pub struct DeleteQueryBuilder<'a, I: DbConnection + ?Sized, R: RowMapper> { - pub(crate) _inner: QueryBuilder<'a, I, R>, +pub struct DeleteQueryBuilder<'a> { + pub(crate) _inner: QueryBuilder<'a>, } -impl<'a, I: DbConnection + ?Sized, R: RowMapper> DeleteQueryBuilder<'a, I, R> { +impl<'a> DeleteQueryBuilder<'a> { /// Generates a new public instance of the [`DeleteQueryBuilder`] pub fn new( table_schema_data: &str, - input: &'a I, + database_type: DatabaseType, ) -> Result> { Ok(Self { - _inner: QueryBuilder::new(format!("DELETE FROM {table_schema_data}"), input)?, + _inner: QueryBuilder::new(format!("DELETE FROM {table_schema_data}"), database_type)?, }) } - /// Launches the generated query to the database pointed by the selected datasource - #[inline] - pub async fn query(self) -> Result, Box<(dyn Error + Send + Sync + 'a)>> - where - Vec: FromIterator<::Output>, - { - self._inner.query().await + pub fn build(self) -> Result, Box> { + self._inner.build() } } diff --git a/canyon_core/src/query/querybuilder/types/mod.rs b/canyon_core/src/query/querybuilder/types/mod.rs index b8774e11..7332eea1 100644 --- a/canyon_core/src/query/querybuilder/types/mod.rs +++ b/canyon_core/src/query/querybuilder/types/mod.rs @@ -3,44 +3,135 @@ pub mod select; pub mod update; pub use self::{delete::*, select::*, update::*}; -use crate::connection::contracts::DbConnection; use crate::connection::database_type::DatabaseType; -use crate::mapper::RowMapper; +use crate::query::bounds::{FieldIdentifier, FieldValueIdentifier}; +use crate::query::operators::Operator; use crate::query::parameters::QueryParameter; +use crate::query::query::Query; use std::error::Error; -use std::marker::PhantomData; /// Type for construct more complex queries than the classical CRUD ones. -pub struct QueryBuilder<'a, I: DbConnection + ?Sized, R: RowMapper> { - // query: Query<'a>, +pub struct QueryBuilder<'a> { pub(crate) sql: String, pub(crate) params: Vec<&'a dyn QueryParameter<'a>>, pub(crate) database_type: DatabaseType, - pub(crate) input: &'a I, - pd: PhantomData, } -unsafe impl Send for QueryBuilder<'_, I, R> {} -unsafe impl Sync for QueryBuilder<'_, I, R> {} +unsafe impl Send for QueryBuilder<'_> {} +unsafe impl Sync for QueryBuilder<'_> {} -impl<'a, I: DbConnection + ?Sized, R: RowMapper> QueryBuilder<'a, I, R> { - pub fn new(sql: String, input: &'a I) -> Result> { +impl<'a> QueryBuilder<'a> { + pub fn new( + sql: String, + database_type: DatabaseType, + ) -> Result> { Ok(Self { sql, - params: vec![], - database_type: input.get_database_type()?, - input, - pd: Default::default(), + params: vec![], // TODO: as option? and then match it for emptyness and pass &[] if possible? + database_type, }) } - /// Launches the generated query against the database targeted - /// by the selected datasource - pub async fn query(mut self) -> Result, Box<(dyn Error + Send + Sync + 'a)>> + pub fn build(mut self) -> Result, Box<(dyn Error + Send + Sync)>> { + // TODO: here we should check for our invariants + self.sql.push(';'); + Ok(Query { + sql: self.sql, + params: self.params, + }) + } + + pub fn r#where>(&mut self, r#where: Z, op: impl Operator) { + let (column_name, value) = r#where.value(); + + let where_ = String::from(" WHERE ") + + column_name + + &op.as_str(self.params.len() + 1, &self.database_type); + + self.sql.push_str(&where_); + self.params.push(value); + } + + pub fn and>(&mut self, r#and: Z, op: impl Operator) { + let (column_name, value) = r#and.value(); + + let and_ = String::from(" AND ") + + column_name + + &op.as_str(self.params.len() + 1, &self.database_type); + + self.sql.push_str(&and_); + self.params.push(value); + } + + pub fn and_values_in(&mut self, r#and: Z, values: &'a [Q]) where - Vec: FromIterator<::Output>, + Z: FieldIdentifier, + Q: QueryParameter<'a>, { - self.sql.push(';'); - self.input.query(&self.sql, &self.params).await + if values.is_empty() { + return; + } + + self.sql.push_str(&format!(" AND {} IN (", r#and.as_str())); + + let mut counter = 1; + values.iter().for_each(|qp| { + if values.len() != counter { + self.sql.push_str(&format!("${}, ", self.params.len())); + counter += 1; + } else { + self.sql.push_str(&format!("${}", self.params.len())); + } + self.params.push(qp) + }); + + self.sql.push(')'); + } + + pub fn or_values_in(&mut self, r#or: Z, values: &'a [Q]) + where + Z: FieldIdentifier, + Q: QueryParameter<'a>, + { + if values.is_empty() { + return; + } + + self.sql.push_str(&format!(" OR {} IN (", r#or.as_str())); + + let mut counter = 1; + values.iter().for_each(|qp| { + if values.len() != counter { + self.sql.push_str(&format!("${}, ", self.params.len())); + counter += 1; + } else { + self.sql.push_str(&format!("${}", self.params.len())); + } + self.params.push(qp) + }); + + self.sql.push(')'); + } + + pub fn or>(&mut self, r#and: Z, op: impl Operator) { + let (column_name, value) = r#and.value(); + + let and_ = String::from(" OR ") + + column_name + + &op.as_str(self.params.len() + 1, &self.database_type); + + self.sql.push_str(&and_); + self.params.push(value); + } + + #[inline] + pub fn order_by(&mut self, order_by: Z, desc: bool) { + self.sql.push_str( + &(format!( + " ORDER BY {}{}", + order_by.as_str(), + if desc { " DESC " } else { "" } + )), + ); } } diff --git a/canyon_core/src/query/querybuilder/types/select.rs b/canyon_core/src/query/querybuilder/types/select.rs index 25b21aea..851723ae 100644 --- a/canyon_core/src/query/querybuilder/types/select.rs +++ b/canyon_core/src/query/querybuilder/types/select.rs @@ -1,29 +1,24 @@ -use crate::connection::contracts::DbConnection; -use crate::mapper::RowMapper; +use crate::connection::database_type::DatabaseType; +use crate::query::query::Query; use crate::query::querybuilder::r#impl::QueryBuilder; use std::error::Error; -pub struct SelectQueryBuilder<'a, I: DbConnection + ?Sized, R: RowMapper> { - pub(crate) _inner: QueryBuilder<'a, I, R>, +pub struct SelectQueryBuilder<'a> { + pub(crate) _inner: QueryBuilder<'a>, } -impl<'a, I: DbConnection + ?Sized, R: RowMapper> SelectQueryBuilder<'a, I, R> { +impl<'a> SelectQueryBuilder<'a> { /// Generates a new public instance of the [`SelectQueryBuilder`] pub fn new( table_schema_data: &str, - input: &'a I, + database_type: DatabaseType, ) -> Result> { Ok(Self { - _inner: QueryBuilder::new(format!("SELECT * FROM {table_schema_data}"), input)?, + _inner: QueryBuilder::new(format!("SELECT * FROM {table_schema_data}"), database_type)?, }) } - /// Launches the generated query to the database pointed by the selected datasource - #[inline] - pub async fn query(self) -> Result, Box<(dyn Error + Send + Sync + 'a)>> - where - Vec: FromIterator<::Output>, - { - self._inner.query().await + pub fn build(self) -> Result, Box> { + self._inner.build() } } diff --git a/canyon_core/src/query/querybuilder/types/update.rs b/canyon_core/src/query/querybuilder/types/update.rs index eae2491d..8a73c515 100644 --- a/canyon_core/src/query/querybuilder/types/update.rs +++ b/canyon_core/src/query/querybuilder/types/update.rs @@ -1,33 +1,25 @@ -use crate::connection::contracts::DbConnection; -use crate::mapper::RowMapper; +use crate::connection::database_type::DatabaseType; +use crate::query::query::Query; use crate::query::querybuilder::r#impl::QueryBuilder; use std::error::Error; /// Contains the specific database operations of the *UPDATE* SQL statements. -/// -/// * `set` - To construct a new `SET` clause to determine the columns to -/// update with the provided values -pub struct UpdateQueryBuilder<'a, I: DbConnection + ?Sized, R: RowMapper> { - pub(crate) _inner: QueryBuilder<'a, I, R>, +pub struct UpdateQueryBuilder<'a> { + pub(crate) _inner: QueryBuilder<'a>, } -impl<'a, I: DbConnection + ?Sized, R: RowMapper> UpdateQueryBuilder<'a, I, R> { +impl<'a> UpdateQueryBuilder<'a> { /// Generates a new public instance of the [`UpdateQueryBuilder`] pub fn new( table_schema_data: &str, - input: &'a I, + database_type: DatabaseType, ) -> Result> { Ok(Self { - _inner: QueryBuilder::new(format!("UPDATE {table_schema_data}"), input)?, + _inner: QueryBuilder::new(format!("UPDATE {table_schema_data}"), database_type)?, }) } - /// Launches the generated query to the database pointed by the selected datasource - #[inline] - pub async fn query(self) -> Result, Box<(dyn Error + Send + Sync + 'a)>> - where - Vec: FromIterator<::Output>, - { - self._inner.query().await + pub fn build(self) -> Result, Box> { + self._inner.build() } } diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 237e7c58..5f5d0918 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -1,4 +1,5 @@ use canyon_core::connection::contracts::DbConnection; +use canyon_core::connection::database_type::DatabaseType; use canyon_core::mapper::RowMapper; use canyon_core::query::parameters::QueryParameter; use canyon_core::query::querybuilder::{ @@ -35,14 +36,11 @@ where where I: DbConnection + Send + 'a; - fn select_query<'a>( - ) -> Result, Box<(dyn Error + Send + Sync + 'a)>>; + fn select_query<'a>() -> Result, Box<(dyn Error + Send + Sync + 'a)>>; - fn select_query_with<'a, I>( - input: &'a I, - ) -> Result, Box<(dyn Error + Send + Sync + 'a)>> - where - I: DbConnection + Send + 'a + ?Sized; + fn select_query_with<'a>( + database_type: DatabaseType, + ) -> Result, Box<(dyn Error + Send + Sync + 'a)>>; fn count() -> impl Future>> + Send; @@ -95,14 +93,11 @@ where where I: DbConnection + Send + 'a; - fn update_query<'a>( - ) -> Result, Box<(dyn Error + Send + Sync + 'a)>>; + fn update_query<'a>() -> Result, Box<(dyn Error + Send + Sync + 'a)>>; - fn update_query_with<'a, I>( - input: &'a I, - ) -> Result, Box<(dyn Error + Send + Sync + 'a)>> - where - I: DbConnection + Send + 'a + ?Sized; + fn update_query_with<'a>( + database_type: DatabaseType, + ) -> Result, Box<(dyn Error + Send + Sync + 'a)>>; fn delete(&self) -> impl Future>> + Send; @@ -113,12 +108,9 @@ where where I: DbConnection + Send + 'a; - fn delete_query<'a>( - ) -> Result, Box<(dyn Error + Send + Sync + 'a)>>; + fn delete_query<'a>() -> Result, Box<(dyn Error + Send + Sync + 'a)>>; - fn delete_query_with<'a, I>( - input: &'a I, - ) -> Result, Box<(dyn Error + Send + Sync + 'a)>> - where - I: DbConnection + Send + 'a + ?Sized; + fn delete_query_with<'a>( + database_type: DatabaseType, + ) -> Result, Box<(dyn Error + Send + Sync + 'a)>>; } diff --git a/canyon_entities/src/manager_builder.rs b/canyon_entities/src/manager_builder.rs index 858a1a5f..60bc0a7c 100644 --- a/canyon_entities/src/manager_builder.rs +++ b/canyon_entities/src/manager_builder.rs @@ -26,7 +26,7 @@ pub fn generate_user_struct(canyon_entity: &CanyonEntity) -> TokenStream { /// of the type identifier + Field /// /// The idea it's to have a representation of the field name as an enum -/// variant, avoiding to let the user passing around Strings and instead, +/// variant, letting the user passing around Strings and instead, /// passing variants of a concrete enumeration type, that when required, /// will be called though macro code to obtain the &str representation /// of the field name. @@ -119,7 +119,7 @@ pub fn generate_enum_with_fields_values(canyon_entity: &CanyonEntity) -> TokenSt /// #[allow(non_camel_case_types)] /// pub enum LeagueFieldValue { /// id(i32), - /// name(String) + /// name(String), /// opt(Option) /// } /// ``` diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index 92a9cc14..923e7266 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -62,7 +62,7 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri }); } - let delete_with_querybuilder = generate_delete_querybuilder_tokens(ty, table_schema_data); + let delete_with_querybuilder = generate_delete_querybuilder_tokens(table_schema_data); delete_ops_tokens.extend(delete_with_querybuilder); delete_ops_tokens @@ -70,7 +70,7 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri /// Generates the TokenStream for the __delete() CRUD operation as a /// [`query_elements::query_builder::QueryBuilder<'a, #ty>`] -fn generate_delete_querybuilder_tokens(ty: &Ident, table_schema_data: &str) -> TokenStream { +fn generate_delete_querybuilder_tokens(table_schema_data: &str) -> TokenStream { quote! { /// Generates a [`canyon_sql::query::querybuilder::DeleteQueryBuilder`] /// that allows you to customize the query by adding parameters and constrains dynamically. @@ -80,9 +80,10 @@ fn generate_delete_querybuilder_tokens(ty: &Ident, table_schema_data: &str) -> T /// unless concrete values are set on the available parameters of the /// `canyon_macro(table_name = "table_name", schema = "schema")` fn delete_query<'a>() -> Result< - canyon_sql::query::querybuilder::DeleteQueryBuilder<'a, str, #ty>, - Box<(dyn std::error::Error + Send + Sync + 'a)>> { - canyon_sql::query::querybuilder::DeleteQueryBuilder::new(#table_schema_data, "") + canyon_sql::query::querybuilder::DeleteQueryBuilder<'a>, + Box<(dyn std::error::Error + Send + Sync + 'a)> + > { + canyon_sql::query::querybuilder::DeleteQueryBuilder::new(#table_schema_data, canyon_sql::connection::DatabaseType::default_type()?) } /// Generates a [`canyon_sql::query::querybuilder::DeleteQueryBuilder`] @@ -95,12 +96,12 @@ fn generate_delete_querybuilder_tokens(ty: &Ident, table_schema_data: &str) -> T /// /// The query it's made against the database with the configured datasource /// described in the configuration file, selected with the input parameter - fn delete_query_with<'a, I>(input: &'a I) -> Result< - canyon_sql::query::querybuilder::DeleteQueryBuilder<'a, I, #ty>, - Box<(dyn std::error::Error + Send + Sync + 'a)> - > where I: canyon_sql::core::DbConnection + Send + 'a + ?Sized - { - canyon_sql::query::querybuilder::DeleteQueryBuilder::new(#table_schema_data, input) + fn delete_query_with<'a>(database_type: canyon_sql::connection::DatabaseType) + -> Result< + canyon_sql::query::querybuilder::DeleteQueryBuilder<'a>, + Box<(dyn std::error::Error + Send + Sync + 'a)> + > { + canyon_sql::query::querybuilder::DeleteQueryBuilder::new(#table_schema_data, database_type) } } } diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index adcf1f06..12601c8c 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -18,7 +18,7 @@ pub fn generate_read_operations_tokens( let find_all_tokens = generate_find_all_operations_tokens(ty, &mapper_ty, table_schema_data); let count_tokens = generate_count_operations_tokens(ty, table_schema_data); let find_by_pk_tokens = generate_find_by_pk_operations_tokens(macro_data, table_schema_data); - let read_querybuilder_ops = generate_select_querybuilder_tokens(&mapper_ty, table_schema_data); + let read_querybuilder_ops = generate_select_querybuilder_tokens(table_schema_data); quote! { #find_all_tokens @@ -48,45 +48,37 @@ fn generate_find_all_operations_tokens( } } -fn generate_select_querybuilder_tokens( - mapper_ty: &Ident, - table_schema_data: &String, -) -> TokenStream { +fn generate_select_querybuilder_tokens(table_schema_data: &String) -> TokenStream { quote! { /// Generates a [`canyon_sql::query::querybuilder::SelectQueryBuilder`] /// that allows you to customize the query by adding parameters and constrains dynamically. /// - /// It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your + /// It generates a Query `SELECT * FROM table_name`, where `table_name` it's the name of your /// entity but converted to the corresponding database convention, /// unless concrete values are set on the available parameters of the /// `canyon_macro(table_name = "table_name", schema = "schema")` fn select_query<'a>() -> Result< - canyon_sql::query::querybuilder::SelectQueryBuilder<'a, str, #mapper_ty>, + canyon_sql::query::querybuilder::SelectQueryBuilder<'a>, Box<(dyn std::error::Error + Send + Sync + 'a)> > { - canyon_sql::query::querybuilder::SelectQueryBuilder::new(#table_schema_data, &"") + canyon_sql::query::querybuilder::SelectQueryBuilder::new(#table_schema_data, canyon_sql::connection::DatabaseType::default_type()?) } /// Generates a [`canyon_sql::query::querybuilder::SelectQueryBuilder`] /// that allows you to customize the query by adding parameters and constrains dynamically. /// - /// It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your + /// It generates a Query `SELECT * FROM table_name`, where `table_name` it's the name of your /// entity but converted to the corresponding database convention, /// unless concrete values are set on the available parameters of the /// `canyon_macro(table_name = "table_name", schema = "schema")` - /// - /// The query it's made against the database with the configured datasource - /// described in the configuration file, and selected with the [`&str`] - /// passed as parameter. - fn select_query_with<'a, I>(input: &'a I) + fn select_query_with<'a>(database_type: canyon_sql::connection::DatabaseType) -> Result< - canyon_sql::query::querybuilder::SelectQueryBuilder<'a, I, #mapper_ty>, + canyon_sql::query::querybuilder::SelectQueryBuilder<'a>, Box<(dyn std::error::Error + Send + Sync + 'a)> - > where I: canyon_sql::core::DbConnection + Send + 'a + ?Sized - { - canyon_sql::query::querybuilder::SelectQueryBuilder::new(#table_schema_data, input) + > { + canyon_sql::query::querybuilder::SelectQueryBuilder::new(#table_schema_data, database_type) } } } diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index 777a55c3..6a1ec5de 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -73,7 +73,7 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri }); } - let querybuilder_update_tokens = generate_update_querybuilder_tokens(ty, table_schema_data); + let querybuilder_update_tokens = generate_update_querybuilder_tokens(table_schema_data); update_ops_tokens.extend(querybuilder_update_tokens); update_ops_tokens @@ -81,7 +81,7 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri /// Generates the TokenStream for the __update() CRUD operation /// being the query generated with the [`QueryBuilder`] -fn generate_update_querybuilder_tokens(ty: &Ident, table_schema_data: &String) -> TokenStream { +fn generate_update_querybuilder_tokens(table_schema_data: &String) -> TokenStream { quote! { /// Generates a [`canyon_sql::query::querybuilder::UpdateQueryBuilder`] /// that allows you to customize the query by adding parameters and constrains dynamically. @@ -91,9 +91,10 @@ fn generate_update_querybuilder_tokens(ty: &Ident, table_schema_data: &String) - /// unless concrete values are set on the available parameters of the /// `canyon_macro(table_name = "table_name", schema = "schema")` fn update_query<'a>() -> Result< - canyon_sql::query::querybuilder::UpdateQueryBuilder<'a, str, #ty>, - Box<(dyn std::error::Error + Send + Sync + 'a)>> { - canyon_sql::query::querybuilder::UpdateQueryBuilder::new(#table_schema_data, "") + canyon_sql::query::querybuilder::UpdateQueryBuilder<'a>, + Box<(dyn std::error::Error + Send + Sync + 'a)> + > { + canyon_sql::query::querybuilder::UpdateQueryBuilder::new(#table_schema_data, canyon_sql::connection::DatabaseType::default_type()?) } /// Generates a [`canyon_sql::query::querybuilder::UpdateQueryBuilder`] @@ -106,12 +107,11 @@ fn generate_update_querybuilder_tokens(ty: &Ident, table_schema_data: &String) - /// /// The query it's made against the database with the configured datasource /// described in the configuration file, and selected with the input parameter - fn update_query_with<'a, I>(input: &'a I) -> Result< - canyon_sql::query::querybuilder::UpdateQueryBuilder<'a, I, #ty>, - Box<(dyn std::error::Error + Send + Sync + 'a)> - > where I: canyon_sql::core::DbConnection + Send + 'a + ?Sized - { - canyon_sql::query::querybuilder::UpdateQueryBuilder::new(#table_schema_data, input) + fn update_query_with<'a>(database_type: canyon_sql::connection::DatabaseType) -> Result< + canyon_sql::query::querybuilder::UpdateQueryBuilder<'a>, + Box<(dyn std::error::Error + Send + Sync + 'a)> + > { + canyon_sql::query::querybuilder::UpdateQueryBuilder::new(#table_schema_data, database_type) } } } diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index 6dcb1409..77dc5a68 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -2,6 +2,8 @@ use crate::constants::MYSQL_DS; #[cfg(feature = "mssql")] use crate::constants::SQL_SERVER_DS; +use canyon_sql::connection::DatabaseType; + /// Tests for the QueryBuilder available operations within Canyon. /// /// QueryBuilder are the way of obtain more flexibility that with @@ -60,7 +62,9 @@ fn test_crud_find_with_querybuilder() { .unwrap() .r#where(LeagueFieldValue::id(&50), Comp::LtEq) .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) - .query() + .build() + .unwrap() + .launch_default() .await; let filtered_leagues: Vec = filtered_leagues_result.unwrap(); @@ -93,7 +97,7 @@ fn test_crud_find_with_querybuilder_and_fulllike() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_fulllike_with_mssql() { // Find all the leagues with "LC" in their name - let filtered_leagues_result = League::select_query_with(SQL_SERVER_DS) + let filtered_leagues_result = League::select_query_with(DatabaseType::SqlServer) .unwrap() .r#where(LeagueFieldValue::name(&"LC"), Like::Full); @@ -109,7 +113,7 @@ fn test_crud_find_with_querybuilder_and_fulllike_with_mssql() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_fulllike_with_mysql() { // Find all the leagues with "LC" in their name - let filtered_leagues_result = League::select_query_with(MYSQL_DS) + let filtered_leagues_result = League::select_query_with(DatabaseType::MySQL) .unwrap() .r#where(LeagueFieldValue::name(&"LC"), Like::Full); @@ -141,7 +145,7 @@ fn test_crud_find_with_querybuilder_and_leftlike() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_leftlike_with_mssql() { // Find all the leagues whose name ends with "CK" - let filtered_leagues_result = League::select_query() + let filtered_leagues_result = League::select_query_with(DatabaseType::SqlServer) .unwrap() .r#where(LeagueFieldValue::name(&"CK"), Like::Left); @@ -157,7 +161,7 @@ fn test_crud_find_with_querybuilder_and_leftlike_with_mssql() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_leftlike_with_mysql() { // Find all the leagues whose name ends with "CK" - let filtered_leagues_result = League::select_query_with(MYSQL_DS) + let filtered_leagues_result = League::select_query_with(DatabaseType::MySQL) .unwrap() .r#where(LeagueFieldValue::name(&"CK"), Like::Left); @@ -189,7 +193,7 @@ fn test_crud_find_with_querybuilder_and_rightlike() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_rightlike_with_mssql() { // Find all the leagues whose name starts with "LC" - let filtered_leagues_result = League::select_query_with(SQL_SERVER_DS) + let filtered_leagues_result = League::select_query_with(DatabaseType::SqlServer) .unwrap() .r#where(LeagueFieldValue::name(&"LC"), Like::Right); @@ -205,7 +209,7 @@ fn test_crud_find_with_querybuilder_and_rightlike_with_mssql() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_rightlike_with_mysql() { // Find all the leagues whose name starts with "LC" - let filtered_leagues_result = League::select_query_with(MYSQL_DS) + let filtered_leagues_result = League::select_query_with(DatabaseType::MySQL) .unwrap() .r#where(LeagueFieldValue::name(&"LC"), Like::Right); @@ -220,10 +224,12 @@ fn test_crud_find_with_querybuilder_and_rightlike_with_mysql() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_with_mssql() { // Find all the players where its ID column value is greater than 50 - let filtered_find_players = Player::select_query_with(SQL_SERVER_DS) + let filtered_find_players = Player::select_query_with(DatabaseType::SqlServer) .unwrap() .r#where(PlayerFieldValue::id(&50), Comp::Gt) - .query() + .build() + .unwrap() + .launch_with::<&str, Player>(SQL_SERVER_DS) .await; assert!(!filtered_find_players.unwrap().is_empty()); @@ -234,10 +240,12 @@ fn test_crud_find_with_querybuilder_with_mssql() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_with_mysql() { // Find all the players where its ID column value is greater than 50 - let filtered_find_players = Player::select_query_with(MYSQL_DS) + let filtered_find_players = Player::select_query_with(DatabaseType::MySQL) .unwrap() .r#where(PlayerFieldValue::id(&50), Comp::Gt) - .query() + .build() + .unwrap() + .launch_with::<&str, Player>(MYSQL_DS) .await; assert!(!filtered_find_players.unwrap().is_empty()); @@ -259,19 +267,16 @@ fn test_crud_update_with_querybuilder() { .r#where(LeagueFieldValue::id(&1), Comp::Gt) .and(LeagueFieldValue::id(&8), Comp::Lt); - /* NOTE: Family of QueryBuilders are clone, useful in case of need to read the generated SQL - let qpr = q.clone(); - println!("PSQL: {:?}", qpr.read_sql()); - */ - q.query() - .await + q.build() .expect("Failed to update records with the querybuilder"); let found_updated_values = League::select_query() .unwrap() .r#where(LeagueFieldValue::id(&1), Comp::Gt) .and(LeagueFieldValue::id(&7), Comp::Lt) - .query() + .build() + .unwrap() + .launch_default::() .await .expect("Failed to retrieve database League entries with the querybuilder"); @@ -286,22 +291,26 @@ fn test_crud_update_with_querybuilder() { fn test_crud_update_with_querybuilder_with_mssql() { // Find all the leagues with ID less or equals that 7 // and where it's region column value is equals to 'Korea' - let q = Player::update_query_with(SQL_SERVER_DS).unwrap(); + let q = Player::update_query_with(DatabaseType::SqlServer).unwrap(); q.set(&[ (PlayerField::summoner_name, "Random updated player name"), (PlayerField::first_name, "I am an updated first name"), ]) .r#where(PlayerFieldValue::id(&1), Comp::Gt) .and(PlayerFieldValue::id(&8), Comp::Lt) - .query() + .build() + .unwrap() + .launch_with::<&str, Player>(SQL_SERVER_DS) .await .expect("Failed to update records with the querybuilder"); - let found_updated_values = Player::select_query_with(SQL_SERVER_DS) + let found_updated_values = Player::select_query_with(DatabaseType::SqlServer) .unwrap() .r#where(PlayerFieldValue::id(&1), Comp::Gt) .and(PlayerFieldValue::id(&7), Comp::LtEq) - .query() + .build() + .unwrap() + .launch_with::<&str, Player>(SQL_SERVER_DS) .await .expect("Failed to retrieve database League entries with the querybuilder"); @@ -318,22 +327,26 @@ fn test_crud_update_with_querybuilder_with_mysql() { // Find all the leagues with ID less or equals that 7 // and where it's region column value is equals to 'Korea' - let q = Player::update_query_with(MYSQL_DS).unwrap(); + let q = Player::update_query_with(DatabaseType::MySQL).unwrap(); q.set(&[ (PlayerField::summoner_name, "Random updated player name"), (PlayerField::first_name, "I am an updated first name"), ]) .r#where(PlayerFieldValue::id(&1), Comp::Gt) .and(PlayerFieldValue::id(&8), Comp::Lt) - .query() + .build() + .unwrap() + .launch_with::<&str, Player>(MYSQL_DS) .await .expect("Failed to update records with the querybuilder"); - let found_updated_values = Player::select_query_with(MYSQL_DS) + let found_updated_values = Player::select_query_with(DatabaseType::MySQL) .unwrap() .r#where(PlayerFieldValue::id(&1), Comp::Gt) .and(PlayerFieldValue::id(&7), Comp::LtEq) - .query() + .build() + .unwrap() + .launch_with::<&str, Player>(MYSQL_DS) .await .expect("Failed to retrieve database League entries with the querybuilder"); @@ -348,7 +361,7 @@ fn test_crud_update_with_querybuilder_with_mysql() { /// /// Note if the database is persisted (not created and destroyed on every docker or /// GitHub Action wake up), it won't delete things that already have been deleted, -/// but this isn't an error. They just don't exists. +/// but this isn't an error. They just don't exist. #[cfg(feature = "postgres")] #[canyon_sql::macros::canyon_tokio_test] fn test_crud_delete_with_querybuilder() { @@ -356,7 +369,9 @@ fn test_crud_delete_with_querybuilder() { .unwrap() .r#where(TournamentFieldValue::id(&14), Comp::Gt) .and(TournamentFieldValue::id(&16), Comp::Lt) - .query() + .build() + .unwrap() + .launch_default::() .await .expect("Error connecting with the database on the delete operation"); @@ -367,18 +382,22 @@ fn test_crud_delete_with_querybuilder() { #[cfg(feature = "mssql")] #[canyon_sql::macros::canyon_tokio_test] fn test_crud_delete_with_querybuilder_with_mssql() { - Player::delete_query_with(SQL_SERVER_DS) + Player::delete_query_with(DatabaseType::SqlServer) .unwrap() .r#where(PlayerFieldValue::id(&120), Comp::Gt) .and(PlayerFieldValue::id(&130), Comp::Lt) - .query() + .build() + .unwrap() + .launch_with::<&str, Player>(SQL_SERVER_DS) .await .expect("Error connecting with the database when we are going to delete data! :)"); - assert!(Player::select_query_with(SQL_SERVER_DS) + assert!(Player::select_query_with(DatabaseType::SqlServer) .unwrap() .r#where(PlayerFieldValue::id(&122), Comp::Eq) - .query() + .build() + .unwrap() + .launch_with::<&str, Player>(SQL_SERVER_DS) .await .unwrap() .is_empty()); @@ -388,18 +407,22 @@ fn test_crud_delete_with_querybuilder_with_mssql() { #[cfg(feature = "mysql")] #[canyon_sql::macros::canyon_tokio_test] fn test_crud_delete_with_querybuilder_with_mysql() { - Player::delete_query_with(MYSQL_DS) + Player::delete_query_with(DatabaseType::MySQL) .unwrap() .r#where(PlayerFieldValue::id(&120), Comp::Gt) .and(PlayerFieldValue::id(&130), Comp::Lt) - .query() + .build() + .unwrap() + .launch_with::<&str, Player>(MYSQL_DS) .await .expect("Error connecting with the database when we are going to delete data! :)"); - assert!(Player::select_query_with(MYSQL_DS) + assert!(Player::select_query_with(DatabaseType::MySQL) .unwrap() .r#where(PlayerFieldValue::id(&122), Comp::Eq) - .query() + .build() + .unwrap() + .launch_with::<&str, Player>(MYSQL_DS) .await .unwrap() .is_empty()); From b270cbead7eacfe9d5d0b0c67bb6be7d47f34f0e Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 9 May 2025 12:16:58 +0200 Subject: [PATCH 109/155] feat: joins on the SelectQuerybuilder types now receive the join fields from the autogenerated enums, so the argument is now a code entity and not an str --- canyon_core/src/canyon.rs | 118 ++++++++++++------ canyon_core/src/connection/clients/mssql.rs | 1 + canyon_core/src/query/bounds.rs | 12 +- canyon_core/src/query/query.rs | 1 - .../src/query/querybuilder/contracts/mod.rs | 31 +++-- .../src/query/querybuilder/impl/select.rs | 60 ++++++--- canyon_entities/src/helpers.rs | 25 ++++ canyon_entities/src/lib.rs | 1 + canyon_entities/src/manager_builder.rs | 17 ++- canyon_macros/src/lib.rs | 5 +- tests/crud/querybuilder_operations.rs | 8 +- 11 files changed, 204 insertions(+), 75 deletions(-) create mode 100644 canyon_entities/src/helpers.rs diff --git a/canyon_core/src/canyon.rs b/canyon_core/src/canyon.rs index 431e723e..fe61568d 100644 --- a/canyon_core/src/canyon.rs +++ b/canyon_core/src/canyon.rs @@ -4,11 +4,9 @@ use crate::connection::datasources::{CanyonSqlConfig, DatasourceConfig, Datasour use crate::connection::{db_connector, get_canyon_tokio_runtime, CANYON_INSTANCE}; use db_connector::DatabaseConnection; use std::collections::HashMap; -use std::path::PathBuf; use std::sync::Arc; use std::{error::Error, fs}; use tokio::sync::Mutex; -use walkdir::WalkDir; pub type SharedConnection = Arc>; @@ -56,12 +54,21 @@ pub type SharedConnection = Arc>; pub struct Canyon { config: Datasources, connections: HashMap<&'static str, SharedConnection>, - default: Option, + default_connection: Option, default_db_type: Option, } impl Canyon { - // Singleton access + /// Returns the global singleton instance of `Canyon`. + /// + /// This function allows access to the singleton instance of the Canyon engine + /// after it has been initialized through [`Canyon::init`]. It returns a shared, + /// read-only reference to the internal `Canyon` state. + /// + /// # Errors + /// + /// Returns an error if the `Canyon` instance has not yet been initialized. + /// In that case, the user must call [`Canyon::init`] before accessing the singleton. pub fn instance() -> Result<&'static Self, Box> { Ok(CANYON_INSTANCE.get().ok_or_else(|| { Box::new(std::io::Error::new( @@ -71,25 +78,47 @@ impl Canyon { })?) } - // Initializes Canyon instance + /// Initializes the global `Canyon` instance from a configuration file. + /// + /// Loads the `Datasources` configuration from the expected `canyon.toml` file (or another + /// discoverable location), establishes one or more database connections, and sets up the default + /// connection and database type. + /// + /// This function is idempotent: calling it multiple times will reuse the already-initialized instance. + /// + /// # Errors + /// + /// - If the configuration file is missing or malformed. + /// - If deserialization into `CanyonSqlConfig` fails. + /// - If any configured datasource fails to initialize. + /// + /// # Example + /// + /// ```ignore + /// #[tokio::main] + /// async fn main() -> Result<(), Box> { + /// let canyon = Canyon::init().await?; + /// Ok(()) + /// } + /// ``` pub async fn init() -> Result<&'static Self, Box> { if CANYON_INSTANCE.get().is_some() { return Canyon::instance(); // Already initialized, no need to do it again } - let path = Canyon::find_config_path()?; + let path = __impl::find_config_path()?; let config_content = fs::read_to_string(&path)?; let config: Datasources = toml::from_str::(&config_content)?.canyon_sql; let mut connections = HashMap::new(); - let mut default = None; + let mut default_connection = None; let mut default_db_type = None; for ds in config.datasources.iter() { __impl::process_new_conn_by_datasource( ds, &mut connections, - &mut default, + &mut default_connection, &mut default_db_type, ) .await?; @@ -98,7 +127,7 @@ impl Canyon { let canyon = Canyon { config, connections, - default, + default_connection, default_db_type, }; @@ -106,46 +135,35 @@ impl Canyon { Ok(CANYON_INSTANCE.get_or_init(|| canyon)) } - // Internal helper to locate the config file - fn find_config_path() -> Result { - WalkDir::new(".") - .max_depth(2) - .into_iter() - .filter_map(Result::ok) - .find_map(|e| { - let filename = e.file_name().to_string_lossy().to_lowercase(); - if e.metadata().ok()?.is_file() - && filename.starts_with("canyon") - && filename.ends_with(".toml") - { - Some(e.path().to_path_buf()) - } else { - None - } - }) - .ok_or_else(|| { - std::io::Error::new(std::io::ErrorKind::NotFound, "No Canyon config found") - }) - } - - // Public accessor for datasources + /// Returns an immutable slice containing all configured datasources. + /// + /// This slice represents the datasources defined in your `canyon.toml` configuration. + /// + /// # Example + /// + /// ``` + /// use canyon_core::canyon::Canyon; + /// for ds in Canyon::instance()?.datasources() { + /// println!("Datasource name: {}", ds.name); + /// } + /// ``` + #[inline(always)] pub fn datasources(&self) -> &[DatasourceConfig] { &self.config.datasources } - // Retrieve a datasource by name or default to the first + // Retrieve a datasource by name or returns the first one declared in the configuration file + // or added by the user via the builder interface as the default one (if exists at least one) pub fn find_datasource_by_name_or_default( &self, name: &str, ) -> Result<&DatasourceConfig, DatasourceNotFound> { if name.is_empty() { - self.config - .datasources + self.datasources() .first() .ok_or_else(|| DatasourceNotFound::from(None)) } else { - self.config - .datasources + self.datasources() .iter() .find(|ds| ds.name == name) .ok_or_else(|| DatasourceNotFound::from(Some(name))) @@ -162,7 +180,7 @@ impl Canyon { &self, ) -> Result, DatasourceNotFound> { Ok(self - .default + .default_connection .as_ref() .ok_or_else(|| DatasourceNotFound::from(None))? .lock() @@ -202,8 +220,32 @@ mod __impl { use crate::connection::db_connector::DatabaseConnection; use std::collections::HashMap; use std::error::Error; + use std::path::PathBuf; use std::sync::Arc; use tokio::sync::Mutex; + use walkdir::WalkDir; + + // Internal helper to locate the config file + pub(crate) fn find_config_path() -> Result { + WalkDir::new(".") + .max_depth(2) + .into_iter() + .filter_map(Result::ok) + .find_map(|e| { + let filename = e.file_name().to_string_lossy().to_lowercase(); + if e.metadata().ok()?.is_file() + && filename.starts_with("canyon") + && filename.ends_with(".toml") + { + Some(e.path().to_path_buf()) + } else { + None + } + }) + .ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::NotFound, "No Canyon config found") + }) + } pub(crate) async fn process_new_conn_by_datasource( ds: &DatasourceConfig, diff --git a/canyon_core/src/connection/clients/mssql.rs b/canyon_core/src/connection/clients/mssql.rs index 70ec24db..0af1f4c5 100644 --- a/canyon_core/src/connection/clients/mssql.rs +++ b/canyon_core/src/connection/clients/mssql.rs @@ -142,6 +142,7 @@ pub(crate) mod sqlserver_query_launcher { } // TODO: We must address the query generation + // NOTE: ready to apply the change now that the querybuilder knows what's the underlying db type let mut mssql_query = Query::new(stmt.to_owned().replace('$', "@P")); params.iter().for_each(|param| { mssql_query.bind(*param); diff --git a/canyon_core/src/query/bounds.rs b/canyon_core/src/query/bounds.rs index 58aeca01..b85c17f1 100644 --- a/canyon_core/src/query/bounds.rs +++ b/canyon_core/src/query/bounds.rs @@ -22,12 +22,14 @@ use crate::query::parameters::QueryParameter; /// /// // Something like: /// `let struct_field_name_from_variant = StructField::some_field.field_name_as_str();` -pub trait FieldIdentifier -// where -// // TODO: maybe just QueryParameter? -// T: QueryParameter<'a>, -{ +pub trait FieldIdentifier: std::fmt::Display { fn as_str(&self) -> &'static str; + + /// Returns a formatted string as `{.}`. + /// + /// This is useful during queries generations for example, in join statements, when you + /// alias other defined names, etc. + fn table_and_column_name(&self) -> String; } /// Represents some kind of introspection to make the implementors diff --git a/canyon_core/src/query/query.rs b/canyon_core/src/query/query.rs index 76ac9678..99a2dfa3 100644 --- a/canyon_core/src/query/query.rs +++ b/canyon_core/src/query/query.rs @@ -54,4 +54,3 @@ impl<'a> Query<'a> { input.query(&self.sql, &self.params).await } } - diff --git a/canyon_core/src/query/querybuilder/contracts/mod.rs b/canyon_core/src/query/querybuilder/contracts/mod.rs index 5b33af86..4cdc6593 100644 --- a/canyon_core/src/query/querybuilder/contracts/mod.rs +++ b/canyon_core/src/query/querybuilder/contracts/mod.rs @@ -16,9 +16,6 @@ pub trait UpdateQueryBuilderOps<'a>: QueryBuilderOps<'a> { } pub trait SelectQueryBuilderOps<'a>: QueryBuilderOps<'a> { - // TODO: cols on the statement must be generics to use &str and fieldvalue (the enum) - // TODO: could we introduce const_format! for the construction of the components of the query? - /// Adds a *LEFT JOIN* SQL statement to the underlying /// `Sql Statement` held by the [`QueryBuilder`], where: /// @@ -27,7 +24,12 @@ pub trait SelectQueryBuilderOps<'a>: QueryBuilderOps<'a> { /// * `col2` - The right side of the ON operator for the join /// /// > Note: The order on the column parameters is irrelevant - fn left_join(self, join_table: &str, col1: &str, col2: &str) -> Self; + fn left_join( + self, + join_table: &str, + col1: impl FieldIdentifier, + col2: impl FieldIdentifier, + ) -> Self; /// Adds a *INNER JOIN* SQL statement to the underlying /// `Sql Statement` held by the [`QueryBuilder`], where: @@ -37,7 +39,12 @@ pub trait SelectQueryBuilderOps<'a>: QueryBuilderOps<'a> { /// * `col2` - The right side of the ON operator for the join /// /// > Note: The order on the column parameters is irrelevant - fn inner_join(self, join_table: &str, col1: &str, col2: &str) -> Self; + fn inner_join( + self, + join_table: &str, + col1: impl FieldIdentifier, + col2: impl FieldIdentifier, + ) -> Self; /// Adds a *RIGHT JOIN* SQL statement to the underlying /// `Sql Statement` held by the [`QueryBuilder`], where: @@ -47,7 +54,12 @@ pub trait SelectQueryBuilderOps<'a>: QueryBuilderOps<'a> { /// * `col2` - The right side of the ON operator for the join /// /// > Note: The order on the column parameters is irrelevant - fn right_join(self, join_table: &str, col1: &str, col2: &str) -> Self; + fn right_join( + self, + join_table: &str, + col1: impl FieldIdentifier, + col2: impl FieldIdentifier, + ) -> Self; /// Adds a *FULL JOIN* SQL statement to the underlying /// `Sql Statement` held by the [`QueryBuilder`], where: @@ -57,7 +69,12 @@ pub trait SelectQueryBuilderOps<'a>: QueryBuilderOps<'a> { /// * `col2` - The right side of the ON operator for the join /// /// > Note: The order on the column parameters is irrelevant - fn full_join(self, join_table: &str, col1: &str, col2: &str) -> Self; + fn full_join( + self, + join_table: &str, + col1: impl FieldIdentifier, + col2: impl FieldIdentifier, + ) -> Self; } /// The [`QueryBuilder`] trait is the root of a kind of hierarchy diff --git a/canyon_core/src/query/querybuilder/impl/select.rs b/canyon_core/src/query/querybuilder/impl/select.rs index 3a1c4a64..3350aa83 100644 --- a/canyon_core/src/query/querybuilder/impl/select.rs +++ b/canyon_core/src/query/querybuilder/impl/select.rs @@ -5,31 +5,59 @@ use crate::query::querybuilder::contracts::{QueryBuilderOps, SelectQueryBuilderO use crate::query::querybuilder::types::select::SelectQueryBuilder; impl<'a> SelectQueryBuilderOps<'a> for SelectQueryBuilder<'a> { - fn left_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { - self._inner - .sql - .push_str(&format!(" LEFT JOIN {join_table} ON {col1} = {col2}")); + fn left_join( + mut self, + join_table: &str, + col1: impl FieldIdentifier, + col2: impl FieldIdentifier, + ) -> Self { + self._inner.sql.push_str(&format!( + " LEFT JOIN {join_table} ON {} = {}", + col1.table_and_column_name(), + col2.table_and_column_name() + )); self } - fn inner_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { - self._inner - .sql - .push_str(&format!(" INNER JOIN {join_table} ON {col1} = {col2}")); + fn inner_join( + mut self, + join_table: &str, + col1: impl FieldIdentifier, + col2: impl FieldIdentifier, + ) -> Self { + self._inner.sql.push_str(&format!( + " INNER JOIN {join_table} ON {} = {}", + col1.table_and_column_name(), + col2.table_and_column_name() + )); self } - fn right_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { - self._inner - .sql - .push_str(&format!(" RIGHT JOIN {join_table} ON {col1} = {col2}")); + fn right_join( + mut self, + join_table: &str, + col1: impl FieldIdentifier, + col2: impl FieldIdentifier, + ) -> Self { + self._inner.sql.push_str(&format!( + " RIGHT JOIN {join_table} ON {} = {}", + col1.table_and_column_name(), + col2.table_and_column_name() + )); self } - fn full_join(mut self, join_table: &str, col1: &str, col2: &str) -> Self { - self._inner - .sql - .push_str(&format!(" FULL JOIN {join_table} ON {col1} = {col2}")); + fn full_join( + mut self, + join_table: &str, + col1: impl FieldIdentifier, + col2: impl FieldIdentifier, + ) -> Self { + self._inner.sql.push_str(&format!( + " FULL JOIN {join_table} ON {} = {}", + col1.table_and_column_name(), + col2.table_and_column_name() + )); self } } diff --git a/canyon_entities/src/helpers.rs b/canyon_entities/src/helpers.rs new file mode 100644 index 00000000..80012b5a --- /dev/null +++ b/canyon_entities/src/helpers.rs @@ -0,0 +1,25 @@ +/// Autogenerates a default table name for an entity given their struct name +/// TODO: This is duplicated from the macro's crate. We should be able to join both crates in +/// one later, but now, for developing purposes, we need to maintain here for a while this here +pub fn default_database_table_name_from_entity_name(ty: &str) -> String { + let struct_name: String = ty.to_string(); + let mut table_name: String = String::new(); + + let mut index = 0; + for char in struct_name.chars() { + if index < 1 { + table_name.push(char.to_ascii_lowercase()); + index += 1; + } else { + match char { + n if n.is_ascii_uppercase() => { + table_name.push('_'); + table_name.push(n.to_ascii_lowercase()); + } + _ => table_name.push(char), + } + } + } + + table_name +} diff --git a/canyon_entities/src/lib.rs b/canyon_entities/src/lib.rs index 8b3abd6c..3ba272bb 100644 --- a/canyon_entities/src/lib.rs +++ b/canyon_entities/src/lib.rs @@ -4,6 +4,7 @@ use std::sync::Mutex; pub mod entity; pub mod entity_fields; pub mod field_annotation; +mod helpers; pub mod manager_builder; pub mod register_types; diff --git a/canyon_entities/src/manager_builder.rs b/canyon_entities/src/manager_builder.rs index 60bc0a7c..3dd5434b 100644 --- a/canyon_entities/src/manager_builder.rs +++ b/canyon_entities/src/manager_builder.rs @@ -1,9 +1,9 @@ +use super::entity::CanyonEntity; +use crate::helpers; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::{Attribute, Generics, Visibility}; -use super::entity::CanyonEntity; - /// Builds the TokenStream that contains the user defined struct pub fn generate_user_struct(canyon_entity: &CanyonEntity) -> TokenStream { let fields = &canyon_entity.get_attrs_as_token_stream(); @@ -32,6 +32,8 @@ pub fn generate_user_struct(canyon_entity: &CanyonEntity) -> TokenStream { /// of the field name. pub fn generate_enum_with_fields(canyon_entity: &CanyonEntity) -> TokenStream { let struct_name = canyon_entity.struct_name.to_string(); + let db_target_table_name = helpers::default_database_table_name_from_entity_name(&struct_name); + let enum_name = Ident::new((struct_name + "Field").as_str(), Span::call_site()); let fields_names = &canyon_entity.get_fields_as_enum_variants(); @@ -76,7 +78,18 @@ pub fn generate_enum_with_fields(canyon_entity: &CanyonEntity) -> TokenStream { #(#fields_names),* } + impl #generics std::fmt::Display for #enum_name #generics { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_str()) + } + } + impl #generics canyon_sql::query::bounds::FieldIdentifier for #generics #enum_name #generics { + #[inline(always)] + fn table_and_column_name(&self) -> String { + format!("{}.{}", #db_target_table_name, self.as_str()) + } + fn as_str(&self) -> &'static str { match *self { #(#match_arms_str),* diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index 51ece4c7..ab0f0789 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -126,8 +126,7 @@ pub fn canyon_entity(meta: CompilerTokenStream, input: CompilerTokenStream) -> C /// type, as defined in the `CrudOperations` + `Transaction` traits. #[proc_macro_derive(CanyonCrud, attributes(canyon_crud))] pub fn crud_operations(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let ast: DeriveInput = - syn::parse(input).expect("Error parsing `Canyon Entity for generate the CRUD methods"); + let ast: DeriveInput = syn::parse(input).expect("Error implementing CanyonCrud AST"); let macro_data = MacroTokens::new(&ast); let table_name_res = helpers::table_schema_parser(¯o_data); @@ -179,6 +178,8 @@ pub fn querybuilder_fields(input: CompilerTokenStream) -> CompilerTokenStream { let _generated_enum_type_for_fields_values = generate_enum_with_fields_values(&entity); quote! { use canyon_sql::core::QueryParameter; + use canyon_sql::query::bounds::FieldIdentifier; + #_generated_enum_type_for_fields #_generated_enum_type_for_fields_values } diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index 77dc5a68..a8257385 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -34,9 +34,9 @@ use crate::tests_models::tournament::*; #[canyon_sql::macros::canyon_tokio_test] fn test_generated_sql_by_the_select_querybuilder() { let select_with_joins = League::select_query() - .unwrap() - .inner_join("tournament", "league.id", "tournament.league_id") - .left_join("team", "tournament.id", "player.tournament_id") + .unwrap() // TournamentTable::name --- // + .inner_join("tournament", LeagueField::id, TournamentField::league) + .left_join("team", TournamentField::id, PlayerField::id) .r#where(LeagueFieldValue::id(&7), Comp::Gt) .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); @@ -47,7 +47,7 @@ fn test_generated_sql_by_the_select_querybuilder() { // generated SQL by the SelectQueryBuilder is the expected assert_eq!( select_with_joins.read_sql(), - "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league_id LEFT JOIN team ON tournament.id = player.tournament_id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" + "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league LEFT JOIN team ON tournament.id = player.id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" ) } From aae7d975b1ef04cb50e87c087f1255e5a6611b0e Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 9 May 2025 12:56:54 +0200 Subject: [PATCH 110/155] feat: new auto-generated enum type that carries the meta-information of a type for giving the user reflection elements over the type ident and the type matching database table name generated by Canyon --- canyon_core/src/query/bounds.rs | 4 ++ canyon_core/src/query/query.rs | 3 - .../src/query/querybuilder/contracts/mod.rs | 10 +-- .../src/query/querybuilder/impl/select.rs | 10 +-- canyon_entities/src/manager_builder.rs | 63 +++++++++++++++++++ canyon_macros/src/lib.rs | 13 ++-- tests/crud/querybuilder_operations.rs | 8 +-- 7 files changed, 90 insertions(+), 21 deletions(-) diff --git a/canyon_core/src/query/bounds.rs b/canyon_core/src/query/bounds.rs index b85c17f1..5162a4bb 100644 --- a/canyon_core/src/query/bounds.rs +++ b/canyon_core/src/query/bounds.rs @@ -1,5 +1,9 @@ use crate::query::parameters::QueryParameter; +pub trait TableMetadata: std::fmt::Display { + fn as_str(&self) -> &'static str; +} + /// Created for retrieve the field's name of a field of a struct, giving /// the Canyon's autogenerated enum with the variants that maps this /// fields. diff --git a/canyon_core/src/query/query.rs b/canyon_core/src/query/query.rs index 99a2dfa3..4a4fe1af 100644 --- a/canyon_core/src/query/query.rs +++ b/canyon_core/src/query/query.rs @@ -6,9 +6,6 @@ use crate::transaction::Transaction; use std::error::Error; use std::fmt::Debug; use std::ops::DerefMut; -// TODO: all the query works here -// TODO: exports things like Select::... where receives the table -// name and prepares the raw query (maybe with const_format!) for improved performance // TODO: query should implement ToStatement (as the drivers underneath Canyon) or similar // to be usable directly in the input of Transaction and DbConnenction diff --git a/canyon_core/src/query/querybuilder/contracts/mod.rs b/canyon_core/src/query/querybuilder/contracts/mod.rs index 4cdc6593..158623a0 100644 --- a/canyon_core/src/query/querybuilder/contracts/mod.rs +++ b/canyon_core/src/query/querybuilder/contracts/mod.rs @@ -1,7 +1,7 @@ //! Contains the elements that makes part of the formal declaration //! of the behaviour of the Canyon-SQL QueryBuilder -use crate::query::bounds::{FieldIdentifier, FieldValueIdentifier}; +use crate::query::bounds::{FieldIdentifier, FieldValueIdentifier, TableMetadata}; use crate::query::operators::Operator; use crate::query::parameters::QueryParameter; @@ -26,7 +26,7 @@ pub trait SelectQueryBuilderOps<'a>: QueryBuilderOps<'a> { /// > Note: The order on the column parameters is irrelevant fn left_join( self, - join_table: &str, + join_table: impl TableMetadata, col1: impl FieldIdentifier, col2: impl FieldIdentifier, ) -> Self; @@ -41,7 +41,7 @@ pub trait SelectQueryBuilderOps<'a>: QueryBuilderOps<'a> { /// > Note: The order on the column parameters is irrelevant fn inner_join( self, - join_table: &str, + join_table: impl TableMetadata, col1: impl FieldIdentifier, col2: impl FieldIdentifier, ) -> Self; @@ -56,7 +56,7 @@ pub trait SelectQueryBuilderOps<'a>: QueryBuilderOps<'a> { /// > Note: The order on the column parameters is irrelevant fn right_join( self, - join_table: &str, + join_table: impl TableMetadata, col1: impl FieldIdentifier, col2: impl FieldIdentifier, ) -> Self; @@ -71,7 +71,7 @@ pub trait SelectQueryBuilderOps<'a>: QueryBuilderOps<'a> { /// > Note: The order on the column parameters is irrelevant fn full_join( self, - join_table: &str, + join_table: impl TableMetadata, col1: impl FieldIdentifier, col2: impl FieldIdentifier, ) -> Self; diff --git a/canyon_core/src/query/querybuilder/impl/select.rs b/canyon_core/src/query/querybuilder/impl/select.rs index 3350aa83..c19ab101 100644 --- a/canyon_core/src/query/querybuilder/impl/select.rs +++ b/canyon_core/src/query/querybuilder/impl/select.rs @@ -1,4 +1,4 @@ -use crate::query::bounds::{FieldIdentifier, FieldValueIdentifier}; +use crate::query::bounds::{FieldIdentifier, FieldValueIdentifier, TableMetadata}; use crate::query::operators::Operator; use crate::query::parameters::QueryParameter; use crate::query::querybuilder::contracts::{QueryBuilderOps, SelectQueryBuilderOps}; @@ -7,7 +7,7 @@ use crate::query::querybuilder::types::select::SelectQueryBuilder; impl<'a> SelectQueryBuilderOps<'a> for SelectQueryBuilder<'a> { fn left_join( mut self, - join_table: &str, + join_table: impl TableMetadata, col1: impl FieldIdentifier, col2: impl FieldIdentifier, ) -> Self { @@ -21,7 +21,7 @@ impl<'a> SelectQueryBuilderOps<'a> for SelectQueryBuilder<'a> { fn inner_join( mut self, - join_table: &str, + join_table: impl TableMetadata, col1: impl FieldIdentifier, col2: impl FieldIdentifier, ) -> Self { @@ -35,7 +35,7 @@ impl<'a> SelectQueryBuilderOps<'a> for SelectQueryBuilder<'a> { fn right_join( mut self, - join_table: &str, + join_table: impl TableMetadata, col1: impl FieldIdentifier, col2: impl FieldIdentifier, ) -> Self { @@ -49,7 +49,7 @@ impl<'a> SelectQueryBuilderOps<'a> for SelectQueryBuilder<'a> { fn full_join( mut self, - join_table: &str, + join_table: impl TableMetadata, col1: impl FieldIdentifier, col2: impl FieldIdentifier, ) -> Self { diff --git a/canyon_entities/src/manager_builder.rs b/canyon_entities/src/manager_builder.rs index 3dd5434b..1347bf0a 100644 --- a/canyon_entities/src/manager_builder.rs +++ b/canyon_entities/src/manager_builder.rs @@ -21,6 +21,69 @@ pub fn generate_user_struct(canyon_entity: &CanyonEntity) -> TokenStream { } } +pub fn generated_enum_type_for_struct_data(canyon_entity: &CanyonEntity) -> TokenStream { + let struct_name = canyon_entity.struct_name.to_string(); + let enum_name = Ident::new(&(String::from(&struct_name) + "Table"), Span::call_site()); + let db_target_table_name = helpers::default_database_table_name_from_entity_name(&struct_name); + + let generics = &canyon_entity.generics; + let visibility = &canyon_entity.vis; + + quote! { + /// Auto-generated enum to represent compile-time metadata + /// about a Canyon entity type. + /// + /// The enum is named by appending `Table` to the struct name and contains + /// variants for retrieving metadata associated with the entity. Currently, + /// it includes: + /// + /// - `name`: The struct's identifier as a string. + /// - `DbName`: The name of the database table derived from the struct's name, + /// but adapted to the `snake_case` convention, which is the standard adopted + /// by Canyon these early days to transform type Idents into table names + /// + /// This enum implements the `TableMetadata` trait, providing the `as_str` method, + /// which is useful in code that needs to retrieve such metadata dynamically while + /// keeping strong typing and avoiding magic strings. + /// + /// # Example + /// ``` + /// pub struct League { + /// id: i32, + /// name: String, + /// } + /// + /// // This is the auto-generated by Canyon with the `Fields` macro + /// pub enum LeagueTable { + /// Name, + /// DbName + /// } + /// + /// assert_eq!(LeagueTable::Name.to_string(), "League"); + /// assert_eq!(LeagueTable::DbName.to_string(), "league"); + /// ``` + #visibility enum #enum_name #generics { + Name, + DbName + } + + impl #generics std::fmt::Display for #enum_name #generics { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_str()) + } + } + + impl #generics canyon_sql::query::bounds::TableMetadata for #generics #enum_name #generics { + fn as_str(&self) -> &'static str { + match *self { + #enum_name::Name => #struct_name, + #enum_name::DbName => #db_target_table_name, + } + } + } + } +} + /// Auto-generated enum to represent every field of the related type /// as a variant of an enum that it's named with the concatenation /// of the type identifier + Field diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index ab0f0789..493b123e 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -24,6 +24,7 @@ use canyon_entities::{ entity::CanyonEntity, manager_builder::{generate_enum_with_fields, generate_enum_with_fields_values}, }; +use canyon_entities::manager_builder::generated_enum_type_for_struct_data; /// Macro for handling the entry point to the program. /// @@ -174,14 +175,18 @@ pub fn querybuilder_fields(input: CompilerTokenStream) -> CompilerTokenStream { // No errors detected on the parsing, so we can safely unwrap the parse result let entity = entity_res.expect("Unexpected error parsing the struct"); - let _generated_enum_type_for_fields = generate_enum_with_fields(&entity); - let _generated_enum_type_for_fields_values = generate_enum_with_fields_values(&entity); + let generated_enum_type_for_struct_data = generated_enum_type_for_struct_data(&entity); + let generated_enum_type_for_fields = generate_enum_with_fields(&entity); + let generated_enum_type_for_fields_values = generate_enum_with_fields_values(&entity); + quote! { use canyon_sql::core::QueryParameter; + use canyon_sql::query::bounds::TableMetadata; use canyon_sql::query::bounds::FieldIdentifier; - #_generated_enum_type_for_fields - #_generated_enum_type_for_fields_values + #generated_enum_type_for_struct_data + #generated_enum_type_for_fields + #generated_enum_type_for_fields_values } .into() } diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index a8257385..3b1854c6 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -34,9 +34,9 @@ use crate::tests_models::tournament::*; #[canyon_sql::macros::canyon_tokio_test] fn test_generated_sql_by_the_select_querybuilder() { let select_with_joins = League::select_query() - .unwrap() // TournamentTable::name --- // - .inner_join("tournament", LeagueField::id, TournamentField::league) - .left_join("team", TournamentField::id, PlayerField::id) + .unwrap() + .inner_join(TournamentTable::DbName, LeagueField::id, TournamentField::league) + .left_join(PlayerTable::DbName, TournamentField::id, PlayerField::id) .r#where(LeagueFieldValue::id(&7), Comp::Gt) .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); @@ -47,7 +47,7 @@ fn test_generated_sql_by_the_select_querybuilder() { // generated SQL by the SelectQueryBuilder is the expected assert_eq!( select_with_joins.read_sql(), - "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league LEFT JOIN team ON tournament.id = player.id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" + "SELECT * FROM league INNER JOIN tournament ON league.id = tournament.league LEFT JOIN player ON tournament.id = player.id WHERE id > $1 AND name = $2 AND name IN ($2, $3)" ) } From 0ebe0ccdee83fb32cf68ecc8c4d25c2d14c5e98e Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 9 May 2025 12:57:18 +0200 Subject: [PATCH 111/155] fix: cargo fmt --- canyon_entities/src/manager_builder.rs | 2 +- canyon_macros/src/lib.rs | 4 ++-- tests/crud/querybuilder_operations.rs | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/canyon_entities/src/manager_builder.rs b/canyon_entities/src/manager_builder.rs index 1347bf0a..188dcb06 100644 --- a/canyon_entities/src/manager_builder.rs +++ b/canyon_entities/src/manager_builder.rs @@ -52,7 +52,7 @@ pub fn generated_enum_type_for_struct_data(canyon_entity: &CanyonEntity) -> Toke /// id: i32, /// name: String, /// } - /// + /// /// // This is the auto-generated by Canyon with the `Fields` macro /// pub enum LeagueTable { /// Name, diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index 493b123e..cfc74e66 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -20,11 +20,11 @@ use crate::canyon_entity_macro::generate_canyon_entity_tokens; use crate::canyon_mapper_macro::canyon_mapper_impl_tokens; use crate::foreignkeyable_macro::foreignkeyable_impl_tokens; use crate::query_operations::impl_crud_operations_trait_for_struct; +use canyon_entities::manager_builder::generated_enum_type_for_struct_data; use canyon_entities::{ entity::CanyonEntity, manager_builder::{generate_enum_with_fields, generate_enum_with_fields_values}, }; -use canyon_entities::manager_builder::generated_enum_type_for_struct_data; /// Macro for handling the entry point to the program. /// @@ -178,7 +178,7 @@ pub fn querybuilder_fields(input: CompilerTokenStream) -> CompilerTokenStream { let generated_enum_type_for_struct_data = generated_enum_type_for_struct_data(&entity); let generated_enum_type_for_fields = generate_enum_with_fields(&entity); let generated_enum_type_for_fields_values = generate_enum_with_fields_values(&entity); - + quote! { use canyon_sql::core::QueryParameter; use canyon_sql::query::bounds::TableMetadata; diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index 3b1854c6..49466f67 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -35,7 +35,11 @@ use crate::tests_models::tournament::*; fn test_generated_sql_by_the_select_querybuilder() { let select_with_joins = League::select_query() .unwrap() - .inner_join(TournamentTable::DbName, LeagueField::id, TournamentField::league) + .inner_join( + TournamentTable::DbName, + LeagueField::id, + TournamentField::league, + ) .left_join(PlayerTable::DbName, TournamentField::id, PlayerField::id) .r#where(LeagueFieldValue::id(&7), Comp::Gt) .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) From 7fbff2d5318dc3ce60674c2a5b83c7e2abc5a575 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 9 May 2025 16:34:37 +0200 Subject: [PATCH 112/155] feat: completed all the possible branches for the FromSql and FromSqlOwnedValue depending on what db configuration features exists --- canyon_core/src/query/parameters.rs | 2 +- canyon_core/src/rows.rs | 128 ++++++++++++++++++-------- tests/crud/querybuilder_operations.rs | 2 - 3 files changed, 90 insertions(+), 42 deletions(-) diff --git a/canyon_core/src/query/parameters.rs b/canyon_core/src/query/parameters.rs index 6b624126..657af4ca 100644 --- a/canyon_core/src/query/parameters.rs +++ b/canyon_core/src/query/parameters.rs @@ -5,7 +5,7 @@ use tiberius::{self, ColumnData, IntoSql}; #[cfg(feature = "postgres")] use tokio_postgres::{self, types::ToSql}; -// TODO: cfg all +// TODO: cfg feature for this re-exports, as date-time or something use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Utc}; /// Defines a trait for represent type bounds against the allowed diff --git a/canyon_core/src/rows.rs b/canyon_core/src/rows.rs index 75430d05..99444457 100644 --- a/canyon_core/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -15,45 +15,6 @@ use crate::row::Row; use cfg_if::cfg_if; -// Helper macro to conditionally add trait bounds -// these are the hacky intermediate traits -cfg_if! { - if #[cfg(all(feature = "postgres", feature = "mysql", feature = "mssql"))] { - pub trait FromSql<'a, T>: tokio_postgres::types::FromSql<'a> - + tiberius::FromSql<'a> - + mysql_async::prelude::FromValue {} - impl<'a, T> FromSql<'a, T> for T where T: - tokio_postgres::types::FromSql<'a> - + tiberius::FromSql<'a> - + mysql_async::prelude::FromValue - {} - - pub trait FromSqlOwnedValue: tokio_postgres::types::FromSqlOwned - + tiberius::FromSqlOwned - + mysql_async::prelude::FromValue {} - impl FromSqlOwnedValue for T where T: - tokio_postgres::types::FromSqlOwned - + tiberius::FromSqlOwned - + mysql_async::prelude::FromValue - {} - } else if #[cfg(feature = "postgres")] { - pub trait FromSql<'a, T>: tokio_postgres::types::FromSql<'a> {} - impl<'a, T> FromSql<'a, T> for T where T: - tokio_postgres::types::FromSql<'a> {} - - pub trait FromSqlOwnedValue: tokio_postgres::types::FromSqlOwned {} - impl FromSqlOwnedValue for T where T: - tokio_postgres::types::FromSqlOwned {} - } else if #[cfg(feature = "mssql")] { - pub trait FromSql<'a, T>: tiberius::FromSqlOwned {} - impl<'a, T> FromSql<'a, T> for T where T: tiberius::FromSqlOwned {} - - pub trait FromSqlOwnedValue: tiberius::FromSqlOwned {} - impl FromSqlOwnedValue for T where T: tiberius::FromSqlOwned {} - } - // TODO: missing combinations else -} - /// Lightweight wrapper over the collection of results of the different crates /// supported by Canyon-SQL. /// @@ -170,3 +131,92 @@ impl CanyonRows { } } } + + +cfg_if! { + if #[cfg(all(feature = "postgres", feature = "mysql", feature = "mssql"))] { + pub trait FromSql<'a, T>: tokio_postgres::types::FromSql<'a> + + tiberius::FromSql<'a> + + mysql_async::prelude::FromValue {} + impl<'a, T> FromSql<'a, T> for T where T: + tokio_postgres::types::FromSql<'a> + + tiberius::FromSql<'a> + + mysql_async::prelude::FromValue + {} + + pub trait FromSqlOwnedValue: tokio_postgres::types::FromSqlOwned + + tiberius::FromSqlOwned + + mysql_async::prelude::FromValue {} + impl FromSqlOwnedValue for T where T: + tokio_postgres::types::FromSqlOwned + + tiberius::FromSqlOwned + + mysql_async::prelude::FromValue + {} + } else if #[cfg(all(feature = "postgres", feature = "mysql"))] { + pub trait FromSql<'a, T>: tokio_postgres::types::FromSql<'a> + + mysql_async::prelude::FromValue {} + impl<'a, T> FromSql<'a, T> for T where T: + tokio_postgres::types::FromSql<'a> + + mysql_async::prelude::FromValue + {} + + pub trait FromSqlOwnedValue: tokio_postgres::types::FromSqlOwned + + mysql_async::prelude::FromValue {} + impl FromSqlOwnedValue for T where T: + tokio_postgres::types::FromSqlOwned + + mysql_async::prelude::FromValue + {} + } else if #[cfg(all(feature = "postgres", feature = "mssql"))] { + pub trait FromSql<'a, T>: tokio_postgres::types::FromSql<'a> + + tiberius::FromSql<'a> {} + impl<'a, T> FromSql<'a, T> for T where T: + tokio_postgres::types::FromSql<'a> + + tiberius::FromSql<'a> + {} + + pub trait FromSqlOwnedValue: tokio_postgres::types::FromSqlOwned + + tiberius::FromSqlOwned {} + impl FromSqlOwnedValue for T where T: + tokio_postgres::types::FromSqlOwned + + tiberius::FromSqlOwned + {} + } else if #[cfg(all(feature = "mysql", feature = "mssql"))] { + pub trait FromSql<'a, T>: mysql_async::prelude::FromValue + + tiberius::FromSql<'a> {} + impl<'a, T> FromSql<'a, T> for T where T: + mysql_async::prelude::FromValue + + tiberius::FromSql<'a> + {} + + pub trait FromSqlOwnedValue: mysql_async::prelude::FromValue + + tiberius::FromSqlOwned {} + impl FromSqlOwnedValue for T where T: + mysql_async::prelude::FromValue + + tiberius::FromSqlOwned + {} + } else if #[cfg(feature = "postgres")] { + pub trait FromSql<'a, T>: tokio_postgres::types::FromSql<'a> {} + impl<'a, T> FromSql<'a, T> for T where T: + tokio_postgres::types::FromSql<'a> {} + + pub trait FromSqlOwnedValue: tokio_postgres::types::FromSqlOwned {} + impl FromSqlOwnedValue for T where T: + tokio_postgres::types::FromSqlOwned {} + } else if #[cfg(feature = "mysql")] { + pub trait FromSql<'a, T>: mysql_async::prelude::FromValue {} + impl<'a, T> FromSql<'a, T> for T where T: + mysql_async::prelude::FromValue {} + + pub trait FromSqlOwnedValue: mysql_async::prelude::FromValue {} + impl FromSqlOwnedValue for T where T: + mysql_async::prelude::FromValue {} + } else if #[cfg(feature = "mssql")] { + pub trait FromSql<'a, T>: tiberius::FromSql<'a> {} + impl<'a, T> FromSql<'a, T> for T where T: + tiberius::FromSql<'a> {} + + pub trait FromSqlOwnedValue: tiberius::FromSqlOwned {} + impl FromSqlOwnedValue for T where T: + tiberius::FromSqlOwned {} + } +} diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index 49466f67..c5a4dc66 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -44,8 +44,6 @@ fn test_generated_sql_by_the_select_querybuilder() { .r#where(LeagueFieldValue::id(&7), Comp::Gt) .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); - // .query() - // .await; // NOTE: We don't have in the docker the generated relationships // with the joins, so for now, we are just going to check that the // generated SQL by the SelectQueryBuilder is the expected From 8f1f4aeb87b0b2c8ca296972d21c5407a28eb219 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 9 May 2025 16:43:48 +0200 Subject: [PATCH 113/155] refactor: changed some re-exports of the public API that was in the incorrect crate --- canyon_core/src/connection/conn_errors.rs | 4 +++- canyon_entities/src/entity.rs | 2 +- canyon_macros/src/foreignkeyable_macro.rs | 6 +++--- canyon_macros/src/lib.rs | 2 +- canyon_macros/src/query_operations/delete.rs | 4 ++-- canyon_macros/src/query_operations/foreign_key.rs | 10 +++++----- canyon_macros/src/query_operations/insert.rs | 12 ++++++------ canyon_macros/src/query_operations/read.rs | 10 +++++----- canyon_macros/src/query_operations/update.rs | 6 +++--- src/lib.rs | 5 +++-- tests/migrations/mod.rs | 2 +- 11 files changed, 33 insertions(+), 30 deletions(-) diff --git a/canyon_core/src/connection/conn_errors.rs b/canyon_core/src/connection/conn_errors.rs index 64f80cf6..5cee0ad5 100644 --- a/canyon_core/src/connection/conn_errors.rs +++ b/canyon_core/src/connection/conn_errors.rs @@ -8,7 +8,9 @@ pub struct DatasourceNotFound { impl From> for DatasourceNotFound { fn from(value: Option<&str>) -> Self { DatasourceNotFound { - datasource_name: value.map(String::from).unwrap_or_default(), // TODO: not default + datasource_name: value + .map(String::from) + .unwrap_or_else(|| String::from("No datasource name was provided")) } } } diff --git a/canyon_entities/src/entity.rs b/canyon_entities/src/entity.rs index bb1e9297..aeee23e4 100644 --- a/canyon_entities/src/entity.rs +++ b/canyon_entities/src/entity.rs @@ -50,7 +50,7 @@ impl CanyonEntity { .iter() .map(|f| { let field_name = &f.name; - quote! { #field_name(&'a dyn canyon_sql::core::QueryParameter<'a>) } + quote! { #field_name(&'a dyn canyon_sql::query::QueryParameter<'a>) } }) .collect::>() } diff --git a/canyon_macros/src/foreignkeyable_macro.rs b/canyon_macros/src/foreignkeyable_macro.rs index 72251d18..69909fd5 100644 --- a/canyon_macros/src/foreignkeyable_macro.rs +++ b/canyon_macros/src/foreignkeyable_macro.rs @@ -18,7 +18,7 @@ pub fn foreignkeyable_impl_tokens(ast: DeriveInput) -> TokenStream { let field_idents = fields.iter().map(|(_vis, ident)| { let i = ident.to_string(); quote! { - #i => Some(&self.#ident as &dyn canyon_sql::core::QueryParameter<'_>) + #i => Some(&self.#ident as &dyn canyon_sql::query::QueryParameter<'_>) } }); let field_idents_cloned = field_idents.clone(); @@ -27,7 +27,7 @@ pub fn foreignkeyable_impl_tokens(ast: DeriveInput) -> TokenStream { /// Implementation of the trait `ForeignKeyable` for the type /// calling this derive proc macro impl canyon_sql::query::bounds::ForeignKeyable for #ty { - fn get_fk_column(&self, column: &str) -> Option<&dyn canyon_sql::core::QueryParameter<'_>> { + fn get_fk_column(&self, column: &str) -> Option<&dyn canyon_sql::query::QueryParameter<'_>> { match column { #(#field_idents),*, _ => None @@ -37,7 +37,7 @@ pub fn foreignkeyable_impl_tokens(ast: DeriveInput) -> TokenStream { /// Implementation of the trait `ForeignKeyable` for a reference of this type /// calling this derive proc macro impl canyon_sql::query::bounds::ForeignKeyable<&Self> for &#ty { - fn get_fk_column<'a>(&self, column: &'a str) -> Option<&dyn canyon_sql::core::QueryParameter<'_>> { + fn get_fk_column<'a>(&self, column: &'a str) -> Option<&dyn canyon_sql::query::QueryParameter<'_>> { match column { #(#field_idents_cloned),*, _ => None diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index cfc74e66..6934f442 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -180,7 +180,7 @@ pub fn querybuilder_fields(input: CompilerTokenStream) -> CompilerTokenStream { let generated_enum_type_for_fields_values = generate_enum_with_fields_values(&entity); quote! { - use canyon_sql::core::QueryParameter; + use canyon_sql::query::QueryParameter; use canyon_sql::query::bounds::TableMetadata; use canyon_sql::query::bounds::FieldIdentifier; diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index 923e7266..cf0e1c1d 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -21,13 +21,13 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri /// the current instance of a T type, returning a result /// indicating a possible failure querying the database with the specified datasource. async fn delete_with<'a, I>(&self, input: I) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'a)>> - where I: canyon_sql::core::DbConnection + Send + 'a + where I: canyon_sql::connection::DbConnection + Send + 'a }; if let Some(primary_key) = pk { let pk_field = Ident::new(&primary_key, Span::call_site()); let pk_field_value = - quote! { &self.#pk_field as &dyn canyon_sql::core::QueryParameter<'_> }; + quote! { &self.#pk_field as &dyn canyon_sql::query::QueryParameter<'_> }; let delete_stmt = format!( "DELETE FROM {} WHERE {:?} = $1", table_schema_data, primary_key diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index 441d1915..23867163 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -86,7 +86,7 @@ fn generate_find_by_foreign_key_tokens( let quoted_with_method_signature: TokenStream = quote! { async fn #method_name_ident_with<'a, I>(&self, input: I) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> - where I: canyon_sql::core::DbConnection + Send + 'a + where I: canyon_sql::connection::DbConnection + Send + 'a }; let stmt = format!( @@ -101,11 +101,11 @@ fn generate_find_by_foreign_key_tokens( #quoted_method_signature { <#fk_ty as canyon_sql::core::Transaction>::query_one::< &str, - &[&dyn canyon_sql::core::QueryParameter<'_>], + &[&dyn canyon_sql::query::QueryParameter<'_>], #fk_ty >( #stmt, - &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>], + &[&self.#field_ident as &dyn canyon_sql::query::QueryParameter<'_>], "" ).await } @@ -119,7 +119,7 @@ fn generate_find_by_foreign_key_tokens( #quoted_with_method_signature { input.query_one::<#fk_ty>( #stmt, - &[&self.#field_ident as &dyn canyon_sql::core::QueryParameter<'_>] + &[&self.#field_ident as &dyn canyon_sql::query::QueryParameter<'_>] ).await } }, @@ -167,7 +167,7 @@ fn generate_find_by_reverse_foreign_key_tokens( -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> where F: canyon_sql::query::bounds::ForeignKeyable + Send + Sync, - I: canyon_sql::core::DbConnection + Send + 'a + I: canyon_sql::connection::DbConnection + Send + 'a }; let f_ident = field_ident.to_string(); diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 51bd7f9d..3e524993 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -117,7 +117,7 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri -> Result<(), Box> { let input = ""; - let mut values: Vec<&dyn canyon_sql::core::QueryParameter<'_>> = vec![#(#insert_values),*]; + let mut values: Vec<&dyn canyon_sql::query::QueryParameter<'_>> = vec![#(#insert_values),*]; #insert_transaction } @@ -162,9 +162,9 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri async fn insert_with<'a, I>(&mut self, input: I) -> Result<(), Box> where - I: canyon_sql::core::DbConnection + Send + 'a + I: canyon_sql::connection::DbConnection + Send + 'a { - let mut values: Vec<&dyn canyon_sql::core::QueryParameter<'_>> = vec![#(#insert_values_cloned),*]; + let mut values: Vec<&dyn canyon_sql::query::QueryParameter<'_>> = vec![#(#insert_values_cloned),*]; #insert_transaction } @@ -425,7 +425,7 @@ fn _generate_multiple_insert_tokens( async fn multi_insert<'a, T>(instances: &'a mut [&'a mut T]) -> ( Result<(), Box> ) { - use canyon_sql::core::QueryParameter; + use canyon_sql::query::QueryParameter; let input = ""; let mut final_values: Vec>> = Vec::new(); @@ -482,9 +482,9 @@ fn _generate_multiple_insert_tokens( async fn multi_insert_with<'a, T, I>(instances: &'a mut [&'a mut T], input: I) -> Result<(), Box> where - I: canyon_sql::core::DbConnection + Send + 'a + I: canyon_sql::connection::DbConnection + Send + 'a { - use canyon_sql::core::QueryParameter; + use canyon_sql::query::QueryParameter; let mut final_values: Vec>> = Vec::new(); for instance in instances.iter() { diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 12601c8c..722e68eb 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -163,7 +163,7 @@ mod __details { async fn find_all_with<'a, I>(input: I) -> Result, Box<(dyn std::error::Error + Send + Sync)>> where - I: canyon_sql::core::DbConnection + Send + 'a + I: canyon_sql::connection::DbConnection + Send + 'a { input.query::<&str, #mapper_ty>(#stmt, &[]).await } @@ -221,7 +221,7 @@ mod __details { quote! { async fn count_with<'a, I>(input: I) -> Result> - where I: canyon_sql::core::DbConnection + Send + 'a + where I: canyon_sql::connection::DbConnection + Send + 'a { let res = input.query_rows(#stmt, &[]).await?; @@ -256,7 +256,7 @@ mod __details { }; quote! { - async fn find_by_pk<'a>(value: &'a dyn canyon_sql::core::QueryParameter<'a>) + async fn find_by_pk<'a>(value: &'a dyn canyon_sql::query::QueryParameter<'a>) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> { #body @@ -278,10 +278,10 @@ mod __details { }; quote! { - async fn find_by_pk_with<'a, I>(value: &'a dyn canyon_sql::core::QueryParameter<'a>, input: I) + async fn find_by_pk_with<'a, I>(value: &'a dyn canyon_sql::query::QueryParameter<'a>, input: I) -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> where - I: canyon_sql::core::DbConnection + Send + 'a + I: canyon_sql::connection::DbConnection + Send + 'a { #body } diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index 6a1ec5de..ac0bd905 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -31,7 +31,7 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let update_with_signature = quote! { async fn update_with<'a, I>(&self, input: I) -> Result> - where I: canyon_sql::core::DbConnection + Send + 'a + where I: canyon_sql::connection::DbConnection + Send + 'a }; if let Some(primary_key) = macro_data.get_primary_key_annotation() { @@ -46,11 +46,11 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri update_ops_tokens.extend(quote! { #update_signature { - let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = #update_values; + let update_values: &[&dyn canyon_sql::query::QueryParameter<'_>] = #update_values; <#ty as canyon_sql::core::Transaction>::execute(#stmt, update_values, "").await } #update_with_signature { - let update_values: &[&dyn canyon_sql::core::QueryParameter<'_>] = #update_values; + let update_values: &[&dyn canyon_sql::query::QueryParameter<'_>] = #update_values; input.execute(#stmt, update_values).await } }); diff --git a/src/lib.rs b/src/lib.rs index dc8cae8d..7737b08b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ extern crate canyon_core; extern crate canyon_crud; extern crate canyon_macros; + #[cfg(feature = "migrations")] extern crate canyon_migrations; @@ -29,13 +30,12 @@ pub mod macros { pub mod connection { pub use canyon_core::connection::database_type::DatabaseType; pub use canyon_core::connection::db_connector::DatabaseConnection; + pub use canyon_core::connection::contracts::DbConnection; } pub mod core { pub use canyon_core::canyon::Canyon; - pub use canyon_core::connection::contracts::DbConnection; // TODO: Available only via connection? pub use canyon_core::mapper::*; - pub use canyon_core::query::parameters::QueryParameter; // TODO: this re-export must be only available on pub mod query pub use canyon_core::rows::CanyonRows; pub use canyon_core::transaction::Transaction; } @@ -51,6 +51,7 @@ pub mod query { pub use canyon_core::query::bounds; pub use canyon_core::query::operators; pub use canyon_core::query::*; + pub use canyon_core::query::parameters::QueryParameter; } /// Reexport the available database clients within Canyon diff --git a/tests/migrations/mod.rs b/tests/migrations/mod.rs index b6f6e937..491224b1 100644 --- a/tests/migrations/mod.rs +++ b/tests/migrations/mod.rs @@ -2,7 +2,7 @@ use crate::constants; use canyon_sql::core::Canyon; -use canyon_sql::core::DbConnection; +use canyon_sql::connection::DbConnection; /// Integration tests for the migrations feature of `Canyon-SQL` use canyon_sql::core::Transaction; #[cfg(feature = "migrations")] From bf04f49bba9364cbc94f3f256344ef400cce6b13 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 9 May 2025 17:34:50 +0200 Subject: [PATCH 114/155] refactor: the crud_operations attribute in its `maps_to` argument is processed only once per procedural macro creation, and not per method invocation --- canyon_core/src/connection/conn_errors.rs | 2 +- canyon_core/src/rows.rs | 1 - canyon_macros/src/lib.rs | 8 +- .../src/query_operations/foreign_key.rs | 4 +- canyon_macros/src/query_operations/mod.rs | 4 +- canyon_macros/src/query_operations/read.rs | 8 +- .../src/utils/canyon_crud_attribute.rs | 9 ++- canyon_macros/src/utils/helpers.rs | 24 ------ canyon_macros/src/utils/macro_tokens.rs | 75 +++++++++++-------- .../src/migrations/information_schema.rs | 3 +- src/lib.rs | 4 +- tests/crud/init_mssql.rs | 2 +- tests/migrations/mod.rs | 2 +- 13 files changed, 68 insertions(+), 78 deletions(-) diff --git a/canyon_core/src/connection/conn_errors.rs b/canyon_core/src/connection/conn_errors.rs index 5cee0ad5..3b2ed455 100644 --- a/canyon_core/src/connection/conn_errors.rs +++ b/canyon_core/src/connection/conn_errors.rs @@ -10,7 +10,7 @@ impl From> for DatasourceNotFound { DatasourceNotFound { datasource_name: value .map(String::from) - .unwrap_or_else(|| String::from("No datasource name was provided")) + .unwrap_or_else(|| String::from("No datasource name was provided")), } } } diff --git a/canyon_core/src/rows.rs b/canyon_core/src/rows.rs index 99444457..3564e693 100644 --- a/canyon_core/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -132,7 +132,6 @@ impl CanyonRows { } } - cfg_if! { if #[cfg(all(feature = "postgres", feature = "mysql", feature = "mssql"))] { pub trait FromSql<'a, T>: tokio_postgres::types::FromSql<'a> diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index 6934f442..d49c3284 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -128,9 +128,15 @@ pub fn canyon_entity(meta: CompilerTokenStream, input: CompilerTokenStream) -> C #[proc_macro_derive(CanyonCrud, attributes(canyon_crud))] pub fn crud_operations(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast: DeriveInput = syn::parse(input).expect("Error implementing CanyonCrud AST"); + let macro_data = MacroTokens::new(&ast); - let table_name_res = helpers::table_schema_parser(¯o_data); + let macro_data = if let Err(err) = macro_data { + return err.to_compile_error().into(); + } else { + macro_data.unwrap() + }; + let table_name_res = helpers::table_schema_parser(¯o_data); let table_schema_data = if let Err(err) = table_name_res { return err.into(); } else { diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index 23867163..1a827a90 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -142,8 +142,8 @@ fn generate_find_by_reverse_foreign_key_tokens( let ty = macro_data.ty; let mapper_ty = macro_data .retrieve_mapping_target_type() - .expect("Expected mapping target ") - .unwrap_or_else(|| ty.clone()); + .as_ref() + .unwrap_or(ty); for (field_ident, fk_annot) in macro_data.get_fk_annotations().iter() { if let EntityFieldAnnotation::ForeignKey(table, column) = fk_annot { diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index 400c8d0b..8025e36b 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -25,8 +25,8 @@ pub fn impl_crud_operations_trait_for_struct( let ty = macro_data.ty; let mapper_ty = macro_data .retrieve_mapping_target_type() - .expect("Expected mapping macro data") - .unwrap_or_else(|| ty.clone()); + .as_ref() + .unwrap_or(ty); let read_operations_tokens = generate_read_operations_tokens(macro_data, &table_schema_data); let insert_tokens = generate_insert_tokens(macro_data, &table_schema_data); diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 722e68eb..e5f9ea99 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -12,8 +12,8 @@ pub fn generate_read_operations_tokens( let ty = macro_data.ty; let mapper_ty = macro_data .retrieve_mapping_target_type() - .expect("Expected mapping target ") // TODO: return Err(...) - .unwrap_or_else(|| ty.clone()); + .as_ref() + .unwrap_or(ty); let find_all_tokens = generate_find_all_operations_tokens(ty, &mapper_ty, table_schema_data); let count_tokens = generate_count_operations_tokens(ty, table_schema_data); @@ -101,8 +101,8 @@ fn generate_find_by_pk_operations_tokens( let ty = macro_data.ty; let mapper_ty = macro_data .retrieve_mapping_target_type() - .expect("Expected mapping target ") - .unwrap_or_else(|| ty.clone()); + .as_ref() + .unwrap_or(ty); let pk = macro_data.get_primary_key_annotation(); let no_pk_runtime_err = if pk.is_some() { None diff --git a/canyon_macros/src/utils/canyon_crud_attribute.rs b/canyon_macros/src/utils/canyon_crud_attribute.rs index 1ce08c34..473a01a2 100644 --- a/canyon_macros/src/utils/canyon_crud_attribute.rs +++ b/canyon_macros/src/utils/canyon_crud_attribute.rs @@ -2,17 +2,18 @@ use proc_macro2::Ident; use syn::parse::{Parse, ParseStream}; use syn::Token; -// TODO: docs -pub(super) struct CanyonCrudAttribute { +/// Type that helps to parse the: `#[canyon_crud(maps_to = Ident)]` proc macro attribute +/// +/// The ident value of the `maps_to` argument brings a type that is the target type for which +/// `CrudOperations` will write the queries as the implementor of [`RowMapper`] +pub(crate) struct CanyonCrudAttribute { pub maps_to: Option, } impl Parse for CanyonCrudAttribute { fn parse(input: ParseStream<'_>) -> syn::Result { - // Parse the argument name let arg_name: Ident = input.parse()?; if arg_name != "maps_to" { - // Same error as before when encountering an unsupported attribute return Err(syn::Error::new_spanned( arg_name, "unsupported 'canyon_crud' attribute, expected `maps_to`", diff --git a/canyon_macros/src/utils/helpers.rs b/canyon_macros/src/utils/helpers.rs index 42323f9f..88f155f5 100644 --- a/canyon_macros/src/utils/helpers.rs +++ b/canyon_macros/src/utils/helpers.rs @@ -97,30 +97,6 @@ pub fn table_schema_parser(macro_data: &MacroTokens<'_>) -> Result String { - let struct_name: String = ty.to_string(); - let mut table_name: String = String::new(); - - let mut index = 0; - for char in struct_name.chars() { - if index < 1 { - table_name.push(char.to_ascii_lowercase()); - index += 1; - } else { - match char { - n if n.is_ascii_uppercase() => { - table_name.push('_'); - table_name.push(n.to_ascii_lowercase()); - } - _ => table_name.push(char), - } - } - } - - table_name -} - /// Autogenerates a default table name for an entity given their struct name pub fn default_database_table_name_from_entity_name(ty: &str) -> String { let struct_name: String = ty.to_string(); diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index e11b493e..ba1821f4 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -1,8 +1,9 @@ use std::convert::TryFrom; +use std::fmt::Write; use crate::utils::canyon_crud_attribute::CanyonCrudAttribute; use canyon_entities::field_annotation::EntityFieldAnnotation; -use proc_macro2::Ident; +use proc_macro2::{Ident, Span}; use syn::{Attribute, DeriveInput, Fields, Generics, Type, Visibility}; /// Provides a convenient way of store the data for the TokenStream @@ -14,6 +15,8 @@ pub struct MacroTokens<'a> { pub generics: &'a Generics, pub attrs: &'a Vec, pub fields: &'a Fields, + // -------- the new fields that must help to avoid recalculations every time that the user compiles + pub(crate) canyon_crud_attribute: Option, } // TODO: this struct, as is, is not really useful. There's tons of methods that must be called and @@ -22,29 +25,40 @@ pub struct MacroTokens<'a> { // the fk operations, the mapping target type... impl<'a> MacroTokens<'a> { - pub fn new(ast: &'a DeriveInput) -> Self { - Self { - vis: &ast.vis, - ty: &ast.ident, - generics: &ast.generics, - attrs: &ast.attrs, - fields: match &ast.data { - syn::Data::Struct(ref s) => &s.fields, - _ => panic!("This derive macro can only be automatically derived for structs"), - }, + pub fn new(ast: &'a DeriveInput) -> Result { + if let syn::Data::Struct(ref s) = ast.data { + let attrs = &ast.attrs; + let mut canyon_crud_attribute = None; + for attr in attrs { + if attr.path.is_ident("canyon_crud") { + canyon_crud_attribute = Some(attr.parse_args::()?); + } + } + + Ok(Self { + vis: &ast.vis, + ty: &ast.ident, + generics: &ast.generics, + attrs: &ast.attrs, + fields: &s.fields, + canyon_crud_attribute, + }) + } else { + Err(syn::Error::new( + Span::call_site(), + "unsupported 'canyon_crud' attribute, expected `maps_to`", + )) } } // TODO: this must be refactored in order to avoid to make the operation everytime that // this method is queried. The trick w'd be to have a map to relate the entries. - pub fn retrieve_mapping_target_type(&self) -> Result, syn::Error> { - for attr in self.attrs { - if attr.path.is_ident("canyon_crud") { - let meta: CanyonCrudAttribute = attr.parse_args()?; - return Ok(meta.maps_to); - } + pub fn retrieve_mapping_target_type(&self) -> &Option { + if let Some(canyon_crud_attribute) = &self.canyon_crud_attribute { + &canyon_crud_attribute.maps_to + } else { + &None } - Ok(None) } /// Gives a Vec of tuples that contains the visibility, the name and @@ -202,23 +216,18 @@ impl<'a> MacroTokens<'a> { /// Already returns the correct number of placeholders, skipping one /// entry in the type contains a `#[primary_key]` pub fn placeholders_generator(&self) -> String { - let mut placeholders = String::new(); - if self.type_has_primary_key() { - for num in 1..self.fields.len() { - if num < self.fields.len() - 1 { - placeholders.push_str(&("$".to_owned() + &(num).to_string() + ", ")); - } else { - placeholders.push_str(&("$".to_owned() + &(num).to_string())); - } - } + let range_upper_bound = if self.type_has_primary_key() { + self.fields.len() } else { - for num in 1..self.fields.len() + 1 { - if num < self.fields.len() { - placeholders.push_str(&("$".to_owned() + &(num).to_string() + ", ")); - } else { - placeholders.push_str(&("$".to_owned() + &(num).to_string())); - } + self.fields.len() + 1 + }; + + let mut placeholders = String::new(); + for (i, n) in (1..range_upper_bound).enumerate() { + if i > 0 { + placeholders.push_str(", "); } + write!(placeholders, "${}", n).unwrap(); } placeholders diff --git a/canyon_migrations/src/migrations/information_schema.rs b/canyon_migrations/src/migrations/information_schema.rs index dfcd5345..3b25086a 100644 --- a/canyon_migrations/src/migrations/information_schema.rs +++ b/canyon_migrations/src/migrations/information_schema.rs @@ -1,6 +1,5 @@ #[cfg(feature = "mssql")] -use canyon_core::connection::tiberius::ColumnType as TIB_TY; // TODO: make them internal public reexports (only for Canyon) -#[cfg(feature = "postgres")] +use canyon_core::connection::tiberius::ColumnType as TIB_TY; use canyon_core::connection::tokio_postgres::types::Type as TP_TYP; use canyon_core::{ column::{Column, ColumnType}, diff --git a/src/lib.rs b/src/lib.rs index 7737b08b..c7be8779 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,9 +28,9 @@ pub mod macros { /// connection module serves to reexport the public elements of the `canyon_connection` crate, /// exposing them through the public API pub mod connection { + pub use canyon_core::connection::contracts::DbConnection; pub use canyon_core::connection::database_type::DatabaseType; pub use canyon_core::connection::db_connector::DatabaseConnection; - pub use canyon_core::connection::contracts::DbConnection; } pub mod core { @@ -50,8 +50,8 @@ pub mod crud { pub mod query { pub use canyon_core::query::bounds; pub use canyon_core::query::operators; - pub use canyon_core::query::*; pub use canyon_core::query::parameters::QueryParameter; + pub use canyon_core::query::*; } /// Reexport the available database clients within Canyon diff --git a/tests/crud/init_mssql.rs b/tests/crud/init_mssql.rs index 3534f457..a0eb6083 100644 --- a/tests/crud/init_mssql.rs +++ b/tests/crud/init_mssql.rs @@ -22,7 +22,7 @@ use canyon_sql::runtime::tokio_util::compat::TokioAsyncWriteCompatExt; #[canyon_sql::macros::canyon_tokio_test] #[ignore] fn initialize_sql_server_docker_instance() { - static CONN_STR: &str = // TODO: change this for the DS when will be in the public API + static CONN_STR: &str = "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true;Encrypt=true"; canyon_sql::runtime::futures::executor::block_on(async { diff --git a/tests/migrations/mod.rs b/tests/migrations/mod.rs index 491224b1..4546cf3c 100644 --- a/tests/migrations/mod.rs +++ b/tests/migrations/mod.rs @@ -1,8 +1,8 @@ #![allow(unused_imports)] use crate::constants; -use canyon_sql::core::Canyon; use canyon_sql::connection::DbConnection; +use canyon_sql::core::Canyon; /// Integration tests for the migrations feature of `Canyon-SQL` use canyon_sql::core::Transaction; #[cfg(feature = "migrations")] From 553b6850bd9ee0b327cc9b932c8cb9e25892fbea Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 9 May 2025 17:50:47 +0200 Subject: [PATCH 115/155] refactor: little improvements on MacroTokens --- canyon_macros/src/query_operations/insert.rs | 8 ++--- canyon_macros/src/utils/macro_tokens.rs | 32 ++------------------ 2 files changed, 6 insertions(+), 34 deletions(-) diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 3e524993..fc7879c9 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -41,9 +41,9 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri .into_iter() .find(|(i, _t)| Some(i.to_string()) == primary_key); - let insert_transaction = if let Some(pk_data) = &pk_ident_type { - let pk_ident = &pk_data.0; - let pk_type = &pk_data.1; + let insert_transaction = if let Some(pk_data) = pk_ident_type { + let pk_ident = pk_data.0; + let pk_type = pk_data.1; quote! { #remove_pk_value_from_fn_entry; @@ -202,7 +202,7 @@ fn _generate_multiple_insert_tokens( let pk_ident_type = macro_data .fields_with_types() .into_iter() - .find(|(i, _t)| *i == pk); + .find(|(i, _t)| *i == &pk); let multi_insert_transaction = if let Some(pk_data) = &pk_ident_type { let pk_ident = &pk_data.0; diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index ba1821f4..61e9b79e 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -19,11 +19,6 @@ pub struct MacroTokens<'a> { pub(crate) canyon_crud_attribute: Option, } -// TODO: this struct, as is, is not really useful. There's tons of methods that must be called and -// process data everytime a Crud Operation needs them. W'd be much more efficient to have a struct -// that holds most of the data already processed, for example, the pk annotations, -// the fk operations, the mapping target type... - impl<'a> MacroTokens<'a> { pub fn new(ast: &'a DeriveInput) -> Result { if let syn::Data::Struct(ref s) = ast.data { @@ -61,27 +56,12 @@ impl<'a> MacroTokens<'a> { } } - /// Gives a Vec of tuples that contains the visibility, the name and - /// the type of every field on a Struct - pub fn _fields_with_visibility_and_types(&self) -> Vec<(Visibility, Ident, Type)> { - self.fields - .iter() - .map(|field| { - ( - field.vis.clone(), - field.ident.as_ref().unwrap().clone(), - field.ty.clone(), - ) - }) - .collect::>() - } - /// Gives a Vec of tuples that contains the name and /// the type of every field on a Struct - pub fn fields_with_types(&self) -> Vec<(Ident, Type)> { + pub fn fields_with_types(&self) -> Vec<(&Ident, &Type)> { self.fields .iter() - .map(|field| (field.ident.as_ref().unwrap().clone(), field.ty.clone())) + .map(|field| (field.ident.as_ref().unwrap(), &field.ty)) .collect::>() } @@ -93,14 +73,6 @@ impl<'a> MacroTokens<'a> { .collect::>() } - /// Gives a Vec populated with the name of the fields of the struct - pub fn _get_struct_fields_as_collection_strings(&self) -> Vec { - self.get_struct_fields() - .iter() - .map(|ident| ident.to_owned().to_string()) - .collect::>() - } - /// Returns a Vec populated with the name of the fields of the struct /// already quote scaped for avoid the upper case column name mangling. /// From 1b1f3eb744e1736adc38a68f2927a86c457c814a Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 13 May 2025 09:55:35 +0200 Subject: [PATCH 116/155] fix: hiding non-relevant warnings that are raised when not all cfg features are enabled --- canyon_core/src/connection/contracts/impl/mod.rs | 3 +++ canyon_core/src/row.rs | 2 ++ canyon_core/src/rows.rs | 2 ++ canyon_macros/src/canyon_mapper_macro.rs | 2 ++ canyon_migrations/src/migrations/handler.rs | 4 +++- 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/canyon_core/src/connection/contracts/impl/mod.rs b/canyon_core/src/connection/contracts/impl/mod.rs index 27550f06..95b6eb11 100644 --- a/canyon_core/src/connection/contracts/impl/mod.rs +++ b/canyon_core/src/connection/contracts/impl/mod.rs @@ -1,5 +1,8 @@ +#[cfg(feature = "mssql")] pub mod mssql; +#[cfg(feature = "mysql")] pub mod mysql; +#[cfg(feature = "postgres")] pub mod postgresql; #[macro_use] diff --git a/canyon_core/src/row.rs b/canyon_core/src/row.rs index 76cf1fce..d00eec9a 100644 --- a/canyon_core/src/row.rs +++ b/canyon_core/src/row.rs @@ -1,3 +1,5 @@ +#![allow(unused_imports)] + #[cfg(feature = "mysql")] use mysql_async::{self}; #[cfg(feature = "mssql")] diff --git a/canyon_core/src/rows.rs b/canyon_core/src/rows.rs index 3564e693..c6312cd9 100644 --- a/canyon_core/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -1,3 +1,5 @@ +#![allow(unreachable_patterns)] + //! The rows module of Canyon-SQL. //! //! This module defines the `CanyonRows` enum, which wraps database query results for supported diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index f166565b..e2685196 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -1,3 +1,5 @@ +#![allow(unused_imports)] + use crate::utils::helpers::fields_with_types; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index 2c924fb8..e6235edd 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -115,13 +115,15 @@ impl Migrations { /// Handler for parse the result of query the information of some database schema, /// and extract the content of the returned rows into custom structures with /// the data well organized for every entity present on that schema + #[allow(unreachable_patterns)] fn map_rows(db_results: CanyonRows, db_type: DatabaseType) -> Vec { match db_results { #[cfg(feature = "postgres")] CanyonRows::Postgres(v) => Self::process_tp_rows(v, db_type), #[cfg(feature = "mssql")] CanyonRows::Tiberius(v) => Self::process_tib_rows(v, db_type), - _ => panic!(), + #[cfg(feature = "mysql")] + CanyonRows::MySQL(v) => panic!("Not implemented fetch database in mysql"), } } From 6d5bd77651853c0fa32c5f30ae44036362d4665a Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 13 May 2025 13:01:10 +0200 Subject: [PATCH 117/155] feat: The Query type now implement AsRef chore: removed the + Display bound on the generic statement parameters that receives the sql sentences --- canyon_core/src/connection/clients/mssql.rs | 3 +-- canyon_core/src/connection/clients/mysql.rs | 4 ++-- canyon_core/src/connection/clients/postgresql.rs | 2 +- .../src/connection/contracts/impl/database_connection.rs | 8 ++++---- canyon_core/src/connection/contracts/impl/mssql.rs | 4 ++-- canyon_core/src/connection/contracts/impl/mysql.rs | 4 ++-- canyon_core/src/connection/contracts/impl/postgresql.rs | 4 ++-- canyon_core/src/connection/contracts/impl/str.rs | 2 +- canyon_core/src/connection/contracts/mod.rs | 3 +-- canyon_core/src/query/query.rs | 4 ++++ 10 files changed, 20 insertions(+), 18 deletions(-) diff --git a/canyon_core/src/connection/clients/mssql.rs b/canyon_core/src/connection/clients/mssql.rs index 0af1f4c5..e17d4479 100644 --- a/canyon_core/src/connection/clients/mssql.rs +++ b/canyon_core/src/connection/clients/mssql.rs @@ -2,7 +2,6 @@ use crate::{query::parameters::QueryParameter, rows::CanyonRows}; #[cfg(feature = "mssql")] use async_std::net::TcpStream; use std::error::Error; -use std::fmt::Display; use tiberius::Query; /// A connection with a `SqlServer` database @@ -25,7 +24,7 @@ pub(crate) mod sqlserver_query_launcher { conn: &SqlServerConnection, ) -> Result, Box<(dyn Error + Send + Sync)>> where - S: AsRef + Display + Send, + S: AsRef + Send, R: RowMapper, Vec: FromIterator<::Output>, { diff --git a/canyon_core/src/connection/clients/mysql.rs b/canyon_core/src/connection/clients/mysql.rs index 905f6405..00f95935 100644 --- a/canyon_core/src/connection/clients/mysql.rs +++ b/canyon_core/src/connection/clients/mysql.rs @@ -37,7 +37,7 @@ pub(crate) mod mysql_query_launcher { conn: &MysqlConnection, ) -> Result, Box<(dyn Error + Send + Sync)>> where - S: AsRef + Display + Send, + S: AsRef + Send, R: RowMapper, Vec: FromIterator<::Output>, { @@ -96,7 +96,7 @@ pub(crate) mod mysql_query_launcher { conn: &MysqlConnection, ) -> Result, Box<(dyn Error + Send + Sync)>> where - S: AsRef + Display + Send, + S: AsRef + Send, { let mysql_connection = conn.client.get_conn().await?; let is_insert = stmt.as_ref().find(" RETURNING"); diff --git a/canyon_core/src/connection/clients/postgresql.rs b/canyon_core/src/connection/clients/postgresql.rs index a01774df..bb5e368a 100644 --- a/canyon_core/src/connection/clients/postgresql.rs +++ b/canyon_core/src/connection/clients/postgresql.rs @@ -26,7 +26,7 @@ pub(crate) mod postgres_query_launcher { conn: &PostgreSqlConnection, ) -> Result, Box<(dyn Error + Send + Sync)>> where - S: AsRef + Display + Send, + S: AsRef + Send, R: RowMapper, Vec: FromIterator<::Output>, { diff --git a/canyon_core/src/connection/contracts/impl/database_connection.rs b/canyon_core/src/connection/contracts/impl/database_connection.rs index 87f0adf9..33e42fe7 100644 --- a/canyon_core/src/connection/contracts/impl/database_connection.rs +++ b/canyon_core/src/connection/contracts/impl/database_connection.rs @@ -6,7 +6,7 @@ use crate::{ query::parameters::QueryParameter, rows::{CanyonRows, FromSqlOwnedValue}, }; -use std::{error::Error, fmt::Display}; +use std::error::Error; impl DbConnection for DatabaseConnection { async fn query_rows<'a>( @@ -23,7 +23,7 @@ impl DbConnection for DatabaseConnection { params: &[&'a (dyn QueryParameter<'a>)], ) -> Result, Box<(dyn Error + Send + Sync)>> where - S: AsRef + Display + Send, + S: AsRef + Send, R: RowMapper, Vec: FromIterator<::Output>, { @@ -77,7 +77,7 @@ impl DbConnection for &mut DatabaseConnection { params: &[&'a (dyn QueryParameter<'a>)], ) -> Result, Box<(dyn Error + Send + Sync)>> where - S: AsRef + Display + Send, + S: AsRef + Send, R: RowMapper, Vec: FromIterator<::Output>, { @@ -159,7 +159,7 @@ pub(crate) async fn db_conn_query_impl<'a, S, R>( params: &[&'a (dyn QueryParameter<'a>)], ) -> Result, Box<(dyn Error + Send + Sync)>> where - S: AsRef + Display + Send, + S: AsRef + Send, R: RowMapper, Vec: FromIterator<::Output>, { diff --git a/canyon_core/src/connection/contracts/impl/mssql.rs b/canyon_core/src/connection/contracts/impl/mssql.rs index 01153121..04d69d5f 100644 --- a/canyon_core/src/connection/contracts/impl/mssql.rs +++ b/canyon_core/src/connection/contracts/impl/mssql.rs @@ -8,7 +8,7 @@ use crate::{ query::parameters::QueryParameter, rows::{CanyonRows, FromSqlOwnedValue}, }; -use std::{error::Error, fmt::Display, future::Future}; +use std::{error::Error, future::Future}; impl DbConnection for SqlServerConnection { fn query_rows<'a>( @@ -25,7 +25,7 @@ impl DbConnection for SqlServerConnection { params: &[&'a (dyn QueryParameter<'a>)], ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where - S: AsRef + Display + Send, + S: AsRef + Send, R: RowMapper, Vec: FromIterator<::Output>, { diff --git a/canyon_core/src/connection/contracts/impl/mysql.rs b/canyon_core/src/connection/contracts/impl/mysql.rs index 04f332c1..5d51ae4b 100644 --- a/canyon_core/src/connection/contracts/impl/mysql.rs +++ b/canyon_core/src/connection/contracts/impl/mysql.rs @@ -7,7 +7,7 @@ use crate::{ query::parameters::QueryParameter, rows::{CanyonRows, FromSqlOwnedValue}, }; -use std::{error::Error, fmt::Display, future::Future}; +use std::{error::Error, future::Future}; impl DbConnection for MysqlConnection { fn query_rows<'a>( @@ -24,7 +24,7 @@ impl DbConnection for MysqlConnection { params: &[&'a (dyn QueryParameter<'a>)], ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where - S: AsRef + Display + Send, + S: AsRef + Send, R: RowMapper, Vec: FromIterator<::Output>, { diff --git a/canyon_core/src/connection/contracts/impl/postgresql.rs b/canyon_core/src/connection/contracts/impl/postgresql.rs index d9b061a2..59d23544 100644 --- a/canyon_core/src/connection/contracts/impl/postgresql.rs +++ b/canyon_core/src/connection/contracts/impl/postgresql.rs @@ -8,7 +8,7 @@ use crate::{ query::parameters::QueryParameter, rows::{CanyonRows, FromSqlOwnedValue}, }; -use std::{error::Error, fmt::Display, future::Future}; +use std::{error::Error, future::Future}; impl DbConnection for PostgreSqlConnection { fn query_rows<'a>( @@ -25,7 +25,7 @@ impl DbConnection for PostgreSqlConnection { params: &[&'a (dyn QueryParameter<'a>)], ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where - S: AsRef + Display + Send, + S: AsRef + Send, R: RowMapper, Vec: FromIterator<::Output>, { diff --git a/canyon_core/src/connection/contracts/impl/str.rs b/canyon_core/src/connection/contracts/impl/str.rs index 8c8974e1..8d4de229 100644 --- a/canyon_core/src/connection/contracts/impl/str.rs +++ b/canyon_core/src/connection/contracts/impl/str.rs @@ -20,7 +20,7 @@ macro_rules! impl_db_connection { params: &[&'a (dyn crate::query::parameters::QueryParameter<'a>)], ) -> Result, Box<(dyn std::error::Error + Send + Sync)>> where - S: AsRef + std::fmt::Display + Send, + S: AsRef + Send, R: crate::mapper::RowMapper, Vec: std::iter::FromIterator<::Output>, { diff --git a/canyon_core/src/connection/contracts/mod.rs b/canyon_core/src/connection/contracts/mod.rs index 934961a5..981c46da 100644 --- a/canyon_core/src/connection/contracts/mod.rs +++ b/canyon_core/src/connection/contracts/mod.rs @@ -3,7 +3,6 @@ use crate::mapper::RowMapper; use crate::query::parameters::QueryParameter; use crate::rows::{CanyonRows, FromSqlOwnedValue}; use std::error::Error; -use std::fmt::Display; use std::future::Future; mod r#impl; @@ -62,7 +61,7 @@ pub trait DbConnection { params: &[&'a (dyn QueryParameter<'a>)], ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where - S: AsRef + Display + Send, + S: AsRef + Send, R: RowMapper, Vec: FromIterator<::Output>; diff --git a/canyon_core/src/query/query.rs b/canyon_core/src/query/query.rs index 4a4fe1af..78337fe1 100644 --- a/canyon_core/src/query/query.rs +++ b/canyon_core/src/query/query.rs @@ -20,6 +20,10 @@ pub struct Query<'a> { pub params: Vec<&'a dyn QueryParameter<'a>>, } +impl AsRef for Query<'_> { + fn as_ref(&self) -> &str { self.sql.as_str() } +} + impl<'a> Query<'a> { pub fn new(sql: String) -> Query<'a> { Self { From b95f0bec07f0c8eeb45e87c6ea252a4ce2c05775 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 13 May 2025 17:02:20 +0200 Subject: [PATCH 118/155] fix: missing the generics on the generated tokens for CrudOperations --- canyon_macros/src/query_operations/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index 8025e36b..8a105f79 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -27,6 +27,7 @@ pub fn impl_crud_operations_trait_for_struct( .retrieve_mapping_target_type() .as_ref() .unwrap_or(ty); + let generics = macro_data.generics; let read_operations_tokens = generate_read_operations_tokens(macro_data, &table_schema_data); let insert_tokens = generate_insert_tokens(macro_data, &table_schema_data); @@ -44,11 +45,11 @@ pub fn impl_crud_operations_trait_for_struct( use canyon_sql::core::IntoResults; use canyon_sql::core::RowMapper; - impl canyon_sql::crud::CrudOperations<#mapper_ty> for #ty { + impl #generics canyon_sql::crud::CrudOperations<#mapper_ty> for #ty #generics { #crud_operations_tokens } - impl canyon_sql::core::Transaction for #ty {} + impl #generics canyon_sql::core::Transaction for #ty #generics {} }); // NOTE: this extends should be documented WHY is needed to be after the base impl of CrudOperations From 66cefe325489de668603576c63cb48d291eaf67a Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 13 May 2025 17:56:32 +0200 Subject: [PATCH 119/155] feat(mssql-broken)!: updated the way on how the count crud operation is internally generated (Care:! We broke the Tiberius implementation because the type requeriments of their api for the moment, is pending to be fixed) --- canyon_core/src/connection/clients/mysql.rs | 3 +- .../src/connection/clients/postgresql.rs | 3 +- canyon_core/src/query/query.rs | 4 +- canyon_core/src/transaction.rs | 12 ++--- canyon_macros/src/query_operations/read.rs | 51 +++---------------- 5 files changed, 17 insertions(+), 56 deletions(-) diff --git a/canyon_core/src/connection/clients/mysql.rs b/canyon_core/src/connection/clients/mysql.rs index 00f95935..73cdfa13 100644 --- a/canyon_core/src/connection/clients/mysql.rs +++ b/canyon_core/src/connection/clients/mysql.rs @@ -7,7 +7,6 @@ use mysql_async::Row; use mysql_common::constants::ColumnType; use mysql_common::row; use std::error::Error; -use std::fmt::Display; /// A connection with a `Mysql` database #[cfg(feature = "mysql")] @@ -126,7 +125,7 @@ pub(crate) mod mysql_query_launcher { conn: &MysqlConnection, ) -> Result> where - S: AsRef + Display + Send, + S: AsRef + Send, { let mysql_connection = conn.client.get_conn().await?; let mysql_stmt = generate_mysql_stmt(stmt.as_ref(), params)?; diff --git a/canyon_core/src/connection/clients/postgresql.rs b/canyon_core/src/connection/clients/postgresql.rs index bb5e368a..60d64251 100644 --- a/canyon_core/src/connection/clients/postgresql.rs +++ b/canyon_core/src/connection/clients/postgresql.rs @@ -1,7 +1,6 @@ use crate::mapper::RowMapper; use crate::{query::parameters::QueryParameter, rows::CanyonRows}; use std::error::Error; -use std::fmt::Display; #[cfg(feature = "postgres")] use tokio_postgres::Client; @@ -100,7 +99,7 @@ pub(crate) mod postgres_query_launcher { conn: &PostgreSqlConnection, ) -> Result> where - S: AsRef + Display + Send, + S: AsRef + Send, { conn.client .execute(stmt.as_ref(), &get_psql_params(params)) diff --git a/canyon_core/src/query/query.rs b/canyon_core/src/query/query.rs index 78337fe1..51bb1a9b 100644 --- a/canyon_core/src/query/query.rs +++ b/canyon_core/src/query/query.rs @@ -21,7 +21,9 @@ pub struct Query<'a> { } impl AsRef for Query<'_> { - fn as_ref(&self) -> &str { self.sql.as_str() } + fn as_ref(&self) -> &str { + self.sql.as_str() + } } impl<'a> Query<'a> { diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index 6397e9e6..6804551a 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -3,7 +3,7 @@ use crate::mapper::RowMapper; use crate::rows::FromSqlOwnedValue; use crate::{query::parameters::QueryParameter, rows::CanyonRows}; use std::error::Error; -use std::{fmt::Display, future::Future}; +use std::future::Future; /// The `Transaction` trait serves as a proxy for types implementing CRUD operations. /// @@ -47,7 +47,7 @@ pub trait Transaction { input: impl DbConnection + Send, ) -> impl Future, Box<(dyn Error + Send + Sync)>>> where - S: AsRef + Display + Send, + S: AsRef + Send, R: RowMapper, Vec: FromIterator<::Output>, { @@ -60,7 +60,7 @@ pub trait Transaction { input: impl DbConnection + Send + 'a, ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where - S: AsRef + Display + Send + 'a, + S: AsRef + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send, R: RowMapper, { @@ -73,7 +73,7 @@ pub trait Transaction { input: impl DbConnection + Send + 'a, ) -> impl Future>> + Send where - S: AsRef + Display + Send + 'a, + S: AsRef + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send + 'a, { async move { input.query_one_for(stmt.as_ref(), params.as_ref()).await } @@ -88,7 +88,7 @@ pub trait Transaction { input: impl DbConnection + Send + 'a, ) -> impl Future>> + Send where - S: AsRef + Display + Send + 'a, + S: AsRef + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send + 'a, { async move { input.query_rows(stmt.as_ref(), params.as_ref()).await } @@ -100,7 +100,7 @@ pub trait Transaction { input: impl DbConnection + Send + 'a, ) -> impl Future>> + Send where - S: AsRef + Display + Send + 'a, + S: AsRef + Send + 'a, Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send + 'a, { async move { input.execute(stmt.as_ref(), params.as_ref()).await } diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index e5f9ea99..9d7c11f0 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -15,7 +15,7 @@ pub fn generate_read_operations_tokens( .as_ref() .unwrap_or(ty); - let find_all_tokens = generate_find_all_operations_tokens(ty, &mapper_ty, table_schema_data); + let find_all_tokens = generate_find_all_operations_tokens(ty, mapper_ty, table_schema_data); let count_tokens = generate_count_operations_tokens(ty, table_schema_data); let find_by_pk_tokens = generate_find_by_pk_operations_tokens(macro_data, table_schema_data); let read_querybuilder_ops = generate_select_querybuilder_tokens(table_schema_data); @@ -86,7 +86,7 @@ fn generate_select_querybuilder_tokens(table_schema_data: &String) -> TokenStrea fn generate_count_operations_tokens(ty: &Ident, table_schema_data: &String) -> TokenStream { let count_stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); let count = __details::count_generators::create_count_macro(ty, &count_stmt); - let count_with = __details::count_generators::create_count_with_macro(ty, &count_stmt); + let count_with = __details::count_generators::create_count_with_macro(&count_stmt); quote! { #count @@ -175,59 +175,20 @@ mod __details { use super::*; use proc_macro2::TokenStream; - // NOTE: We can't use the QueryOneFor here due that the Tiberius `.get::(0)` for - // some reason returns an I32(Some(v)), instead of I64, so we need to manually mapped the wrapped - // type as i64. Also, we don't have in the count macro datasource info to match it by database, - // so isn't worth to refactor for the other two drivers and then do some dirty magic on mssql, - // since we don't distinguish them as the returned type isn't a CanyonRows wrapped one - fn generate_count_manual_result_handling(ty: &Ident) -> TokenStream { - let ty_str = ty.to_string(); - - quote! { - #[cfg(feature="postgres")] - canyon_sql::core::CanyonRows::Postgres(mut v) => Ok( - v.remove(0).get::<&str, i64>("count") - ), - #[cfg(feature="mssql")] - canyon_sql::core::CanyonRows::Tiberius(mut v) => - v.remove(0) - .get::(0) - .map(|c| c as i64) - .ok_or(format!("Failure in the COUNT query for MSSQL for: {}", #ty_str).into()) - .into(), - #[cfg(feature="mysql")] - canyon_sql::core::CanyonRows::MySQL(mut v) => v.remove(0) - .get::(0) - .ok_or(format!("Failure in the COUNT query for MYSQL for: {}", #ty_str).into()), - } - } - pub fn create_count_macro(ty: &syn::Ident, stmt: &str) -> TokenStream { - let result_handling = generate_count_manual_result_handling(ty); - quote! { async fn count() -> Result> { - let res = <#ty as canyon_sql::core::Transaction>::query_rows(#stmt, &[], "") - .await?; - match res { - #result_handling - } + Ok(<#ty as canyon_sql::core::Transaction>::query_one_for::<&str, &[&dyn canyon_sql::query::QueryParameter<'_>], i64>(#stmt, &[], "").await? as i64) } } } - pub fn create_count_with_macro(ty: &syn::Ident, stmt: &str) -> TokenStream { - let result_handling = generate_count_manual_result_handling(ty); - + pub fn create_count_with_macro(stmt: &str) -> TokenStream { quote! { async fn count_with<'a, I>(input: I) -> Result> where I: canyon_sql::connection::DbConnection + Send + 'a { - let res = input.query_rows(#stmt, &[]).await?; - - match res { - #result_handling - } + Ok(input.query_one_for::(#stmt, &[]).await? as i64) } } } @@ -343,7 +304,7 @@ mod macro_builder_read_ops_tests { #[test] fn test_create_count_with_macro() { let ty = syn::parse_str::("User").unwrap(); - let tokens = create_count_with_macro(&ty, COUNT_STMT); + let tokens = create_count_with_macro(COUNT_STMT); let generated = tokens.to_string(); assert!(generated.contains("async fn count_with")); From 8cc181ffb66d2cc7fa7d8212743371a7967b4bae Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 14 May 2025 17:28:31 +0200 Subject: [PATCH 120/155] fix: missing the generics on the quote interpolation for the CanyonMapper proc macro --- canyon_macros/src/canyon_mapper_macro.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index e2685196..8558e7dc 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -13,6 +13,7 @@ const BY_VALUE_CONVERSION_TARGETS: [&str; 1] = ["String"]; pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { let ty = &ast.ident; + let generics = &ast.generics; let mut impl_methods = TokenStream::new(); // Recovers the identifiers of the structs members @@ -58,7 +59,7 @@ pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { }); quote! { - impl canyon_sql::core::RowMapper for #ty { + impl #generics canyon_sql::core::RowMapper for #ty #generics { type Output = #ty; #impl_methods } From 934cbe678e74c50f5664de89c9cc1eb9409e8cf8 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 15 May 2025 16:49:51 +0200 Subject: [PATCH 121/155] fix: correctly applying the generics on the macros for all the implementors of CrudOperations --- canyon_entities/src/helpers.rs | 3 +- canyon_macros/src/canyon_mapper_macro.rs | 4 +- canyon_macros/src/lib.rs | 12 +- canyon_macros/src/query_operations/delete.rs | 3 +- .../src/query_operations/foreign_key.rs | 3 +- canyon_macros/src/query_operations/insert.rs | 62 +++++--- canyon_macros/src/query_operations/mod.rs | 17 ++- canyon_macros/src/query_operations/read.rs | 68 ++++++--- canyon_macros/src/query_operations/update.rs | 3 +- canyon_macros/src/utils/helpers.rs | 142 +++++++++++------- canyon_macros/src/utils/macro_tokens.rs | 4 +- tests/crud/hex_arch_example.rs | 63 ++++++++ tests/crud/mod.rs | 19 ++- tests/tests_models/league.rs | 12 +- tests/tests_models/mod.rs | 6 +- tests/tests_models/player.rs | 4 +- tests/tests_models/tournament.rs | 8 +- 17 files changed, 289 insertions(+), 144 deletions(-) create mode 100644 tests/crud/hex_arch_example.rs diff --git a/canyon_entities/src/helpers.rs b/canyon_entities/src/helpers.rs index 80012b5a..2f26fc9b 100644 --- a/canyon_entities/src/helpers.rs +++ b/canyon_entities/src/helpers.rs @@ -2,11 +2,10 @@ /// TODO: This is duplicated from the macro's crate. We should be able to join both crates in /// one later, but now, for developing purposes, we need to maintain here for a while this here pub fn default_database_table_name_from_entity_name(ty: &str) -> String { - let struct_name: String = ty.to_string(); let mut table_name: String = String::new(); let mut index = 0; - for char in struct_name.chars() { + for char in ty.chars() { if index < 1 { table_name.push(char.to_ascii_lowercase()); index += 1; diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index 8558e7dc..d42a8735 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -13,7 +13,7 @@ const BY_VALUE_CONVERSION_TARGETS: [&str; 1] = ["String"]; pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { let ty = &ast.ident; - let generics = &ast.generics; + let (impl_generics, ty_generics, where_clause) = &ast.generics.split_for_impl(); let mut impl_methods = TokenStream::new(); // Recovers the identifiers of the structs members @@ -59,7 +59,7 @@ pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { }); quote! { - impl #generics canyon_sql::core::RowMapper for #ty #generics { + impl #impl_generics canyon_sql::core::RowMapper for #ty #ty_generics #where_clause { type Output = #ty; #impl_methods } diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index d49c3284..b5ed5a64 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -137,14 +137,12 @@ pub fn crud_operations(input: proc_macro::TokenStream) -> proc_macro::TokenStrea }; let table_name_res = helpers::table_schema_parser(¯o_data); - let table_schema_data = if let Err(err) = table_name_res { - return err.into(); - } else { - table_name_res.ok().unwrap() - }; - // Build the trait implementation - impl_crud_operations_trait_for_struct(¯o_data, table_schema_data) + if let Ok(table_schema_data) = table_name_res { + impl_crud_operations_trait_for_struct(¯o_data, table_schema_data) + } else { + table_name_res.unwrap_err().into() + } } /// proc-macro for annotate struct fields that holds a foreign key relation. diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index cf0e1c1d..7f2550ca 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -8,6 +8,7 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let mut delete_ops_tokens = TokenStream::new(); let ty = macro_data.ty; + let (_, ty_generics, _) = macro_data.generics.split_for_impl(); let pk = macro_data.get_primary_key_annotation(); let delete_signature = quote! { @@ -35,7 +36,7 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri delete_ops_tokens.extend(quote! { #delete_signature { - <#ty as canyon_sql::core::Transaction>::execute(#delete_stmt, &[#pk_field_value], "").await?; + <#ty #ty_generics as canyon_sql::core::Transaction>::execute(#delete_stmt, &[#pk_field_value], "").await?; Ok(()) } diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index 1a827a90..4f5bfc1d 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -144,6 +144,7 @@ fn generate_find_by_reverse_foreign_key_tokens( .retrieve_mapping_target_type() .as_ref() .unwrap_or(ty); + let (_, ty_generics, _) = macro_data.generics.split_for_impl(); for (field_ident, fk_annot) in macro_data.get_fk_annotations().iter() { if let EntityFieldAnnotation::ForeignKey(table, column) = fk_annot { @@ -192,7 +193,7 @@ fn generate_find_by_reverse_foreign_key_tokens( #quoted_method_signature { let lookup_value = #lookup_value; - <#ty as canyon_sql::core::Transaction>::query::<&str, #mapper_ty>( + <#ty #ty_generics as canyon_sql::core::Transaction>::query::<&str, #mapper_ty>( #stmt, &[lookup_value], "" diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index fc7879c9..0c45d7fc 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -1,3 +1,4 @@ +use crate::query_operations::consts; use crate::utils::macro_tokens::MacroTokens; use proc_macro2::TokenStream; use quote::quote; @@ -7,6 +8,8 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let mut insert_ops_tokens = TokenStream::new(); let ty = macro_data.ty; + let is_mapper_ty_present = macro_data.retrieve_mapping_target_type().is_some(); + let (_, ty_generics, _) = macro_data.generics.split_for_impl(); // Retrieves the fields of the Struct as a collection of Strings, already parsed // the condition of remove the primary key if it's present and it's autoincremental @@ -21,13 +24,13 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let insert_values = fields.iter().map(|ident| { quote! { &self.#ident } }); - let insert_values_cloned = insert_values.clone(); let primary_key = macro_data.get_primary_key_annotation(); let remove_pk_value_from_fn_entry = if let Some(pk_index) = macro_data.get_pk_index() { quote! { values.remove(#pk_index) } } else { + // TODO: this can be avoid just avoiding the field of the pk if exists on the macro_data.get_struct_fields(); creating a new method, not modifying that one quote! {} }; @@ -41,16 +44,33 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri .into_iter() .find(|(i, _t)| Some(i.to_string()) == primary_key); - let insert_transaction = if let Some(pk_data) = pk_ident_type { + let insert_values = quote! { + let mut values: Vec<&dyn canyon_sql::query::QueryParameter<'_>> = vec![#(#insert_values),*]; + }; + + let insert_signature = quote! { + async fn insert<'a>(&'a mut self) + -> Result<(), Box> + }; + let insert_with_signature = quote! { + async fn insert_with<'a, I>(&mut self, input: I) + -> Result<(), Box> + where + I: canyon_sql::connection::DbConnection + Send + 'a + }; + let err_msg = consts::UNAVAILABLE_CRUD_OP_ON_INSTANCE; // if required :( + + let insert_body = if let Some(pk_data) = pk_ident_type { let pk_ident = pk_data.0; let pk_type = pk_data.1; quote! { + #insert_values #remove_pk_value_from_fn_entry; let stmt = format!("{} RETURNING {}", #stmt , #primary_key); - self.#pk_ident = <#ty as canyon_sql::core::Transaction>::query_one_for::< + self.#pk_ident = <#ty #ty_generics as canyon_sql::core::Transaction>::query_one_for::< String, Vec<&'_ dyn QueryParameter<'_>>, #pk_type @@ -64,7 +84,8 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri } } else { quote! { - <#ty as canyon_sql::core::Transaction>::query_rows( // TODO: this should be execute + #insert_values + <#ty #ty_generics as canyon_sql::core::Transaction>::query_rows( // TODO: this should be execute #stmt, values, input @@ -74,6 +95,19 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri } }; + let insert_transaction = if is_mapper_ty_present { + quote! { + Err( + std::io::Error::new( + std::io::ErrorKind::Unsupported, + #err_msg + ).into_inner().unwrap() + ) + } + } else { + quote! { #insert_body } + }; + insert_ops_tokens.extend(quote! { /// Inserts into a database entity the current data in `self`, generating a new /// entry (row), returning the `PRIMARY KEY` = `self.` with the specified @@ -113,11 +147,8 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri /// } /// ``` /// - async fn insert<'a>(&'a mut self) - -> Result<(), Box> - { + #insert_signature { let input = ""; - let mut values: Vec<&dyn canyon_sql::query::QueryParameter<'_>> = vec![#(#insert_values),*]; #insert_transaction } @@ -159,15 +190,7 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri /// } /// ``` /// - async fn insert_with<'a, I>(&mut self, input: I) - -> Result<(), Box> - where - I: canyon_sql::connection::DbConnection + Send + 'a - { - let mut values: Vec<&dyn canyon_sql::query::QueryParameter<'_>> = vec![#(#insert_values_cloned),*]; - #insert_transaction - } - + #insert_with_signature { #insert_transaction } }); // let multi_insert_tokens = generate_multiple_insert_tokens(macro_data, table_schema_data); @@ -187,6 +210,7 @@ fn _generate_multiple_insert_tokens( table_schema_data: &String, ) -> TokenStream { let ty = macro_data.ty; + let (_, ty_generics, _) = macro_data.generics.split_for_impl(); // Retrieves the fields of the Struct as continuous String let column_names = macro_data._get_struct_fields_as_strings(); @@ -279,7 +303,7 @@ fn _generate_multiple_insert_tokens( } } - let multi_insert_result = <#ty as canyon_sql::core::Transaction>::query_rows( + let multi_insert_result = <#ty #ty_generics as canyon_sql::core::Transaction>::query_rows( stmt, v_arr, input @@ -378,7 +402,7 @@ fn _generate_multiple_insert_tokens( } } - <#ty as canyon_sql::core::Transaction>::query_rows( + <#ty #ty_generics as canyon_sql::core::Transaction>::query_rows( stmt, v_arr, input diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index 8a105f79..635a0a02 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -3,6 +3,7 @@ use crate::query_operations::foreign_key::generate_find_by_fk_ops; use crate::query_operations::insert::generate_insert_tokens; use crate::query_operations::read::generate_read_operations_tokens; use crate::query_operations::update::generate_update_tokens; +use crate::utils::helpers::compute_crud_ops_mapping_target_type_with_generics; use crate::utils::macro_tokens::MacroTokens; use proc_macro2::TokenStream; use quote::quote; @@ -23,11 +24,12 @@ pub fn impl_crud_operations_trait_for_struct( let mut crud_ops_tokens = TokenStream::new(); let ty = macro_data.ty; - let mapper_ty = macro_data - .retrieve_mapping_target_type() - .as_ref() - .unwrap_or(ty); - let generics = macro_data.generics; + let (impl_generics, ty_generics, where_clause) = macro_data.generics.split_for_impl(); + let mapper_ty = compute_crud_ops_mapping_target_type_with_generics( + ty, + &ty_generics, + macro_data.retrieve_mapping_target_type().as_ref(), + ); let read_operations_tokens = generate_read_operations_tokens(macro_data, &table_schema_data); let insert_tokens = generate_insert_tokens(macro_data, &table_schema_data); @@ -44,12 +46,13 @@ pub fn impl_crud_operations_trait_for_struct( crud_ops_tokens.extend(quote! { use canyon_sql::core::IntoResults; use canyon_sql::core::RowMapper; + use canyon_sql::query::QueryParameter; - impl #generics canyon_sql::crud::CrudOperations<#mapper_ty> for #ty #generics { + impl #impl_generics canyon_sql::crud::CrudOperations<#mapper_ty> for #ty #ty_generics #where_clause { #crud_operations_tokens } - impl #generics canyon_sql::core::Transaction for #ty #generics {} + impl #impl_generics canyon_sql::core::Transaction for #ty #ty_generics #where_clause {} }); // NOTE: this extends should be documented WHY is needed to be after the base impl of CrudOperations diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 9d7c11f0..4781b7e3 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -2,6 +2,7 @@ use crate::query_operations::consts; use crate::utils::macro_tokens::MacroTokens; use proc_macro2::{Ident, TokenStream}; use quote::quote; +use syn::TypeGenerics; /// Facade function that acts as the unique API for export to the real macro implementation /// of all the generated macros for the READ operations @@ -10,14 +11,17 @@ pub fn generate_read_operations_tokens( table_schema_data: &String, ) -> TokenStream { let ty = macro_data.ty; + let (_, ty_generics, _) = macro_data.generics.split_for_impl(); let mapper_ty = macro_data .retrieve_mapping_target_type() .as_ref() .unwrap_or(ty); - let find_all_tokens = generate_find_all_operations_tokens(ty, mapper_ty, table_schema_data); - let count_tokens = generate_count_operations_tokens(ty, table_schema_data); - let find_by_pk_tokens = generate_find_by_pk_operations_tokens(macro_data, table_schema_data); + let find_all_tokens = + generate_find_all_operations_tokens(ty, Some(&ty_generics), mapper_ty, table_schema_data); + let count_tokens = generate_count_operations_tokens(ty, Some(&ty_generics), table_schema_data); + let find_by_pk_tokens = + generate_find_by_pk_operations_tokens(macro_data, Some(&ty_generics), table_schema_data); let read_querybuilder_ops = generate_select_querybuilder_tokens(table_schema_data); quote! { @@ -30,6 +34,7 @@ pub fn generate_read_operations_tokens( fn generate_find_all_operations_tokens( ty: &Ident, + ty_generics: Option<&TypeGenerics>, mapper_ty: &Ident, table_schema_data: &String, ) -> TokenStream { @@ -38,7 +43,8 @@ fn generate_find_all_operations_tokens( // TODO: bring the helper and convert the SELECT * into the SELECT col_name, col_name2...? // TODO: remember that this queries statements must be autogenerated by some automatic procedure - let find_all = __details::find_all_generators::create_find_all_macro(ty, mapper_ty, &fa_stmt); + let find_all = + __details::find_all_generators::create_find_all_macro(ty, ty_generics, mapper_ty, &fa_stmt); let find_all_with = __details::find_all_generators::create_find_all_with_macro(mapper_ty, &fa_stmt); @@ -83,9 +89,13 @@ fn generate_select_querybuilder_tokens(table_schema_data: &String) -> TokenStrea } } -fn generate_count_operations_tokens(ty: &Ident, table_schema_data: &String) -> TokenStream { +fn generate_count_operations_tokens( + ty: &Ident, + ty_generics: Option<&TypeGenerics>, + table_schema_data: &String, +) -> TokenStream { let count_stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); - let count = __details::count_generators::create_count_macro(ty, &count_stmt); + let count = __details::count_generators::create_count_macro(ty, ty_generics, &count_stmt); let count_with = __details::count_generators::create_count_with_macro(&count_stmt); quote! { @@ -96,6 +106,7 @@ fn generate_count_operations_tokens(ty: &Ident, table_schema_data: &String) -> T fn generate_find_by_pk_operations_tokens( macro_data: &MacroTokens<'_>, + ty_generics: Option<&TypeGenerics>, table_schema_data: &String, ) -> TokenStream { let ty = macro_data.ty; @@ -124,12 +135,16 @@ fn generate_find_by_pk_operations_tokens( let find_by_pk = __details::find_by_pk_generators::create_find_by_pk_macro( ty, - &mapper_ty, + ty_generics, + mapper_ty, + &stmt, + &no_pk_runtime_err, + ); + let find_by_pk_with = __details::find_by_pk_generators::create_find_by_pk_with( + mapper_ty, &stmt, &no_pk_runtime_err, ); - let find_by_pk_with = - __details::find_by_pk_generators::create_find_by_pk_with(ty, &stmt, &no_pk_runtime_err); quote! { #find_by_pk @@ -144,12 +159,18 @@ mod __details { pub mod find_all_generators { use super::*; use proc_macro2::TokenStream; + use syn::TypeGenerics; - pub fn create_find_all_macro(ty: &Ident, mapper_ty: &Ident, stmt: &str) -> TokenStream { + pub fn create_find_all_macro( + ty: &Ident, + ty_generics: Option<&TypeGenerics>, + mapper_ty: &Ident, + stmt: &str, + ) -> TokenStream { quote! { async fn find_all() -> Result, Box<(dyn std::error::Error + Send + Sync)>> { - <#ty as canyon_sql::core::Transaction>::query::<&str, #mapper_ty>( + <#ty #ty_generics as canyon_sql::core::Transaction>::query::<&str, #mapper_ty>( #stmt, &[], "" @@ -174,11 +195,16 @@ mod __details { pub mod count_generators { use super::*; use proc_macro2::TokenStream; + use syn::TypeGenerics; - pub fn create_count_macro(ty: &syn::Ident, stmt: &str) -> TokenStream { + pub fn create_count_macro( + ty: &syn::Ident, + ty_generics: Option<&TypeGenerics>, + stmt: &str, + ) -> TokenStream { quote! { async fn count() -> Result> { - Ok(<#ty as canyon_sql::core::Transaction>::query_one_for::<&str, &[&dyn canyon_sql::query::QueryParameter<'_>], i64>(#stmt, &[], "").await? as i64) + Ok(<#ty #ty_generics as canyon_sql::core::Transaction>::query_one_for::<&str, &[&dyn canyon_sql::query::QueryParameter<'_>], i64>(#stmt, &[], "").await? as i64) } } } @@ -197,16 +223,18 @@ mod __details { pub mod find_by_pk_generators { use super::*; use proc_macro2::TokenStream; + use syn::TypeGenerics; pub fn create_find_by_pk_macro( ty: &Ident, - mapper_ty: &syn::Ident, + ty_generics: Option<&TypeGenerics>, + mapper_ty: &Ident, stmt: &str, pk_runtime_error: &Option, ) -> TokenStream { let body = if pk_runtime_error.is_none() { quote! { - <#ty as canyon_sql::core::Transaction>::query_one::< + <#ty #ty_generics as canyon_sql::core::Transaction>::query_one::< &str, &[&'a (dyn QueryParameter<'a>)], #mapper_ty @@ -226,7 +254,7 @@ mod __details { } pub fn create_find_by_pk_with( - mapper_ty: &syn::Ident, + mapper_ty: &Ident, stmt: &str, pk_runtime_error: &Option, ) -> TokenStream { @@ -269,7 +297,7 @@ mod macro_builder_read_ops_tests { fn test_create_find_all_macro() { let ty = syn::parse_str::("User").unwrap(); let mapper_ty = syn::parse_str::("User").unwrap(); - let tokens = create_find_all_macro(&ty, &mapper_ty, SELECT_ALL_STMT); + let tokens = create_find_all_macro(&ty, None, &mapper_ty, SELECT_ALL_STMT); let generated = tokens.to_string(); assert!(generated.contains("async fn find_all")); @@ -293,7 +321,7 @@ mod macro_builder_read_ops_tests { #[test] fn test_create_count_macro() { let ty = syn::parse_str::("User").unwrap(); - let tokens = create_count_macro(&ty, COUNT_STMT); + let tokens = create_count_macro(&ty, None, COUNT_STMT); let generated = tokens.to_string(); assert!(generated.contains("async fn count")); @@ -303,7 +331,6 @@ mod macro_builder_read_ops_tests { #[test] fn test_create_count_with_macro() { - let ty = syn::parse_str::("User").unwrap(); let tokens = create_count_with_macro(COUNT_STMT); let generated = tokens.to_string(); @@ -319,7 +346,8 @@ mod macro_builder_read_ops_tests { let ty = syn::parse_str::("User").unwrap(); let mapper_ty = syn::parse_str::("User").unwrap(); let pk_runtime_error = None; - let tokens = create_find_by_pk_macro(&ty, &mapper_ty, FIND_BY_PK_STMT, &pk_runtime_error); + let tokens = + create_find_by_pk_macro(&ty, None, &mapper_ty, FIND_BY_PK_STMT, &pk_runtime_error); let generated = tokens.to_string(); assert!(generated.contains("async fn find_by_pk")); diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index ac0bd905..f87c84c7 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -8,6 +8,7 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let mut update_ops_tokens = TokenStream::new(); let ty = macro_data.ty; + let (_, ty_generics, _) = macro_data.generics.split_for_impl(); let update_columns = macro_data.get_column_names_pk_parsed(); let fields = macro_data.get_struct_fields(); @@ -47,7 +48,7 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri update_ops_tokens.extend(quote! { #update_signature { let update_values: &[&dyn canyon_sql::query::QueryParameter<'_>] = #update_values; - <#ty as canyon_sql::core::Transaction>::execute(#stmt, update_values, "").await + <#ty #ty_generics as canyon_sql::core::Transaction>::execute(#stmt, update_values, "").await } #update_with_signature { let update_values: &[&dyn canyon_sql::query::QueryParameter<'_>] = #update_values; diff --git a/canyon_macros/src/utils/helpers.rs b/canyon_macros/src/utils/helpers.rs index 88f155f5..98a9e769 100644 --- a/canyon_macros/src/utils/helpers.rs +++ b/canyon_macros/src/utils/helpers.rs @@ -1,8 +1,25 @@ use proc_macro2::{Ident, Span, TokenStream}; -use syn::{punctuated::Punctuated, Fields, MetaNameValue, Token, Type, Visibility}; +use quote::quote; +use syn::{ + punctuated::Punctuated, Attribute, Fields, MetaNameValue, Token, Type, TypeGenerics, Visibility, +}; use super::macro_tokens::MacroTokens; +/// Given the derived type of CrudOperations, and the possible mapping type if the `#[canyon_crud(maps_to=]` exists, +/// returns a [`TokenStream`] with the final `RowMapper` implementor. +pub fn compute_crud_ops_mapping_target_type_with_generics( + row_mapper_ty: &Ident, + row_mapper_ty_generics: &TypeGenerics, + crud_ops_ty: Option<&Ident>, +) -> TokenStream { + if let Some(crud_ops_ty) = crud_ops_ty { + quote! { #crud_ops_ty } + } else { + quote! { #row_mapper_ty #row_mapper_ty_generics } + } +} + pub fn filter_fields(fields: &Fields) -> Vec<(Visibility, Ident)> { fields .iter() @@ -32,78 +49,91 @@ pub fn table_schema_parser(macro_data: &MacroTokens<'_>) -> Result = None; for attr in macro_data.attrs { - if attr - .path - .segments - .iter() - .any(|seg| seg.ident == "canyon_macros" || seg.ident == "canyon_entity") - { - let name_values_result: Result, syn::Error> = - attr.parse_args_with(Punctuated::parse_terminated); - - if let Ok(meta_name_values) = name_values_result { - for nv in meta_name_values { - let ident = nv.path.get_ident(); - if let Some(i) = ident { - let identifier = i; - match &nv.lit { - syn::Lit::Str(s) => { - if identifier == "table_name" { - table_name = Some(s.value()) - } else if identifier == "schema" { - schema = Some(s.value()) - } else { - return Err( - syn::Error::new_spanned( - Ident::new(&identifier.to_string(), i.span()), - "Only string literals are valid values for the attribute arguments" - ).into_compile_error() - ); - } + let mut segments = attr.path.segments.iter(); + if segments.any(|seg| seg.ident == "canyon_macros" || seg.ident == "canyon_entity") { + parse_canyon_entity_attr(attr, &mut schema, &mut table_name)?; + } + // TODO: if segments because we could parse here the canyon_crud proc_macro_attr + // TODO: create a custom struct for hold this pair of data + } + + let mut final_table_name = String::new(); + if schema.is_some() { + final_table_name.push_str(format!("{}.", schema.unwrap()).as_str()) + } + + if let Some(t_name) = table_name { + final_table_name.push_str(t_name.as_str()) + } else { + let defaulted = &default_database_table_name_from_entity_name(¯o_data.ty.to_string()); + final_table_name.push_str(defaulted) + } + + Ok(final_table_name) +} + +fn parse_canyon_entity_attr( + attr: &Attribute, + schema: &mut Option, + table_name: &mut Option, +) -> Result<(), TokenStream> { + if attr + .path + .segments + .iter() + .any(|seg| seg.ident == "canyon_macros" || seg.ident == "canyon_entity") + { + let name_values_result: Result, syn::Error> = + attr.parse_args_with(Punctuated::parse_terminated); + + if let Ok(meta_name_values) = name_values_result { + for nv in meta_name_values { + let ident = nv.path.get_ident(); + if let Some(i) = ident { + let identifier = i; + match &nv.lit { + syn::Lit::Str(s) => { + if identifier == "table_name" { + *table_name = Some(s.value()); + } else if identifier == "schema" { + *schema = Some(s.value()); + } else { + return Err( + syn::Error::new_spanned( + Ident::new(&identifier.to_string(), i.span()), + "Only string literals are valid values for the attribute arguments" + ).into_compile_error() + ); } - _ => return Err(syn::Error::new_spanned( + } + _ => { + return Err(syn::Error::new_spanned( Ident::new(&identifier.to_string(), i.span()), "Only string literals are valid values for the attribute arguments", ) - .into_compile_error()), + .into_compile_error()) } - } else { - return Err(syn::Error::new( - Span::call_site(), - "Only string literals are valid values for the attribute arguments", - ) - .into_compile_error()); } + } else { + return Err(syn::Error::new( + Span::call_site(), + "Only string literals are valid values for the attribute arguments", + ) + .into_compile_error()); } } - - let mut final_table_name = String::new(); - if schema.is_some() { - final_table_name.push_str(format!("{}.", schema.unwrap()).as_str()) - } - - if let Some(t_name) = table_name { - final_table_name.push_str(t_name.as_str()) - } else { - let defaulted = - &default_database_table_name_from_entity_name(¯o_data.ty.to_string()); - final_table_name.push_str(defaulted) - } - - return Ok(final_table_name); } } - Ok(macro_data.ty.to_string()) + Ok(()) } /// Autogenerates a default table name for an entity given their struct name pub fn default_database_table_name_from_entity_name(ty: &str) -> String { - let struct_name: String = ty.to_string(); let mut table_name: String = String::new(); let mut index = 0; - for char in struct_name.chars() { + for char in ty.chars() { if index < 1 { table_name.push(char.to_ascii_lowercase()); index += 1; @@ -149,7 +179,7 @@ pub fn database_table_name_to_struct_ident(name: &str) -> Ident { } } - Ident::new(&struct_name, proc_macro2::Span::call_site()) + Ident::new(&struct_name, Span::call_site()) } /// Parses a syn::Identifier to create a defaulted snake case database table name diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index 61e9b79e..30fbae50 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -41,13 +41,11 @@ impl<'a> MacroTokens<'a> { } else { Err(syn::Error::new( Span::call_site(), - "unsupported 'canyon_crud' attribute, expected `maps_to`", + "CanyonCrud may only be implemented for structs", )) } } - // TODO: this must be refactored in order to avoid to make the operation everytime that - // this method is queried. The trick w'd be to have a map to relate the entries. pub fn retrieve_mapping_target_type(&self) -> &Option { if let Some(canyon_crud_attribute) = &self.canyon_crud_attribute { &canyon_crud_attribute.maps_to diff --git a/tests/crud/hex_arch_example.rs b/tests/crud/hex_arch_example.rs new file mode 100644 index 00000000..338ea642 --- /dev/null +++ b/tests/crud/hex_arch_example.rs @@ -0,0 +1,63 @@ +use canyon_sql::connection::DbConnection; +use canyon_sql::core::Canyon; +use canyon_sql::macros::{canyon_entity, CanyonCrud, CanyonMapper}; +use canyon_sql::query::querybuilder::SelectQueryBuilder; +use std::error::Error; + +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_hex_arch_find_all() { + let binding = Canyon::instance() + .unwrap() + .get_default_connection() + .await + .unwrap(); + let league_service = LeagueServiceAdapter { + league_repository: LeagueRepositoryAdapter { + db_conn: binding.postgres_connection(), + }, + }; + let find_all_result = league_service.find_all().await; + + // Connection doesn't return an error + assert!(find_all_result.is_ok()); + assert!(!find_all_result.unwrap().is_empty()); +} + +#[derive(CanyonMapper)] +#[canyon_entity] +pub struct League { + // The core model of the 'League' domain + #[primary_key] + pub id: i32, +} + +pub trait LeagueService { + async fn find_all(&self) -> Result, Box>; +} // As a domain boundary for the application side of the hexagon + +pub struct LeagueServiceAdapter { + league_repository: T, +} +impl LeagueService for LeagueServiceAdapter { + async fn find_all(&self) -> Result, Box> { + self.league_repository.find_all().await + } +} + +pub trait LeagueRepository { + async fn find_all(&self) -> Result, Box>; +} // As a domain boundary for the infrastructure side of the hexagon + +#[derive(CanyonCrud)] +#[canyon_crud(maps_to=League)] +pub struct LeagueRepositoryAdapter<'b, T: DbConnection + Send + Sync> { + db_conn: &'b T, +} +impl LeagueRepository for LeagueRepositoryAdapter<'_, T> { + async fn find_all(&self) -> Result, Box> { + let select_query = + SelectQueryBuilder::new("league", self.db_conn.get_database_type()?)?.build()?; + self.db_conn.query(select_query, &[]).await + } +} diff --git a/tests/crud/mod.rs b/tests/crud/mod.rs index 69ad58c3..e22cabc6 100644 --- a/tests/crud/mod.rs +++ b/tests/crud/mod.rs @@ -1,10 +1,9 @@ -#![allow(unused_imports)] - -pub mod delete_operations; -pub mod foreign_key_operations; -#[cfg(feature = "mssql")] -pub mod init_mssql; -pub mod insert_operations; -pub mod querybuilder_operations; -pub mod read_operations; -pub mod update_operations; +// pub mod delete_operations; +// pub mod foreign_key_operations; +pub mod hex_arch_example; +// #[cfg(feature = "mssql")] +// pub mod init_mssql; +// pub mod insert_operations; +// pub mod querybuilder_operations; +// pub mod read_operations; +// pub mod update_operations; diff --git a/tests/tests_models/league.rs b/tests/tests_models/league.rs index b6476bb5..7f2f57ab 100644 --- a/tests/tests_models/league.rs +++ b/tests/tests_models/league.rs @@ -1,12 +1,12 @@ use canyon_sql::macros::*; -#[derive(Debug, Fields, CanyonCrud, CanyonMapper, ForeignKeyable, Eq, PartialEq)] -#[canyon_crud(maps_to = League)] -// canyon_crud mapping to Self is already the default behaviour -// just here for demonstration purposes -#[canyon_entity(table_name = "league", /* schema = "public"*/)] +// #[derive(Debug, Fields, CanyonCrud, CanyonMapper, ForeignKeyable, Eq, PartialEq)] +// #[canyon_crud(maps_to = League)] +// // canyon_crud mapping to Self is already the default behaviour +// // just here for demonstration purposes +// #[canyon_entity(table_name = "league", /* schema = "public"*/)] pub struct League { - #[primary_key] + // #[primary_key] id: i32, ext_id: i64, slug: String, diff --git a/tests/tests_models/mod.rs b/tests/tests_models/mod.rs index bba7142b..3dddfb86 100644 --- a/tests/tests_models/mod.rs +++ b/tests/tests_models/mod.rs @@ -1,3 +1,3 @@ -pub mod league; -pub mod player; -pub mod tournament; +// pub mod league; +// pub mod player; +// pub mod tournament; diff --git a/tests/tests_models/player.rs b/tests/tests_models/player.rs index 0cba50ec..27b8ed81 100644 --- a/tests/tests_models/player.rs +++ b/tests/tests_models/player.rs @@ -1,7 +1,7 @@ use canyon_sql::macros::*; -#[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] -#[canyon_entity] +// #[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] +// #[canyon_entity] /// Data model that represents a database entity for Players. /// /// For test the behaviour of Canyon with entities that no declares primary keys, diff --git a/tests/tests_models/tournament.rs b/tests/tests_models/tournament.rs index 880076f4..08eca7f2 100644 --- a/tests/tests_models/tournament.rs +++ b/tests/tests_models/tournament.rs @@ -1,15 +1,15 @@ use crate::tests_models::league::League; use canyon_sql::{date_time::NaiveDate, macros::*}; -#[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] -#[canyon_entity] +// #[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] +// #[canyon_entity] pub struct Tournament { - #[primary_key] + // #[primary_key] id: i32, ext_id: i64, slug: String, start_date: NaiveDate, end_date: NaiveDate, - #[foreign_key(table = "league", column = "id")] + // #[foreign_key(table = "league", column = "id")] league: i32, } From a06e6a4cec0a86da80a17ab7b8dc6072bb7f3e6a Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 15 May 2025 17:59:53 +0200 Subject: [PATCH 122/155] perf: avoiding vec allocations on the insert query on CrudOperations to pass the query parameters --- canyon_core/src/query/parameters.rs | 2 +- canyon_entities/src/manager_builder.rs | 2 +- canyon_macros/src/lib.rs | 1 - canyon_macros/src/query_operations/insert.rs | 54 ++++++++------------ canyon_macros/src/query_operations/mod.rs | 1 - canyon_macros/src/query_operations/read.rs | 2 +- canyon_macros/src/utils/macro_tokens.rs | 23 +++++++-- tests/crud/mod.rs | 16 +++--- tests/tests_models/league.rs | 9 ++-- tests/tests_models/mod.rs | 6 +-- tests/tests_models/player.rs | 4 +- tests/tests_models/tournament.rs | 8 +-- 12 files changed, 63 insertions(+), 65 deletions(-) diff --git a/canyon_core/src/query/parameters.rs b/canyon_core/src/query/parameters.rs index 657af4ca..16b479da 100644 --- a/canyon_core/src/query/parameters.rs +++ b/canyon_core/src/query/parameters.rs @@ -228,7 +228,7 @@ impl QueryParameter<'_> for Option<&f32> { )) } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { self } } diff --git a/canyon_entities/src/manager_builder.rs b/canyon_entities/src/manager_builder.rs index 188dcb06..8460b757 100644 --- a/canyon_entities/src/manager_builder.rs +++ b/canyon_entities/src/manager_builder.rs @@ -204,7 +204,7 @@ pub fn generate_enum_with_fields_values(canyon_entity: &CanyonEntity) -> TokenSt } impl<'a> canyon_sql::query::bounds::FieldValueIdentifier<'a> for #enum_name<'a> { - fn value(self) -> (&'static str, &'a dyn QueryParameter<'a>) { + fn value(self) -> (&'static str, &'a dyn canyon_sql::query::QueryParameter<'a>) { match self { #(#match_arms),* } diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index b5ed5a64..d1aa6dd6 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -184,7 +184,6 @@ pub fn querybuilder_fields(input: CompilerTokenStream) -> CompilerTokenStream { let generated_enum_type_for_fields_values = generate_enum_with_fields_values(&entity); quote! { - use canyon_sql::query::QueryParameter; use canyon_sql::query::bounds::TableMetadata; use canyon_sql::query::bounds::FieldIdentifier; diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 0c45d7fc..bc05f514 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -19,33 +19,21 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let placeholders = macro_data.placeholders_generator(); // Retrieves the fields of the Struct - let fields = macro_data.get_struct_fields(); + let fields = macro_data.get_columns_pk_parsed(); - let insert_values = fields.iter().map(|ident| { - quote! { &self.#ident } + let insert_values = fields.iter().map(|field| { + let field = field.ident.as_ref().unwrap(); + quote! { &self.#field } }); let primary_key = macro_data.get_primary_key_annotation(); - - let remove_pk_value_from_fn_entry = if let Some(pk_index) = macro_data.get_pk_index() { - quote! { values.remove(#pk_index) } - } else { - // TODO: this can be avoid just avoiding the field of the pk if exists on the macro_data.get_struct_fields(); creating a new method, not modifying that one - quote! {} - }; - - let stmt = format!( - "INSERT INTO {} ({}) VALUES ({})", - table_schema_data, insert_columns, placeholders - ); - let pk_ident_type = macro_data .fields_with_types() .into_iter() .find(|(i, _t)| Some(i.to_string()) == primary_key); - let insert_values = quote! { - let mut values: Vec<&dyn canyon_sql::query::QueryParameter<'_>> = vec![#(#insert_values),*]; + let ins_values = quote! { + let values: &[&dyn canyon_sql::query::QueryParameter<'_>] = &[#(#insert_values),*]; }; let insert_signature = quote! { @@ -58,21 +46,24 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri where I: canyon_sql::connection::DbConnection + Send + 'a }; - let err_msg = consts::UNAVAILABLE_CRUD_OP_ON_INSTANCE; // if required :( + + let stmt = format!( + "INSERT INTO {} ({}) VALUES ({})", + table_schema_data, insert_columns, placeholders + ); let insert_body = if let Some(pk_data) = pk_ident_type { let pk_ident = pk_data.0; let pk_type = pk_data.1; quote! { - #insert_values - #remove_pk_value_from_fn_entry; + #ins_values let stmt = format!("{} RETURNING {}", #stmt , #primary_key); self.#pk_ident = <#ty #ty_generics as canyon_sql::core::Transaction>::query_one_for::< String, - Vec<&'_ dyn QueryParameter<'_>>, + &[&dyn canyon_sql::query::QueryParameter<'_>], #pk_type >( stmt, @@ -84,7 +75,7 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri } } else { quote! { - #insert_values + #ins_values <#ty #ty_generics as canyon_sql::core::Transaction>::query_rows( // TODO: this should be execute #stmt, values, @@ -100,7 +91,7 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri Err( std::io::Error::new( std::io::ErrorKind::Unsupported, - #err_msg + "Can't use the 'Insert' family transactions if your T type in CrudOperations is the same type that implements RowMapper" ).into_inner().unwrap() ) } @@ -449,14 +440,13 @@ fn _generate_multiple_insert_tokens( async fn multi_insert<'a, T>(instances: &'a mut [&'a mut T]) -> ( Result<(), Box> ) { - use canyon_sql::query::QueryParameter; let input = ""; - let mut final_values: Vec>> = Vec::new(); + let mut final_values: Vec>> = Vec::new(); for instance in instances.iter() { - let intermediate: &[&dyn QueryParameter<'_>] = &[#(#macro_fields),*]; + let intermediate: &[&dyn canyon_sql::query::QueryParameter<'_>] = &[#(#macro_fields),*]; - let mut longer_lived: Vec<&dyn QueryParameter<'_>> = Vec::new(); + let mut longer_lived: Vec<&dyn canyon_sql::query::QueryParameter<'_>> = Vec::new(); for value in intermediate.into_iter() { longer_lived.push(*value) } @@ -508,13 +498,11 @@ fn _generate_multiple_insert_tokens( where I: canyon_sql::connection::DbConnection + Send + 'a { - use canyon_sql::query::QueryParameter; - - let mut final_values: Vec>> = Vec::new(); + let mut final_values: Vec>> = Vec::new(); for instance in instances.iter() { - let intermediate: &[&dyn QueryParameter<'_>] = &[#(#macro_fields_cloned),*]; + let intermediate: &[&dyn canyon_sql::query::QueryParameter<'_>] = &[#(#macro_fields_cloned),*]; - let mut longer_lived: Vec<&dyn QueryParameter<'_>> = Vec::new(); + let mut longer_lived: Vec<&dyn canyon_sql::query::QueryParameter<'_>> = Vec::new(); for value in intermediate.into_iter() { longer_lived.push(*value) } diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index 635a0a02..300d3615 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -46,7 +46,6 @@ pub fn impl_crud_operations_trait_for_struct( crud_ops_tokens.extend(quote! { use canyon_sql::core::IntoResults; use canyon_sql::core::RowMapper; - use canyon_sql::query::QueryParameter; impl #impl_generics canyon_sql::crud::CrudOperations<#mapper_ty> for #ty #ty_generics #where_clause { #crud_operations_tokens diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 4781b7e3..ad913842 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -236,7 +236,7 @@ mod __details { quote! { <#ty #ty_generics as canyon_sql::core::Transaction>::query_one::< &str, - &[&'a (dyn QueryParameter<'a>)], + &[&'a (dyn canyon_sql::query::QueryParameter<'a>)], #mapper_ty >(#stmt, &[value], "").await } diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index 30fbae50..424ecb36 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -4,7 +4,7 @@ use std::fmt::Write; use crate::utils::canyon_crud_attribute::CanyonCrudAttribute; use canyon_entities::field_annotation::EntityFieldAnnotation; use proc_macro2::{Ident, Span}; -use syn::{Attribute, DeriveInput, Fields, Generics, Type, Visibility}; +use syn::{Attribute, DeriveInput, Field, Fields, Generics, Type, Visibility}; /// Provides a convenient way of store the data for the TokenStream /// received on a macro @@ -71,8 +71,7 @@ impl<'a> MacroTokens<'a> { .collect::>() } - /// Returns a Vec populated with the name of the fields of the struct - /// already quote scaped for avoid the upper case column name mangling. + /// Returns a Vec populated with the fields of the struct /// /// If the type contains a `#[primary_key]` annotation (and), returns the /// name of the columns without the fields that maps against the column designed as @@ -81,7 +80,7 @@ impl<'a> MacroTokens<'a> { /// to the same behaviour. /// /// Returns every field if there's no PK, or if it's present but autoincremental = false - pub fn get_column_names_pk_parsed(&self) -> Vec { + pub fn get_columns_pk_parsed(&self) -> Vec<&Field> { self.fields .iter() .filter(|field| { @@ -95,6 +94,22 @@ impl<'a> MacroTokens<'a> { true } }) + .collect::>() + } + + /// Returns a Vec populated with the name of the fields of the struct + /// already quote scaped for avoid the upper case column name mangling. + /// + /// If the type contains a `#[primary_key]` annotation (and), returns the + /// name of the columns without the fields that maps against the column designed as + /// primary key (if its present and its autoincremental attribute is set to true) + /// (autoincremental = true) or its without the autoincremental attribute, which leads + /// to the same behaviour. + /// + /// Returns every field if there's no PK, or if it's present but autoincremental = false + pub fn get_column_names_pk_parsed(&self) -> Vec { + self.get_columns_pk_parsed() + .iter() .map(|c| format!("\"{}\"", c.ident.as_ref().unwrap())) .collect::>() } diff --git a/tests/crud/mod.rs b/tests/crud/mod.rs index e22cabc6..f333a6de 100644 --- a/tests/crud/mod.rs +++ b/tests/crud/mod.rs @@ -1,9 +1,9 @@ -// pub mod delete_operations; -// pub mod foreign_key_operations; +pub mod delete_operations; +pub mod foreign_key_operations; pub mod hex_arch_example; -// #[cfg(feature = "mssql")] -// pub mod init_mssql; -// pub mod insert_operations; -// pub mod querybuilder_operations; -// pub mod read_operations; -// pub mod update_operations; +#[cfg(feature = "mssql")] +pub mod init_mssql; +pub mod insert_operations; +pub mod querybuilder_operations; +pub mod read_operations; +pub mod update_operations; diff --git a/tests/tests_models/league.rs b/tests/tests_models/league.rs index 7f2f57ab..b1503117 100644 --- a/tests/tests_models/league.rs +++ b/tests/tests_models/league.rs @@ -1,12 +1,9 @@ use canyon_sql::macros::*; -// #[derive(Debug, Fields, CanyonCrud, CanyonMapper, ForeignKeyable, Eq, PartialEq)] -// #[canyon_crud(maps_to = League)] -// // canyon_crud mapping to Self is already the default behaviour -// // just here for demonstration purposes -// #[canyon_entity(table_name = "league", /* schema = "public"*/)] +#[derive(Debug, Fields, CanyonCrud, CanyonMapper, ForeignKeyable, Eq, PartialEq)] +#[canyon_entity(table_name = "league", /* schema = "public"*/)] pub struct League { - // #[primary_key] + #[primary_key] id: i32, ext_id: i64, slug: String, diff --git a/tests/tests_models/mod.rs b/tests/tests_models/mod.rs index 3dddfb86..bba7142b 100644 --- a/tests/tests_models/mod.rs +++ b/tests/tests_models/mod.rs @@ -1,3 +1,3 @@ -// pub mod league; -// pub mod player; -// pub mod tournament; +pub mod league; +pub mod player; +pub mod tournament; diff --git a/tests/tests_models/player.rs b/tests/tests_models/player.rs index 27b8ed81..0cba50ec 100644 --- a/tests/tests_models/player.rs +++ b/tests/tests_models/player.rs @@ -1,7 +1,7 @@ use canyon_sql::macros::*; -// #[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] -// #[canyon_entity] +#[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] +#[canyon_entity] /// Data model that represents a database entity for Players. /// /// For test the behaviour of Canyon with entities that no declares primary keys, diff --git a/tests/tests_models/tournament.rs b/tests/tests_models/tournament.rs index 08eca7f2..880076f4 100644 --- a/tests/tests_models/tournament.rs +++ b/tests/tests_models/tournament.rs @@ -1,15 +1,15 @@ use crate::tests_models::league::League; use canyon_sql::{date_time::NaiveDate, macros::*}; -// #[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] -// #[canyon_entity] +#[derive(Debug, Clone, Fields, CanyonCrud, CanyonMapper, Eq, PartialEq)] +#[canyon_entity] pub struct Tournament { - // #[primary_key] + #[primary_key] id: i32, ext_id: i64, slug: String, start_date: NaiveDate, end_date: NaiveDate, - // #[foreign_key(table = "league", column = "id")] + #[foreign_key(table = "league", column = "id")] league: i32, } From ec69135b7d20eb38d6b5cedae177c065649a8ded Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 16 May 2025 13:27:05 +0200 Subject: [PATCH 123/155] feat: row_mapper methods now returns the value wrapped on Result, leading to a much better hygiene on the derive proc macro --- canyon_core/Cargo.toml | 2 +- canyon_core/src/connection/clients/mssql.rs | 4 +- canyon_core/src/connection/clients/mysql.rs | 4 +- .../src/connection/clients/postgresql.rs | 4 +- canyon_core/src/connection/database_type.rs | 7 ++ canyon_core/src/mapper.rs | 12 ++- canyon_core/src/rows.rs | 56 ++++++------ canyon_macros/src/canyon_mapper_macro.rs | 87 ++++++++++++------- canyon_migrations/Cargo.toml | 3 +- .../src/migrations/information_schema.rs | 1 + tests/canyon_integration_tests.rs | 1 + tests/crud/foreign_key_operations.rs | 2 +- tests/migrations/mod.rs | 1 - 13 files changed, 112 insertions(+), 72 deletions(-) diff --git a/canyon_core/Cargo.toml b/canyon_core/Cargo.toml index ca9b77be..32f82877 100644 --- a/canyon_core/Cargo.toml +++ b/canyon_core/Cargo.toml @@ -31,4 +31,4 @@ cfg-if = "1.0.0" [features] postgres = ["tokio-postgres"] mssql = ["tiberius", "async-std"] -mysql = ["mysql_async","mysql_common"] \ No newline at end of file +mysql = ["mysql_async", "mysql_common"] \ No newline at end of file diff --git a/canyon_core/src/connection/clients/mssql.rs b/canyon_core/src/connection/clients/mssql.rs index e17d4479..49e4e619 100644 --- a/canyon_core/src/connection/clients/mssql.rs +++ b/canyon_core/src/connection/clients/mssql.rs @@ -34,7 +34,7 @@ pub(crate) mod sqlserver_query_launcher { .await? .into_iter() .flatten() - .map(|row| R::deserialize_sqlserver(&row)) + .flat_map(|row| R::deserialize_sqlserver(&row)) .collect::>()) } @@ -66,7 +66,7 @@ pub(crate) mod sqlserver_query_launcher { let result = execute_query(stmt, params, conn).await?.into_row().await?; match result { - Some(r) => Ok(Some(R::deserialize_sqlserver(&r))), + Some(r) => Ok(Some(R::deserialize_sqlserver(&r)?)), None => Ok(None), } } diff --git a/canyon_core/src/connection/clients/mysql.rs b/canyon_core/src/connection/clients/mysql.rs index 73cdfa13..43484ef2 100644 --- a/canyon_core/src/connection/clients/mysql.rs +++ b/canyon_core/src/connection/clients/mysql.rs @@ -43,7 +43,7 @@ pub(crate) mod mysql_query_launcher { Ok(execute_query(stmt, params, conn) .await? .iter() - .map(|row| R::deserialize_mysql(row)) + .flat_map(|row| R::deserialize_mysql(row)) .collect()) } @@ -68,7 +68,7 @@ pub(crate) mod mysql_query_launcher { let result = execute_query(stmt, params, conn).await?; match result.first() { - Some(r) => Ok(Some(R::deserialize_mysql(r))), + Some(r) => Ok(Some(R::deserialize_mysql(r)?)), None => Ok(None), } } diff --git a/canyon_core/src/connection/clients/postgresql.rs b/canyon_core/src/connection/clients/postgresql.rs index 60d64251..66aa1bfa 100644 --- a/canyon_core/src/connection/clients/postgresql.rs +++ b/canyon_core/src/connection/clients/postgresql.rs @@ -34,7 +34,7 @@ pub(crate) mod postgres_query_launcher { .query(stmt.as_ref(), &get_psql_params(params)) .await? .iter() - .map(|row| R::deserialize_postgresql(row)) + .flat_map(|row| R::deserialize_postgresql(row)) .collect()) } @@ -70,7 +70,7 @@ pub(crate) mod postgres_query_launcher { let result = conn.client.query_one(stmt, m_params.as_slice()).await; match result { - Ok(row) => Ok(Some(R::deserialize_postgresql(&row))), + Ok(row) => Ok(Some(R::deserialize_postgresql(&row)?)), Err(e) => match e.to_string().contains("unexpected number of rows") { true => Ok(None), _ => Err(e)?, diff --git a/canyon_core/src/connection/database_type.rs b/canyon_core/src/connection/database_type.rs index d7cb595f..46935080 100644 --- a/canyon_core/src/connection/database_type.rs +++ b/canyon_core/src/connection/database_type.rs @@ -2,6 +2,7 @@ use super::datasources::Auth; use crate::canyon::Canyon; use serde::Deserialize; use std::error::Error; +use std::fmt::Display; /// Holds the current supported databases by Canyon-SQL #[derive(Deserialize, Debug, Eq, PartialEq, Clone, Copy)] @@ -17,6 +18,12 @@ pub enum DatabaseType { MySQL, } +impl Display for DatabaseType { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(fmt, "{:?}", self) + } +} + impl From<&Auth> for DatabaseType { fn from(value: &Auth) -> Self { value.get_db_type() diff --git a/canyon_core/src/mapper.rs b/canyon_core/src/mapper.rs index 4e999485..b42455d6 100644 --- a/canyon_core/src/mapper.rs +++ b/canyon_core/src/mapper.rs @@ -10,11 +10,17 @@ pub trait RowMapper: Sized { type Output; #[cfg(feature = "postgres")] - fn deserialize_postgresql(row: &tokio_postgres::Row) -> ::Output; + fn deserialize_postgresql( + row: &tokio_postgres::Row, + ) -> Result<::Output, CanyonError>; #[cfg(feature = "mssql")] - fn deserialize_sqlserver(row: &tiberius::Row) -> Self::Output; + fn deserialize_sqlserver( + row: &tiberius::Row, + ) -> Result<::Output, CanyonError>; #[cfg(feature = "mysql")] - fn deserialize_mysql(row: &mysql_async::Row) -> Self::Output; + fn deserialize_mysql( + row: &mysql_async::Row, + ) -> Result<::Output, CanyonError>; } pub trait DefaultRowMapper { diff --git a/canyon_core/src/rows.rs b/canyon_core/src/rows.rs index c6312cd9..ed72f9b8 100644 --- a/canyon_core/src/rows.rs +++ b/canyon_core/src/rows.rs @@ -12,7 +12,7 @@ use tiberius::{self}; #[cfg(feature = "postgres")] use tokio_postgres::{self}; -use crate::mapper::{CanyonError, IntoResults, RowMapper}; +use crate::mapper::RowMapper; use crate::row::Row; use cfg_if::cfg_if; @@ -33,15 +33,15 @@ pub enum CanyonRows { MySQL(Vec), } -impl IntoResults for Result { - fn into_results(self) -> Result, CanyonError> - where - R: RowMapper, - Vec: FromIterator<::Output>, - { - self.map(move |rows| rows.into_results::()) - } -} +// impl IntoResults for Result { +// fn into_results(self) -> Result, CanyonError> +// where +// R: RowMapper, +// Vec: FromIterator<::Output>, +// { +// self.map(move |rows| rows.into_results::()) +// } +// } impl CanyonRows { #[cfg(feature = "postgres")] @@ -68,21 +68,21 @@ impl CanyonRows { } } - /// Consumes `self` and returns the wrapped [`std::vec::Vec`] with the instances of R - pub fn into_results(self) -> Vec - where - R: RowMapper, - Vec: FromIterator<::Output>, - { - match self { - #[cfg(feature = "postgres")] - Self::Postgres(v) => v.iter().map(|row| R::deserialize_postgresql(row)).collect(), - #[cfg(feature = "mssql")] - Self::Tiberius(v) => v.iter().map(|row| R::deserialize_sqlserver(row)).collect(), - #[cfg(feature = "mysql")] - Self::MySQL(v) => v.iter().map(|row| R::deserialize_mysql(row)).collect(), - } - } + // /// Consumes `self` and returns the wrapped [`std::vec::Vec`] with the instances of R + // pub fn into_results(self) -> Vec + // where + // R: RowMapper, + // Vec: FromIterator<::Output>, + // { + // match self { + // #[cfg(feature = "postgres")] + // Self::Postgres(v) => v.iter().map(|row| R::deserialize_postgresql(row)?).collect(), + // #[cfg(feature = "mssql")] + // Self::Tiberius(v) => v.iter().map(|row| R::deserialize_sqlserver(row)?).collect(), + // #[cfg(feature = "mysql")] + // Self::MySQL(v) => v.iter().map(|row| R::deserialize_mysql(row)?).collect(), + // } + // } /// Returns the entity at the given index for the returned rows /// @@ -99,14 +99,16 @@ impl CanyonRows { } pub fn first_row>(&self) -> Option { - match self { + let row = match self { #[cfg(feature = "postgres")] Self::Postgres(v) => v.first().map(|r| T::deserialize_postgresql(r)), #[cfg(feature = "mssql")] Self::Tiberius(v) => v.first().map(|r| T::deserialize_sqlserver(r)), #[cfg(feature = "mysql")] Self::MySQL(v) => v.first().map(|r| T::deserialize_mysql(r)), - } + }; + + row?.ok() } /// Returns the number of elements present on the wrapped collection diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index d42a8735..29618f7b 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -13,6 +13,7 @@ const BY_VALUE_CONVERSION_TARGETS: [&str; 1] = ["String"]; pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { let ty = &ast.ident; + let ty_str = ty.to_string(); let (impl_generics, ty_generics, where_clause) = &ast.generics.split_for_impl(); let mut impl_methods = TokenStream::new(); @@ -26,35 +27,35 @@ pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { }); #[cfg(feature = "postgres")] - let pg_implementation = create_postgres_fields_mapping(&fields); + let pg_implementation = create_postgres_fields_mapping(&ty_str, &fields); #[cfg(feature = "postgres")] impl_methods.extend(quote! { - fn deserialize_postgresql(row: &canyon_sql::db_clients::tokio_postgres::Row) -> Self::Output { - Self { + fn deserialize_postgresql(row: &canyon_sql::db_clients::tokio_postgres::Row) -> Result> { + Ok(Self { #(#pg_implementation),* - } + }) } }); #[cfg(feature = "mssql")] - let sqlserver_implementation = create_sqlserver_fields_mapping(&fields); + let sqlserver_implementation = create_sqlserver_fields_mapping(&ty_str, &fields); #[cfg(feature = "mssql")] impl_methods.extend(quote! { - fn deserialize_sqlserver(row: &canyon_sql::db_clients::tiberius::Row) -> Self::Output { - Self { + fn deserialize_sqlserver(row: &canyon_sql::db_clients::tiberius::Row) -> Result> { + Ok(Self { #(#sqlserver_implementation),* - } + }) } }); #[cfg(feature = "mysql")] - let mysql_implementation = create_mysql_fields_mapping(&fields); + let mysql_implementation = create_mysql_fields_mapping(&ty_str, &fields); #[cfg(feature = "mysql")] impl_methods.extend(quote! { - fn deserialize_mysql(row: &canyon_sql::db_clients::mysql_async::Row) -> Self::Output { - Self { + fn deserialize_mysql(row: &canyon_sql::db_clients::mysql_async::Row) -> Result> { + Ok(Self { #(#mysql_implementation),* - } + }) } }); @@ -67,44 +68,45 @@ pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { } #[cfg(feature = "postgres")] -#[allow(clippy::type_complexity)] -fn create_postgres_fields_mapping( - fields: &[(Visibility, Ident, Type)], -) -> Map, fn(&'_ (Visibility, Ident, Type)) -> TokenStream> { +fn create_postgres_fields_mapping<'a>( + ty: &'a str, + fields: &'a [(Visibility, Ident, Type)], +) -> impl Iterator + use<'a> { fields.iter().map(|(_vis, ident, _ty)| { let ident_name = ident.to_string(); + let err = create_row_mapper_error_extracting_row(ident, ty, DatabaseType::PostgreSql); quote! { - #ident: row.try_get(#ident_name) // TODO: can we wrap RowMapper in a Result and propagate errors with ?\? - .expect(format!("Failed to retrieve the {} field", #ident_name).as_ref()) + #ident: row.try_get::<&str, #_ty>(#ident_name).map_err(|_| #err)? } }) } #[cfg(feature = "mysql")] -#[allow(clippy::type_complexity)] -fn create_mysql_fields_mapping( - fields: &[(Visibility, Ident, Type)], -) -> Map, fn(&'_ (Visibility, Ident, Type)) -> TokenStream> { +fn create_mysql_fields_mapping<'a>( + ty: &'a str, + fields: &'a [(Visibility, Ident, Type)], +) -> impl Iterator + use<'a> { fields.iter().map(|(_vis, ident, _ty)| { let ident_name = ident.to_string(); + let err = create_row_mapper_error_extracting_row(ident, ty, DatabaseType::MySQL); quote! { - #ident: row.get(#ident_name) - .expect(format!("Failed to retrieve the {} field", #ident_name).as_ref()) + #ident: row.get_opt(#ident_name).ok_or_else(|| #err)?? } }) } #[cfg(feature = "mssql")] -#[allow(clippy::type_complexity)] -fn create_sqlserver_fields_mapping( - fields: &[(Visibility, Ident, Type)], -) -> Map, fn(&'_ (Visibility, Ident, Type)) -> TokenStream> { - fields.iter().map(|(_vis, ident, ty)| { +fn create_sqlserver_fields_mapping<'a>( + struct_ty: &'a str, + fields: &'a [(Visibility, Ident, Type)], +) -> impl Iterator + use<'a> { + fields.iter().map(move |(_vis, ident, ty)| { let ident_name = ident.to_string(); + let err = create_row_mapper_error_extracting_row(ident, struct_ty, DatabaseType::SqlServer); let target_field_type_str = get_field_type_as_string(ty); let field_deserialize_impl = - handle_stupid_tiberius_sql_conversions(&target_field_type_str, &ident_name); + handle_stupid_tiberius_sql_conversions(&target_field_type_str, &ident_name, err); quote! { #ident: #field_deserialize_impl @@ -113,10 +115,14 @@ fn create_sqlserver_fields_mapping( } #[cfg(feature = "mssql")] -fn handle_stupid_tiberius_sql_conversions(target_type: &str, ident_name: &str) -> TokenStream { +fn handle_stupid_tiberius_sql_conversions( + target_type: &str, + ident_name: &str, + err: String, +) -> TokenStream { let is_opt_type = target_type.contains("Option"); let handle_opt = if !is_opt_type { - quote! { .expect(format!("Failed to retrieve the `{}` field", #ident_name).as_ref()) } + quote! { .ok_or_else(|| #err)? } } else { quote! {} }; @@ -177,8 +183,10 @@ fn __get_deserializing_type_str(target_type: &str) -> String { .collect::() } +use canyon_core::connection::database_type::DatabaseType; #[cfg(feature = "mssql")] use quote::ToTokens; + #[cfg(feature = "mssql")] fn get_field_type_as_string(typ: &Type) -> String { match typ { @@ -201,6 +209,21 @@ fn get_field_type_as_string(typ: &Type) -> String { } } +fn create_row_mapper_error_extracting_row( + field_ident: &Ident, + ty: &str, + db_ty: DatabaseType, +) -> String { + std::io::Error::new( + std::io::ErrorKind::Other, + format!( + "Failed to retrieve the `{}` field for type: {} with {}", + field_ident, ty, db_ty + ), + ) + .to_string() +} + #[cfg(test)] #[cfg(feature = "mssql")] mod mapper_macro_tests { diff --git a/canyon_migrations/Cargo.toml b/canyon_migrations/Cargo.toml index 78af6b8f..1e6b1c0d 100644 --- a/canyon_migrations/Cargo.toml +++ b/canyon_migrations/Cargo.toml @@ -25,7 +25,8 @@ partialdebug = { workspace = true } walkdir = { workspace = true } [features] +migrations = [] postgres = ["tokio-postgres", "canyon_core/postgres", "canyon_crud/postgres"] mssql = ["tiberius", "canyon_core/mssql", "canyon_crud/mssql"] -mysql = ["mysql_async","mysql_common", "canyon_core/mysql", "canyon_crud/mysql"] +mysql = ["mysql_async", "mysql_common", "canyon_core/mysql", "canyon_crud/mysql"] diff --git a/canyon_migrations/src/migrations/information_schema.rs b/canyon_migrations/src/migrations/information_schema.rs index 3b25086a..00170ed4 100644 --- a/canyon_migrations/src/migrations/information_schema.rs +++ b/canyon_migrations/src/migrations/information_schema.rs @@ -1,5 +1,6 @@ #[cfg(feature = "mssql")] use canyon_core::connection::tiberius::ColumnType as TIB_TY; +#[cfg(feature = "postgres")] use canyon_core::connection::tokio_postgres::types::Type as TP_TYP; use canyon_core::{ column::{Column, ColumnType}, diff --git a/tests/canyon_integration_tests.rs b/tests/canyon_integration_tests.rs index 6e61b549..fa51d7f0 100644 --- a/tests/canyon_integration_tests.rs +++ b/tests/canyon_integration_tests.rs @@ -11,6 +11,7 @@ extern crate canyon_sql; use std::error::Error; mod crud; +#[cfg(feature = "migrations")] mod migrations; mod constants; diff --git a/tests/crud/foreign_key_operations.rs b/tests/crud/foreign_key_operations.rs index ae1c843c..099354ff 100644 --- a/tests/crud/foreign_key_operations.rs +++ b/tests/crud/foreign_key_operations.rs @@ -10,7 +10,7 @@ /// For more info: TODO -> Link to the docs of the foreign key chapter use canyon_sql::crud::CrudOperations; -#[cfg(feature = "mssql")] +#[cfg(feature = "mysql")] use crate::constants::MYSQL_DS; #[cfg(feature = "mssql")] use crate::constants::SQL_SERVER_DS; diff --git a/tests/migrations/mod.rs b/tests/migrations/mod.rs index 4546cf3c..5fab2361 100644 --- a/tests/migrations/mod.rs +++ b/tests/migrations/mod.rs @@ -5,7 +5,6 @@ use canyon_sql::connection::DbConnection; use canyon_sql::core::Canyon; /// Integration tests for the migrations feature of `Canyon-SQL` use canyon_sql::core::Transaction; -#[cfg(feature = "migrations")] use canyon_sql::migrations::handler::Migrations; /// Brings the information of the `PostgreSQL` requested schema From 047125b31006834d3b83dc58d0e290a4443f96a5 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 16 May 2025 13:42:07 +0200 Subject: [PATCH 124/155] chore: raised Rust edition to 2024 --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index aedd1420..66ec4a74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,8 +63,8 @@ proc-macro2 = "1.0.27" [workspace.package] version = "0.5.1" -edition = "2021" -authors = ["Alex Vergara, Gonzalo Busto Musi"] +edition = "2024" +authors = ["Alex Vergara, Gonzalo Busto Musi"] documentation = "https://zerodaycode.github.io/canyon-book/" homepage = "https://github.com/zerodaycode/Canyon-SQL" readme = "README.md" From fda007014c056018d4b673bbdbda0ddc051c63d0 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 16 May 2025 14:43:25 +0200 Subject: [PATCH 125/155] fix: removed unneeded Debug + Clone derives in the autogenerated enums of Fields --- Cargo.toml | 4 ++-- canyon_core/src/query/bounds.rs | 7 ++++++- canyon_core/src/query/querybuilder/contracts/mod.rs | 2 +- canyon_core/src/query/querybuilder/impl/update.rs | 2 +- canyon_entities/src/manager_builder.rs | 2 -- canyon_macros/src/lib.rs | 4 +--- canyon_macros/src/query_operations/insert.rs | 1 - 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 66ec4a74..4499391d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,8 +45,8 @@ tokio = { version = "1.27.0", features = ["full"] } tokio-util = { version = "0.7.4", features = ["compat"] } tokio-postgres = { version = "0.7.2", features = ["with-chrono-0_4"] } tiberius = { version = "0.12.3", features = ["tds73", "chrono", "integrated-auth-gssapi"] } -mysql_async = { version = "0.32.2" } -mysql_common = { version = "0.30.6", features = [ "chrono" ]} +mysql_async = { version = "0.36.1" } +mysql_common = { version = "0.35.4", features = [ "chrono" ]} chrono = { version = "0.4", features = ["serde"] } # Just from TP better? serde = { version = "1.0.138", features = ["derive"] } diff --git a/canyon_core/src/query/bounds.rs b/canyon_core/src/query/bounds.rs index 5162a4bb..bbebb3ff 100644 --- a/canyon_core/src/query/bounds.rs +++ b/canyon_core/src/query/bounds.rs @@ -1,4 +1,9 @@ use crate::query::parameters::QueryParameter; +use std::any::Any; + +pub trait StructMetadata { + fn type_fields_actual_values(&self) -> Vec<&dyn QueryParameter<'_>>; +} pub trait TableMetadata: std::fmt::Display { fn as_str(&self) -> &'static str; @@ -68,6 +73,6 @@ pub trait FieldValueIdentifier<'a> { /// Usually, it's used on the Canyon macros to retrieve the column that /// this side of the relation it's representing pub trait ForeignKeyable { - /// Retrieves the field related to the column passed in + /// Returns the actual value of the field related to the column passed in fn get_fk_column(&self, column: &str) -> Option<&dyn QueryParameter<'_>>; } diff --git a/canyon_core/src/query/querybuilder/contracts/mod.rs b/canyon_core/src/query/querybuilder/contracts/mod.rs index 158623a0..45c75dd3 100644 --- a/canyon_core/src/query/querybuilder/contracts/mod.rs +++ b/canyon_core/src/query/querybuilder/contracts/mod.rs @@ -11,7 +11,7 @@ pub trait UpdateQueryBuilderOps<'a>: QueryBuilderOps<'a> { /// Creates an SQL `SET` clause to specify the columns that must be updated in the sentence fn set(self, columns: &'a [(Z, Q)]) -> Self where - Z: FieldIdentifier + Clone, + Z: FieldIdentifier, Q: QueryParameter<'a>; } diff --git a/canyon_core/src/query/querybuilder/impl/update.rs b/canyon_core/src/query/querybuilder/impl/update.rs index 6e684c48..556717d4 100644 --- a/canyon_core/src/query/querybuilder/impl/update.rs +++ b/canyon_core/src/query/querybuilder/impl/update.rs @@ -8,7 +8,7 @@ impl<'a> UpdateQueryBuilderOps<'a> for UpdateQueryBuilder<'a> { /// Creates an SQL `SET` clause to specify the columns that must be updated in the sentence fn set(mut self, columns: &'a [(Z, Q)]) -> Self where - Z: FieldIdentifier + Clone, + Z: FieldIdentifier, Q: QueryParameter<'a>, { if columns.is_empty() { diff --git a/canyon_entities/src/manager_builder.rs b/canyon_entities/src/manager_builder.rs index 8460b757..a41e3845 100644 --- a/canyon_entities/src/manager_builder.rs +++ b/canyon_entities/src/manager_builder.rs @@ -106,7 +106,6 @@ pub fn generate_enum_with_fields(canyon_entity: &CanyonEntity) -> TokenStream { let generics = &canyon_entity.generics; quote! { - #[derive(Clone, Debug)] #[allow(non_camel_case_types)] #[allow(unused_variables)] #[allow(dead_code)] @@ -177,7 +176,6 @@ pub fn generate_enum_with_fields_values(canyon_entity: &CanyonEntity) -> TokenSt let visibility = &canyon_entity.vis; quote! { - #[derive(Debug)] #[allow(non_camel_case_types)] #[allow(unused_variables)] #[allow(dead_code)] diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index d1aa6dd6..9a72fa14 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -13,7 +13,7 @@ mod utils; use proc_macro::TokenStream as CompilerTokenStream; use quote::quote; -use syn::{parse_macro_input, DeriveInput, Error}; +use syn::{DeriveInput, Error, parse_macro_input}; use utils::{function_parser::FunctionParser, helpers, macro_tokens::MacroTokens}; use crate::canyon_entity_macro::generate_canyon_entity_tokens; @@ -164,8 +164,6 @@ pub fn implement_row_mapper_for_type(input: proc_macro::TokenStream) -> proc_mac canyon_mapper_impl_tokens(ast).into() } -/// Generates the enums that contains the `TypeFields` and `TypeFieldsValues` -/// that the query-builder requires for construct its queries #[proc_macro_derive(Fields)] pub fn querybuilder_fields(input: CompilerTokenStream) -> CompilerTokenStream { let entity_res = syn::parse::(input); diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index bc05f514..70f5cbc0 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -1,4 +1,3 @@ -use crate::query_operations::consts; use crate::utils::macro_tokens::MacroTokens; use proc_macro2::TokenStream; use quote::quote; From 65660d4b7c3215dcced688261c8d0eb9eaa249b1 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 16 May 2025 14:44:12 +0200 Subject: [PATCH 126/155] fix: removed unneeded Debug + Clone derives in the autogenerated enums of Fields --- canyon_core/src/canyon.rs | 2 +- canyon_core/src/connection/clients/mysql.rs | 2 +- .../src/connection/contracts/impl/mssql.rs | 2 +- canyon_core/src/connection/db_connector.rs | 4 +- canyon_core/src/mapper.rs | 2 +- canyon_core/src/query/operators.rs | 4 +- canyon_entities/src/entity.rs | 2 +- canyon_entities/src/field_annotation.rs | 22 +- canyon_macros/src/canyon_entity_macro.rs | 2 +- canyon_macros/src/canyon_mapper_macro.rs | 2 +- canyon_macros/src/foreignkeyable_macro.rs | 2 +- canyon_macros/src/query_operations/consts.rs | 3 +- .../src/query_operations/doc_comments.rs | 9 +- .../src/utils/canyon_crud_attribute.rs | 2 +- canyon_macros/src/utils/function_parser.rs | 2 +- canyon_macros/src/utils/helpers.rs | 4 +- canyon_migrations/src/migrations/processor.rs | 197 +++++++++--------- tests/crud/hex_arch_example.rs | 2 +- tests/crud/init_mssql.rs | 3 +- tests/crud/querybuilder_operations.rs | 40 ++-- 20 files changed, 158 insertions(+), 150 deletions(-) diff --git a/canyon_core/src/canyon.rs b/canyon_core/src/canyon.rs index fe61568d..16871295 100644 --- a/canyon_core/src/canyon.rs +++ b/canyon_core/src/canyon.rs @@ -1,7 +1,7 @@ use crate::connection::conn_errors::DatasourceNotFound; use crate::connection::database_type::DatabaseType; use crate::connection::datasources::{CanyonSqlConfig, DatasourceConfig, Datasources}; -use crate::connection::{db_connector, get_canyon_tokio_runtime, CANYON_INSTANCE}; +use crate::connection::{CANYON_INSTANCE, db_connector, get_canyon_tokio_runtime}; use db_connector::DatabaseConnection; use std::collections::HashMap; use std::sync::Arc; diff --git a/canyon_core/src/connection/clients/mysql.rs b/canyon_core/src/connection/clients/mysql.rs index 43484ef2..ab795241 100644 --- a/canyon_core/src/connection/clients/mysql.rs +++ b/canyon_core/src/connection/clients/mysql.rs @@ -23,9 +23,9 @@ pub(crate) mod mysql_query_launcher { use super::*; - use mysql_async::prelude::Query; use mysql_async::QueryWithParams; use mysql_async::Value; + use mysql_async::prelude::Query; use regex::Regex; use std::sync::Arc; diff --git a/canyon_core/src/connection/contracts/impl/mssql.rs b/canyon_core/src/connection/contracts/impl/mssql.rs index 04d69d5f..2c175951 100644 --- a/canyon_core/src/connection/contracts/impl/mssql.rs +++ b/canyon_core/src/connection/contracts/impl/mssql.rs @@ -1,6 +1,6 @@ use crate::{ connection::{ - clients::mssql::{sqlserver_query_launcher, SqlServerConnection}, + clients::mssql::{SqlServerConnection, sqlserver_query_launcher}, contracts::DbConnection, database_type::DatabaseType, }, diff --git a/canyon_core/src/connection/db_connector.rs b/canyon_core/src/connection/db_connector.rs index be7f6942..24aa623f 100644 --- a/canyon_core/src/connection/db_connector.rs +++ b/canyon_core/src/connection/db_connector.rs @@ -124,8 +124,8 @@ mod connection_helpers { tiberius_config.authentication(auth_config); tiberius_config.trust_cert(); // TODO: this should be specifically set via user input tiberius_config.encryption(tiberius::EncryptionLevel::NotSupported); // TODO: user input - // TODO: in MacOS 15, this is the actual workaround. We need to investigate further - // https://github.com/prisma/tiberius/issues/364 + // TODO: in MacOS 15, this is the actual workaround. We need to investigate further + // https://github.com/prisma/tiberius/issues/364 let tcp = TcpStream::connect(tiberius_config.get_addr()).await?; tcp.set_nodelay(true)?; diff --git a/canyon_core/src/mapper.rs b/canyon_core/src/mapper.rs index b42455d6..756fa5c6 100644 --- a/canyon_core/src/mapper.rs +++ b/canyon_core/src/mapper.rs @@ -36,7 +36,7 @@ where } pub type CanyonError = Box<(dyn std::error::Error + Send + Sync)>; // TODO: convert this into a - // real error +// real error pub trait IntoResults { fn into_results(self) -> Result, CanyonError> where diff --git a/canyon_core/src/query/operators.rs b/canyon_core/src/query/operators.rs index eaa6c5fb..3bf354c8 100644 --- a/canyon_core/src/query/operators.rs +++ b/canyon_core/src/query/operators.rs @@ -56,7 +56,9 @@ impl Operator for Like { match *self { Like::Full => { - format!(" LIKE CONCAT('%', CAST(${placeholder_counter} AS {type_data_to_cast_str}) ,'%')") + format!( + " LIKE CONCAT('%', CAST(${placeholder_counter} AS {type_data_to_cast_str}) ,'%')" + ) } Like::Left => format!( " LIKE CONCAT('%', CAST(${placeholder_counter} AS {type_data_to_cast_str}))" diff --git a/canyon_entities/src/entity.rs b/canyon_entities/src/entity.rs index aeee23e4..e1fb3652 100644 --- a/canyon_entities/src/entity.rs +++ b/canyon_entities/src/entity.rs @@ -3,8 +3,8 @@ use proc_macro2::{Ident, TokenStream}; use quote::quote; use std::convert::TryFrom; use syn::{ - parse::{Parse, ParseBuffer}, Attribute, Generics, ItemStruct, Visibility, + parse::{Parse, ParseBuffer}, }; use super::entity_fields::EntityField; diff --git a/canyon_entities/src/field_annotation.rs b/canyon_entities/src/field_annotation.rs index 8c01615d..abaf80e9 100644 --- a/canyon_entities/src/field_annotation.rs +++ b/canyon_entities/src/field_annotation.rs @@ -1,6 +1,6 @@ use proc_macro2::Ident; use std::{collections::HashMap, convert::TryFrom}; -use syn::{punctuated::Punctuated, Attribute, MetaNameValue, Token}; +use syn::{Attribute, MetaNameValue, Token, punctuated::Punctuated}; /// The available annotations for a field that belongs to any struct /// annotaded with `#[canyon_entity]` @@ -46,7 +46,7 @@ impl EntityFieldAnnotation { "Only bool literals are supported for the `{}` attribute", &attr_value_ident ), - )) + )); } }; data.insert(attr_value_ident, attr_value); @@ -87,12 +87,12 @@ impl EntityFieldAnnotation { // TODO Implement the option (or change it to) to use a Rust Ident instead a Str Lit syn::Lit::Str(v) => v.value(), _ => { - return Err( - syn::Error::new_spanned( - nv.path.clone(), - format!("Only string literals are supported for the `{attr_value_ident}` attribute") - ) - ) + return Err(syn::Error::new_spanned( + nv.path.clone(), + format!( + "Only string literals are supported for the `{attr_value_ident}` attribute" + ), + )); } }; data.insert(attr_value_ident, attr_value); @@ -105,7 +105,7 @@ impl EntityFieldAnnotation { return Err(syn::Error::new_spanned( ident, "Missed `table` argument on the Foreign Key annotation".to_string(), - )) + )); } }, match data.get("column") { @@ -115,7 +115,7 @@ impl EntityFieldAnnotation { ident, "Missed `column` argument on the Foreign Key annotation" .to_string(), - )) + )); } }, )) @@ -143,7 +143,7 @@ impl TryFrom<&&Attribute> for EntityFieldAnnotation { return Err(syn::Error::new_spanned( ident.clone(), format!("Unknown attribute `{}`", &ident), - )) + )); } }) } diff --git a/canyon_macros/src/canyon_entity_macro.rs b/canyon_macros/src/canyon_entity_macro.rs index 4357e3ad..9210c516 100644 --- a/canyon_macros/src/canyon_entity_macro.rs +++ b/canyon_macros/src/canyon_entity_macro.rs @@ -1,8 +1,8 @@ use crate::utils::helpers; +use canyon_entities::CANYON_REGISTER_ENTITIES; use canyon_entities::entity::CanyonEntity; use canyon_entities::manager_builder::generate_user_struct; use canyon_entities::register_types::{CanyonRegisterEntity, CanyonRegisterEntityField}; -use canyon_entities::CANYON_REGISTER_ENTITIES; use proc_macro::TokenStream as CompilerTokenStream; use proc_macro2::{Span, TokenStream}; use quote::quote; diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index 29618f7b..dc5f64d8 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -22,7 +22,7 @@ pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { syn::Data::Struct(ref s) => &s.fields, _ => { return syn::Error::new(ast.ident.span(), "CanyonMapper only works with Structs") - .to_compile_error() + .to_compile_error(); } }); diff --git a/canyon_macros/src/foreignkeyable_macro.rs b/canyon_macros/src/foreignkeyable_macro.rs index 69909fd5..0da201e4 100644 --- a/canyon_macros/src/foreignkeyable_macro.rs +++ b/canyon_macros/src/foreignkeyable_macro.rs @@ -11,7 +11,7 @@ pub fn foreignkeyable_impl_tokens(ast: DeriveInput) -> TokenStream { syn::Data::Struct(ref s) => &s.fields, _ => { return syn::Error::new(ty.span(), "ForeignKeyable only works with Structs") - .to_compile_error() + .to_compile_error(); } }); diff --git a/canyon_macros/src/query_operations/consts.rs b/canyon_macros/src/query_operations/consts.rs index 8f416cbb..38619bd7 100644 --- a/canyon_macros/src/query_operations/consts.rs +++ b/canyon_macros/src/query_operations/consts.rs @@ -6,8 +6,7 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{Ident, Type}; -pub const UNAVAILABLE_CRUD_OP_ON_INSTANCE: &str = - "Operation is unavailable. T doesn't contain a #[primary_key]\ +pub const UNAVAILABLE_CRUD_OP_ON_INSTANCE: &str = "Operation is unavailable. T doesn't contain a #[primary_key]\ annotation. You must construct the query with the QueryBuilder type\ (_query method for the CrudOperations implementors"; diff --git a/canyon_macros/src/query_operations/doc_comments.rs b/canyon_macros/src/query_operations/doc_comments.rs index 6f6e5131..401e5b5e 100644 --- a/canyon_macros/src/query_operations/doc_comments.rs +++ b/canyon_macros/src/query_operations/doc_comments.rs @@ -1,13 +1,11 @@ #![allow(dead_code)] -pub const SELECT_ALL_BASE_DOC_COMMENT: &str = - "Performs a `SELECT * FROM table_name`, where `table_name` it's \ +pub const SELECT_ALL_BASE_DOC_COMMENT: &str = "Performs a `SELECT * FROM table_name`, where `table_name` it's \ the name of your entity but converted to the corresponding \ database convention. P.ej. PostgreSQL prefers table names declared \ with snake_case identifiers."; -pub const SELECT_QUERYBUILDER_DOC_COMMENT: &str = - "Generates a [`canyon_sql::query::querybuilder::SelectQueryBuilder`] \ +pub const SELECT_QUERYBUILDER_DOC_COMMENT: &str = "Generates a [`canyon_sql::query::querybuilder::SelectQueryBuilder`] \ that allows you to customize the query by adding parameters and constrains dynamically. \ \ It performs a `SELECT * FROM table_name`, where `table_name` it's the name of your \ @@ -28,8 +26,7 @@ pub const FIND_BY_PK: &str = "Finds an element on the queried table that matches and Option with the data found wrapped in the Some(T) variant, \ or None if the value isn't found on the table."; -pub const DS_ADVERTISING: &str = - "The query it's made against the database with the configured datasource \ +pub const DS_ADVERTISING: &str = "The query it's made against the database with the configured datasource \ described in the configuration file, and selected with the [`&str`] \ passed as parameter."; diff --git a/canyon_macros/src/utils/canyon_crud_attribute.rs b/canyon_macros/src/utils/canyon_crud_attribute.rs index 473a01a2..265affe4 100644 --- a/canyon_macros/src/utils/canyon_crud_attribute.rs +++ b/canyon_macros/src/utils/canyon_crud_attribute.rs @@ -1,6 +1,6 @@ use proc_macro2::Ident; -use syn::parse::{Parse, ParseStream}; use syn::Token; +use syn::parse::{Parse, ParseStream}; /// Type that helps to parse the: `#[canyon_crud(maps_to = Ident)]` proc macro attribute /// diff --git a/canyon_macros/src/utils/function_parser.rs b/canyon_macros/src/utils/function_parser.rs index 81266d4d..7f0a294b 100644 --- a/canyon_macros/src/utils/function_parser.rs +++ b/canyon_macros/src/utils/function_parser.rs @@ -1,6 +1,6 @@ use syn::{ - parse::{Parse, ParseBuffer}, Attribute, Block, ItemFn, Signature, Visibility, + parse::{Parse, ParseBuffer}, }; /// Implementation of syn::Parse for the `#[canyon]` proc-macro diff --git a/canyon_macros/src/utils/helpers.rs b/canyon_macros/src/utils/helpers.rs index 98a9e769..0477ba51 100644 --- a/canyon_macros/src/utils/helpers.rs +++ b/canyon_macros/src/utils/helpers.rs @@ -1,7 +1,7 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::{ - punctuated::Punctuated, Attribute, Fields, MetaNameValue, Token, Type, TypeGenerics, Visibility, + Attribute, Fields, MetaNameValue, Token, Type, TypeGenerics, Visibility, punctuated::Punctuated, }; use super::macro_tokens::MacroTokens; @@ -111,7 +111,7 @@ fn parse_canyon_entity_attr( Ident::new(&identifier.to_string(), i.span()), "Only string literals are valid values for the attribute arguments", ) - .into_compile_error()) + .into_compile_error()); } } } else { diff --git a/canyon_migrations/src/migrations/processor.rs b/canyon_migrations/src/migrations/processor.rs index 0ee224c1..ea887449 100644 --- a/canyon_migrations/src/migrations/processor.rs +++ b/canyon_migrations/src/migrations/processor.rs @@ -764,65 +764,68 @@ impl DatabaseOperation for TableOperation { let db_type = datasource.get_db_type(); let stmt = match self { - TableOperation::CreateTable(table_name, table_fields) => { - match db_type { - #[cfg(feature = "postgres")] DatabaseType::PostgreSql => { - format!( - "CREATE TABLE \"{table_name}\" ({});", - table_fields - .iter() - .map(|entity_field| format!( - "\"{}\" {}", - entity_field.field_name, - to_postgres_syntax(entity_field) - )) - .collect::>() - .join(", ") - ) - } - #[cfg(feature = "mssql")] DatabaseType::SqlServer => { - format!( - "CREATE TABLE {:?} ({:?});", - table_name, - table_fields - .iter() - .map(|entity_field| format!( - "{} {}", - entity_field.field_name, - to_sqlserver_syntax(entity_field) - )) - .collect::>() - .join(", ") - ) - .replace('"', "") - }, - #[cfg(feature = "mysql")] DatabaseType::MySQL => todo!() - + TableOperation::CreateTable(table_name, table_fields) => match db_type { + #[cfg(feature = "postgres")] + DatabaseType::PostgreSql => { + format!( + "CREATE TABLE \"{table_name}\" ({});", + table_fields + .iter() + .map(|entity_field| format!( + "\"{}\" {}", + entity_field.field_name, + to_postgres_syntax(entity_field) + )) + .collect::>() + .join(", ") + ) } - } + #[cfg(feature = "mssql")] + DatabaseType::SqlServer => format!( + "CREATE TABLE {:?} ({:?});", + table_name, + table_fields + .iter() + .map(|entity_field| format!( + "{} {}", + entity_field.field_name, + to_sqlserver_syntax(entity_field) + )) + .collect::>() + .join(", ") + ) + .replace('"', ""), + #[cfg(feature = "mysql")] + DatabaseType::MySQL => todo!(), + }, TableOperation::AlterTableName(old_table_name, new_table_name) => { match db_type { - #[cfg(feature = "postgres")] DatabaseType::PostgreSql => - format!("ALTER TABLE {old_table_name} RENAME TO {new_table_name};"), - #[cfg(feature = "mssql")] DatabaseType::SqlServer => - /* - Notes: Brackets around `old_table_name`, p.e. - exec sp_rename ['league'], 'leagues' // NOT VALID! - is only allowed for compound names split by a dot. - exec sp_rename ['random.league'], 'leagues' // OK - - CARE! This doesn't mean that we are including the schema. - exec sp_rename ['dbo.random.league'], 'leagues' // OK - exec sp_rename 'dbo.league', 'leagues' // OK - Schema doesn't need brackets - - Due to the automatic mapped name from Rust to DB and vice-versa, this won't - be an allowed behaviour for now, only with the table_name parameter on the - CanyonEntity annotation. - */ - format!("exec sp_rename '{old_table_name}', '{new_table_name}';"), - #[cfg(feature = "mysql")] DatabaseType::MySQL => todo!() - + #[cfg(feature = "postgres")] + DatabaseType::PostgreSql => { + format!("ALTER TABLE {old_table_name} RENAME TO {new_table_name};") + } + #[cfg(feature = "mssql")] + DatabaseType::SqlServer => + /* + Notes: Brackets around `old_table_name`, p.e. + exec sp_rename ['league'], 'leagues' // NOT VALID! + is only allowed for compound names split by a dot. + exec sp_rename ['random.league'], 'leagues' // OK + + CARE! This doesn't mean that we are including the schema. + exec sp_rename ['dbo.random.league'], 'leagues' // OK + exec sp_rename 'dbo.league', 'leagues' // OK - Schema doesn't need brackets + + Due to the automatic mapped name from Rust to DB and vice-versa, this won't + be an allowed behaviour for now, only with the table_name parameter on the + CanyonEntity annotation. + */ + { + format!("exec sp_rename '{old_table_name}', '{new_table_name}';") + } + #[cfg(feature = "mysql")] + DatabaseType::MySQL => todo!(), } } @@ -832,57 +835,61 @@ impl DatabaseOperation for TableOperation { _column_foreign_key, _table_to_reference, _column_to_reference, - ) => { - match db_type { - #[cfg(feature = "postgres")] DatabaseType::PostgreSql => - format!( - "ALTER TABLE {_table_name} ADD CONSTRAINT {_foreign_key_name} \ + ) => match db_type { + #[cfg(feature = "postgres")] + DatabaseType::PostgreSql => format!( + "ALTER TABLE {_table_name} ADD CONSTRAINT {_foreign_key_name} \ FOREIGN KEY ({_column_foreign_key}) REFERENCES {_table_to_reference} ({_column_to_reference});" - ), - #[cfg(feature = "mssql")] DatabaseType::SqlServer => - todo!("[MS-SQL -> Operation still won't supported by Canyon for Sql Server]"), - #[cfg(feature = "mysql")] DatabaseType::MySQL => todo!() - + ), + #[cfg(feature = "mssql")] + DatabaseType::SqlServer => { + todo!("[MS-SQL -> Operation still won't supported by Canyon for Sql Server]") } - } + #[cfg(feature = "mysql")] + DatabaseType::MySQL => todo!(), + }, TableOperation::DeleteTableForeignKey(_table_with_foreign_key, _constraint_name) => { match db_type { - #[cfg(feature = "postgres")] DatabaseType::PostgreSql => - format!( - "ALTER TABLE {_table_with_foreign_key} DROP CONSTRAINT {_constraint_name};", - ), - #[cfg(feature = "mssql")] DatabaseType::SqlServer => - todo!("[MS-SQL -> Operation still won't supported by Canyon for Sql Server]"), - #[cfg(feature = "mysql")] DatabaseType::MySQL => todo!() - + #[cfg(feature = "postgres")] + DatabaseType::PostgreSql => format!( + "ALTER TABLE {_table_with_foreign_key} DROP CONSTRAINT {_constraint_name};", + ), + #[cfg(feature = "mssql")] + DatabaseType::SqlServer => todo!( + "[MS-SQL -> Operation still won't supported by Canyon for Sql Server]" + ), + #[cfg(feature = "mysql")] + DatabaseType::MySQL => todo!(), } } - TableOperation::AddTablePrimaryKey(_table_name, _entity_field) => { - match db_type { - #[cfg(feature = "postgres")] DatabaseType::PostgreSql => - format!( - "ALTER TABLE \"{_table_name}\" ADD PRIMARY KEY (\"{}\");", - _entity_field.field_name - ), - #[cfg(feature = "mssql")] DatabaseType::SqlServer => - todo!("[MS-SQL -> Operation still won't supported by Canyon for Sql Server]"), - #[cfg(feature = "mysql")] DatabaseType::MySQL => todo!() - + TableOperation::AddTablePrimaryKey(_table_name, _entity_field) => match db_type { + #[cfg(feature = "postgres")] + DatabaseType::PostgreSql => format!( + "ALTER TABLE \"{_table_name}\" ADD PRIMARY KEY (\"{}\");", + _entity_field.field_name + ), + #[cfg(feature = "mssql")] + DatabaseType::SqlServer => { + todo!("[MS-SQL -> Operation still won't supported by Canyon for Sql Server]") } - } - - TableOperation::DeleteTablePrimaryKey(table_name, primary_key_name) => { - match db_type { - #[cfg(feature = "postgres")] DatabaseType::PostgreSql => - format!("ALTER TABLE {table_name} DROP CONSTRAINT {primary_key_name} CASCADE;"), - #[cfg(feature = "mssql")] DatabaseType::SqlServer => - format!("ALTER TABLE {table_name} DROP CONSTRAINT {primary_key_name} CASCADE;"), - #[cfg(feature = "mysql")] DatabaseType::MySQL => todo!() + #[cfg(feature = "mysql")] + DatabaseType::MySQL => todo!(), + }, + TableOperation::DeleteTablePrimaryKey(table_name, primary_key_name) => match db_type { + #[cfg(feature = "postgres")] + DatabaseType::PostgreSql => { + format!("ALTER TABLE {table_name} DROP CONSTRAINT {primary_key_name} CASCADE;") } - } + #[cfg(feature = "mssql")] + DatabaseType::SqlServer => { + format!("ALTER TABLE {table_name} DROP CONSTRAINT {primary_key_name} CASCADE;") + } + #[cfg(feature = "mysql")] + DatabaseType::MySQL => todo!(), + }, }; save_migrations_query_to_execute(stmt, &datasource.name); diff --git a/tests/crud/hex_arch_example.rs b/tests/crud/hex_arch_example.rs index 338ea642..c522a5a9 100644 --- a/tests/crud/hex_arch_example.rs +++ b/tests/crud/hex_arch_example.rs @@ -1,6 +1,6 @@ use canyon_sql::connection::DbConnection; use canyon_sql::core::Canyon; -use canyon_sql::macros::{canyon_entity, CanyonCrud, CanyonMapper}; +use canyon_sql::macros::{CanyonCrud, CanyonMapper, canyon_entity}; use canyon_sql::query::querybuilder::SelectQueryBuilder; use std::error::Error; diff --git a/tests/crud/init_mssql.rs b/tests/crud/init_mssql.rs index a0eb6083..ee8bc341 100644 --- a/tests/crud/init_mssql.rs +++ b/tests/crud/init_mssql.rs @@ -22,8 +22,7 @@ use canyon_sql::runtime::tokio_util::compat::TokioAsyncWriteCompatExt; #[canyon_sql::macros::canyon_tokio_test] #[ignore] fn initialize_sql_server_docker_instance() { - static CONN_STR: &str = - "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true;Encrypt=true"; + static CONN_STR: &str = "server=tcp:localhost,1434;User Id=SA;Password=SqlServer-10;TrustServerCertificate=true;Encrypt=true"; canyon_sql::runtime::futures::executor::block_on(async { let mut config = Config::from_ado_string(CONN_STR).expect("could not parse ado string"); diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index c5a4dc66..c5aeb5c6 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -394,15 +394,17 @@ fn test_crud_delete_with_querybuilder_with_mssql() { .await .expect("Error connecting with the database when we are going to delete data! :)"); - assert!(Player::select_query_with(DatabaseType::SqlServer) - .unwrap() - .r#where(PlayerFieldValue::id(&122), Comp::Eq) - .build() - .unwrap() - .launch_with::<&str, Player>(SQL_SERVER_DS) - .await - .unwrap() - .is_empty()); + assert!( + Player::select_query_with(DatabaseType::SqlServer) + .unwrap() + .r#where(PlayerFieldValue::id(&122), Comp::Eq) + .build() + .unwrap() + .launch_with::<&str, Player>(SQL_SERVER_DS) + .await + .unwrap() + .is_empty() + ); } /// Same as the above delete, but with the specified datasource @@ -419,15 +421,17 @@ fn test_crud_delete_with_querybuilder_with_mysql() { .await .expect("Error connecting with the database when we are going to delete data! :)"); - assert!(Player::select_query_with(DatabaseType::MySQL) - .unwrap() - .r#where(PlayerFieldValue::id(&122), Comp::Eq) - .build() - .unwrap() - .launch_with::<&str, Player>(MYSQL_DS) - .await - .unwrap() - .is_empty()); + assert!( + Player::select_query_with(DatabaseType::MySQL) + .unwrap() + .r#where(PlayerFieldValue::id(&122), Comp::Eq) + .build() + .unwrap() + .launch_with::<&str, Player>(MYSQL_DS) + .await + .unwrap() + .is_empty() + ); } /// Tests for the generated SQL query after use the From 4bb9fac1458d5cfab8ef0747af9221e1a91f79ab Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 19 May 2025 21:27:57 +0200 Subject: [PATCH 127/155] refactor(wip): towards a better default CrudOperations that doesn't use Transaction --- canyon_core/src/canyon.rs | 30 ++++------ .../src/connection/contracts/impl/str.rs | 25 +++++---- canyon_core/src/query/query.rs | 2 +- .../src/query_operations/foreign_key.rs | 23 ++++---- canyon_macros/src/query_operations/mod.rs | 2 +- canyon_macros/src/query_operations/read.rs | 55 +++++++------------ canyon_migrations/src/migrations/handler.rs | 6 +- canyon_migrations/src/migrations/memory.rs | 5 +- canyon_migrations/src/migrations/processor.rs | 5 +- tests/crud/hex_arch_example.rs | 6 +- tests/migrations/mod.rs | 4 +- 11 files changed, 73 insertions(+), 90 deletions(-) diff --git a/canyon_core/src/canyon.rs b/canyon_core/src/canyon.rs index 16871295..a9359d60 100644 --- a/canyon_core/src/canyon.rs +++ b/canyon_core/src/canyon.rs @@ -70,7 +70,9 @@ impl Canyon { /// Returns an error if the `Canyon` instance has not yet been initialized. /// In that case, the user must call [`Canyon::init`] before accessing the singleton. pub fn instance() -> Result<&'static Self, Box> { - Ok(CANYON_INSTANCE.get().ok_or_else(|| { + Ok(CANYON_INSTANCE.get().ok_or_else(|| { // TODO: just call Canyon::init()? Why should we raise this error? + // I guess that there's no point in making it fail for the user to manually start Canyon when we can handle everything + // internally Box::new(std::io::Error::new( std::io::ErrorKind::Other, "Canyon not initialized. Call `Canyon::init()` first.", @@ -176,24 +178,22 @@ impl Canyon { } // Retrieve a read-only connection from the cache - pub async fn get_default_connection( + pub fn get_default_connection( &self, - ) -> Result, DatasourceNotFound> { - Ok(self + ) -> Result<&SharedConnection, DatasourceNotFound> { + self .default_connection .as_ref() - .ok_or_else(|| DatasourceNotFound::from(None))? - .lock() - .await) + .ok_or_else(|| DatasourceNotFound::from(None)) } // Retrieve a read-only connection from the cache - pub async fn get_connection( + pub fn get_connection( &self, name: &str, - ) -> Result, DatasourceNotFound> { + ) -> Result<&SharedConnection, DatasourceNotFound> { if name.is_empty() { - return self.get_default_connection().await; + return self.get_default_connection(); } let conn = self @@ -201,15 +201,7 @@ impl Canyon { .get(name) .ok_or_else(|| DatasourceNotFound::from(Some(name)))?; - Ok(conn.lock().await) - } - - // Retrieve a mutable connection from the cache - pub async fn get_mut_connection( - &self, - name: &str, - ) -> Result, DatasourceNotFound> { - self.get_connection(name).await + Ok(conn) } } diff --git a/canyon_core/src/connection/contracts/impl/str.rs b/canyon_core/src/connection/contracts/impl/str.rs index 8d4de229..b24ee90a 100644 --- a/canyon_core/src/connection/contracts/impl/str.rs +++ b/canyon_core/src/connection/contracts/impl/str.rs @@ -9,8 +9,9 @@ macro_rules! impl_db_connection { params: &[&'a dyn crate::query::parameters::QueryParameter<'a>], ) -> Result> { let conn = crate::connection::Canyon::instance()? - .get_connection(self) - .await?; + .get_connection(self)? + .lock() + .await; conn.query_rows(stmt, params).await } @@ -25,8 +26,9 @@ macro_rules! impl_db_connection { Vec: std::iter::FromIterator<::Output>, { let conn = crate::connection::Canyon::instance()? - .get_connection(self) - .await?; + .get_connection(self)? + .lock() + .await; conn.query(stmt, params).await } @@ -39,8 +41,9 @@ macro_rules! impl_db_connection { R: crate::mapper::RowMapper, { let conn = crate::connection::Canyon::instance()? - .get_connection(self) - .await?; + .get_connection(self)? + .lock() + .await; conn.query_one::(stmt, params).await } @@ -50,8 +53,9 @@ macro_rules! impl_db_connection { params: &[&'a dyn crate::query::parameters::QueryParameter<'a>], ) -> Result> { let conn = crate::connection::Canyon::instance()? - .get_connection(self) - .await?; + .get_connection(self)? + .lock() + .await; conn.query_one_for(stmt, params).await } @@ -61,8 +65,9 @@ macro_rules! impl_db_connection { params: &[&'a dyn crate::query::parameters::QueryParameter<'a>], ) -> Result> { let conn = crate::connection::Canyon::instance()? - .get_connection(self) - .await?; + .get_connection(self)? + .lock() + .await; conn.execute(stmt, params).await } diff --git a/canyon_core/src/query/query.rs b/canyon_core/src/query/query.rs index 51bb1a9b..9c3670f4 100644 --- a/canyon_core/src/query/query.rs +++ b/canyon_core/src/query/query.rs @@ -42,7 +42,7 @@ impl<'a> Query<'a> { where Vec: FromIterator<::Output>, { - let mut input = Canyon::instance()?.get_default_connection().await?; + let mut input = Canyon::instance()?.get_default_connection()?.lock().await; ::query(&self.sql, &self.params, input.deref_mut()).await } diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index 4f5bfc1d..9bf559bb 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -99,14 +99,13 @@ fn generate_find_by_foreign_key_tokens( quote! { /// Searches the parent entity (if exists) for this type #quoted_method_signature { - <#fk_ty as canyon_sql::core::Transaction>::query_one::< - &str, - &[&dyn canyon_sql::query::QueryParameter<'_>], - #fk_ty - >( + let default_db_conn = canyon_sql::core::Canyon::instance()? + .get_default_connection()? + .lock() + .await; + default_db_conn.query_one::<#fk_ty>( #stmt, - &[&self.#field_ident as &dyn canyon_sql::query::QueryParameter<'_>], - "" + &[&self.#field_ident as &dyn canyon_sql::query::QueryParameter<'_>] ).await } }, @@ -193,11 +192,11 @@ fn generate_find_by_reverse_foreign_key_tokens( #quoted_method_signature { let lookup_value = #lookup_value; - <#ty #ty_generics as canyon_sql::core::Transaction>::query::<&str, #mapper_ty>( - #stmt, - &[lookup_value], - "" - ).await + let default_db_conn = canyon_sql::core::Canyon::instance()? + .get_default_connection()? + .lock() + .await; + default_db_conn.query::<&str, #mapper_ty>(#stmt, &[lookup_value]).await } }, )); diff --git a/canyon_macros/src/query_operations/mod.rs b/canyon_macros/src/query_operations/mod.rs index 300d3615..d7d99cad 100644 --- a/canyon_macros/src/query_operations/mod.rs +++ b/canyon_macros/src/query_operations/mod.rs @@ -44,7 +44,7 @@ pub fn impl_crud_operations_trait_for_struct( }; crud_ops_tokens.extend(quote! { - use canyon_sql::core::IntoResults; + use canyon_sql::connection::DbConnection; use canyon_sql::core::RowMapper; impl #impl_generics canyon_sql::crud::CrudOperations<#mapper_ty> for #ty #ty_generics #where_clause { diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index ad913842..f82f9892 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -11,17 +11,16 @@ pub fn generate_read_operations_tokens( table_schema_data: &String, ) -> TokenStream { let ty = macro_data.ty; - let (_, ty_generics, _) = macro_data.generics.split_for_impl(); let mapper_ty = macro_data .retrieve_mapping_target_type() .as_ref() .unwrap_or(ty); let find_all_tokens = - generate_find_all_operations_tokens(ty, Some(&ty_generics), mapper_ty, table_schema_data); - let count_tokens = generate_count_operations_tokens(ty, Some(&ty_generics), table_schema_data); + generate_find_all_operations_tokens(mapper_ty, table_schema_data); + let count_tokens = generate_count_operations_tokens(table_schema_data); let find_by_pk_tokens = - generate_find_by_pk_operations_tokens(macro_data, Some(&ty_generics), table_schema_data); + generate_find_by_pk_operations_tokens(macro_data, table_schema_data); let read_querybuilder_ops = generate_select_querybuilder_tokens(table_schema_data); quote! { @@ -33,8 +32,6 @@ pub fn generate_read_operations_tokens( } fn generate_find_all_operations_tokens( - ty: &Ident, - ty_generics: Option<&TypeGenerics>, mapper_ty: &Ident, table_schema_data: &String, ) -> TokenStream { @@ -44,7 +41,7 @@ fn generate_find_all_operations_tokens( // TODO: remember that this queries statements must be autogenerated by some automatic procedure let find_all = - __details::find_all_generators::create_find_all_macro(ty, ty_generics, mapper_ty, &fa_stmt); + __details::find_all_generators::create_find_all_macro(mapper_ty, &fa_stmt); let find_all_with = __details::find_all_generators::create_find_all_with_macro(mapper_ty, &fa_stmt); @@ -90,12 +87,10 @@ fn generate_select_querybuilder_tokens(table_schema_data: &String) -> TokenStrea } fn generate_count_operations_tokens( - ty: &Ident, - ty_generics: Option<&TypeGenerics>, table_schema_data: &String, ) -> TokenStream { let count_stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); - let count = __details::count_generators::create_count_macro(ty, ty_generics, &count_stmt); + let count = __details::count_generators::create_count_macro(&count_stmt); let count_with = __details::count_generators::create_count_with_macro(&count_stmt); quote! { @@ -106,7 +101,6 @@ fn generate_count_operations_tokens( fn generate_find_by_pk_operations_tokens( macro_data: &MacroTokens<'_>, - ty_generics: Option<&TypeGenerics>, table_schema_data: &String, ) -> TokenStream { let ty = macro_data.ty; @@ -134,8 +128,6 @@ fn generate_find_by_pk_operations_tokens( ); let find_by_pk = __details::find_by_pk_generators::create_find_by_pk_macro( - ty, - ty_generics, mapper_ty, &stmt, &no_pk_runtime_err, @@ -162,19 +154,15 @@ mod __details { use syn::TypeGenerics; pub fn create_find_all_macro( - ty: &Ident, - ty_generics: Option<&TypeGenerics>, mapper_ty: &Ident, stmt: &str, ) -> TokenStream { quote! { async fn find_all() - -> Result, Box<(dyn std::error::Error + Send + Sync)>> { - <#ty #ty_generics as canyon_sql::core::Transaction>::query::<&str, #mapper_ty>( - #stmt, - &[], - "" - ).await + -> Result, Box<(dyn std::error::Error + Send + Sync)>> + { + let default_db_conn = canyon_sql::core::Canyon::instance()?.get_default_connection()?.lock().await; + default_db_conn.query(#stmt, &[]).await } } } @@ -198,13 +186,12 @@ mod __details { use syn::TypeGenerics; pub fn create_count_macro( - ty: &syn::Ident, - ty_generics: Option<&TypeGenerics>, stmt: &str, ) -> TokenStream { quote! { async fn count() -> Result> { - Ok(<#ty #ty_generics as canyon_sql::core::Transaction>::query_one_for::<&str, &[&dyn canyon_sql::query::QueryParameter<'_>], i64>(#stmt, &[], "").await? as i64) + let default_db_conn = canyon_sql::core::Canyon::instance()?.get_default_connection()?.lock().await; + default_db_conn.query_one_for(#stmt, &[]).await } } } @@ -226,19 +213,17 @@ mod __details { use syn::TypeGenerics; pub fn create_find_by_pk_macro( - ty: &Ident, - ty_generics: Option<&TypeGenerics>, mapper_ty: &Ident, stmt: &str, pk_runtime_error: &Option, ) -> TokenStream { let body = if pk_runtime_error.is_none() { quote! { - <#ty #ty_generics as canyon_sql::core::Transaction>::query_one::< - &str, - &[&'a (dyn canyon_sql::query::QueryParameter<'a>)], - #mapper_ty - >(#stmt, &[value], "").await + let default_db_conn = canyon_sql::core::Canyon::instance()? + .get_default_connection()? + .lock() + .await; + default_db_conn.query_one::<#mapper_ty>(#stmt, &[value]).await } } else { quote! { #pk_runtime_error } @@ -295,9 +280,8 @@ mod macro_builder_read_ops_tests { #[test] fn test_create_find_all_macro() { - let ty = syn::parse_str::("User").unwrap(); let mapper_ty = syn::parse_str::("User").unwrap(); - let tokens = create_find_all_macro(&ty, None, &mapper_ty, SELECT_ALL_STMT); + let tokens = create_find_all_macro(&mapper_ty, SELECT_ALL_STMT); let generated = tokens.to_string(); assert!(generated.contains("async fn find_all")); @@ -321,7 +305,7 @@ mod macro_builder_read_ops_tests { #[test] fn test_create_count_macro() { let ty = syn::parse_str::("User").unwrap(); - let tokens = create_count_macro(&ty, None, COUNT_STMT); + let tokens = create_count_macro(COUNT_STMT); let generated = tokens.to_string(); assert!(generated.contains("async fn count")); @@ -343,11 +327,10 @@ mod macro_builder_read_ops_tests { #[test] fn test_create_find_by_pk_macro() { - let ty = syn::parse_str::("User").unwrap(); let mapper_ty = syn::parse_str::("User").unwrap(); let pk_runtime_error = None; let tokens = - create_find_by_pk_macro(&ty, None, &mapper_ty, FIND_BY_PK_STMT, &pk_runtime_error); + create_find_by_pk_macro(&mapper_ty, FIND_BY_PK_STMT, &pk_runtime_error); let generated = tokens.to_string(); assert!(generated.contains("async fn find_by_pk")); diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index e6235edd..5624c642 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -1,3 +1,4 @@ +use std::ops::DerefMut; use crate::{ canyon_crud::DatabaseType, constants, @@ -44,13 +45,14 @@ impl Migrations { let mut db_conn = Canyon::instance() .unwrap_or_else(|_| panic!("Failure getting db connection: {}", &datasource.name)) .get_connection(&datasource.name) - .await .unwrap_or_else(|_| { panic!( "Unable to get a database connection on the migrations processor for: {:?}", datasource.name ) - }); + }) + .lock() + .await; let canyon_entities = CANYON_REGISTER_ENTITIES.lock().unwrap().to_vec(); let canyon_memory = CanyonMemory::remember(datasource, &canyon_entities).await; diff --git a/canyon_migrations/src/migrations/memory.rs b/canyon_migrations/src/migrations/memory.rs index 38d7c14f..87d46f35 100644 --- a/canyon_migrations/src/migrations/memory.rs +++ b/canyon_migrations/src/migrations/memory.rs @@ -75,13 +75,14 @@ impl CanyonMemory { ) }) .get_connection(&datasource.name) - .await .unwrap_or_else(|_| { panic!( "Unable to get a database connection on Canyon Memory: {:?}", datasource.name ) - }); + }) + .lock() + .await; // Creates the memory table if not exists Self::create_memory(&datasource.name, &mut db_conn, &datasource.get_db_type()).await; diff --git a/canyon_migrations/src/migrations/processor.rs b/canyon_migrations/src/migrations/processor.rs index ea887449..c2d2b93f 100644 --- a/canyon_migrations/src/migrations/processor.rs +++ b/canyon_migrations/src/migrations/processor.rs @@ -587,13 +587,14 @@ impl MigrationsProcessor { let db_conn = Canyon::instance() .expect("Error getting db connection on `from_query_register`") .get_connection(datasource_name) - .await .unwrap_or_else(|_| { panic!( "Unable to get a database connection on Canyon Memory: {:?}", datasource_name ) - }); + }) + .lock() + .await; for query_to_execute in datasource.1 { let res = db_conn.query_rows(query_to_execute, &[]).await; diff --git a/tests/crud/hex_arch_example.rs b/tests/crud/hex_arch_example.rs index c522a5a9..6acb8156 100644 --- a/tests/crud/hex_arch_example.rs +++ b/tests/crud/hex_arch_example.rs @@ -1,4 +1,3 @@ -use canyon_sql::connection::DbConnection; use canyon_sql::core::Canyon; use canyon_sql::macros::{CanyonCrud, CanyonMapper, canyon_entity}; use canyon_sql::query::querybuilder::SelectQueryBuilder; @@ -10,8 +9,9 @@ fn test_hex_arch_find_all() { let binding = Canyon::instance() .unwrap() .get_default_connection() - .await - .unwrap(); + .unwrap() + .lock() + .await; let league_service = LeagueServiceAdapter { league_repository: LeagueRepositoryAdapter { db_conn: binding.postgres_connection(), diff --git a/tests/migrations/mod.rs b/tests/migrations/mod.rs index 5fab2361..a334a5dd 100644 --- a/tests/migrations/mod.rs +++ b/tests/migrations/mod.rs @@ -18,12 +18,12 @@ fn test_migrations_postgresql_status_query() { let ds = ds.unwrap(); let ds_name = &ds.name; - let db_conn = canyon.get_connection(ds_name).await.unwrap_or_else(|_| { + let db_conn = canyon.get_connection(ds_name).unwrap_or_else(|_| { panic!( "Unable to get a database connection on Canyon Memory: {:?}", ds_name ) - }); + }).lock().await; let results = db_conn .query_rows(constants::FETCH_PUBLIC_SCHEMA, &[]) From 6d7111be6ddb87b650b411b2b1e84a2a4c7886b5 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 20 May 2025 15:32:43 +0200 Subject: [PATCH 128/155] refactor(internal): cleaned the insert operation macro tokens generators --- canyon_core/src/canyon.rs | 15 +- canyon_crud/src/crud.rs | 97 +++++++ canyon_macros/src/query_operations/insert.rs | 279 +++++++++---------- canyon_macros/src/query_operations/read.rs | 25 +- canyon_migrations/src/migrations/handler.rs | 2 +- tests/migrations/mod.rs | 16 +- 6 files changed, 244 insertions(+), 190 deletions(-) diff --git a/canyon_core/src/canyon.rs b/canyon_core/src/canyon.rs index a9359d60..b6db4a90 100644 --- a/canyon_core/src/canyon.rs +++ b/canyon_core/src/canyon.rs @@ -70,7 +70,8 @@ impl Canyon { /// Returns an error if the `Canyon` instance has not yet been initialized. /// In that case, the user must call [`Canyon::init`] before accessing the singleton. pub fn instance() -> Result<&'static Self, Box> { - Ok(CANYON_INSTANCE.get().ok_or_else(|| { // TODO: just call Canyon::init()? Why should we raise this error? + Ok(CANYON_INSTANCE.get().ok_or_else(|| { + // TODO: just call Canyon::init()? Why should we raise this error? // I guess that there's no point in making it fail for the user to manually start Canyon when we can handle everything // internally Box::new(std::io::Error::new( @@ -178,20 +179,14 @@ impl Canyon { } // Retrieve a read-only connection from the cache - pub fn get_default_connection( - &self, - ) -> Result<&SharedConnection, DatasourceNotFound> { - self - .default_connection + pub fn get_default_connection(&self) -> Result<&SharedConnection, DatasourceNotFound> { + self.default_connection .as_ref() .ok_or_else(|| DatasourceNotFound::from(None)) } // Retrieve a read-only connection from the cache - pub fn get_connection( - &self, - name: &str, - ) -> Result<&SharedConnection, DatasourceNotFound> { + pub fn get_connection(&self, name: &str) -> Result<&SharedConnection, DatasourceNotFound> { if name.is_empty() { return self.get_default_connection(); } diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 5f5d0918..ad222d0b 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -61,10 +61,107 @@ where where I: DbConnection + Send + 'a; + /// Inserts the current instance into the corresponding database table using the default datasource. + /// + /// This asynchronous operation creates a new row in the database based on the data in `self`. + /// Upon successful insertion, it updates the primary key field (`self.`) with the value + /// generated by the database. + /// + /// # Behavior + /// + /// - Requires a mutable reference to `self` (`&mut self`) because the method updates the primary key field. + /// - Utilizes the default datasource as specified in the configuration. + /// - Returns a `Result` indicating success or failure of the operation. + /// + /// # Errors + /// + /// Returns an error if: + /// - The insertion fails due to database constraints or connectivity issues. + /// - The default datasource is not properly configured or unavailable. + /// + /// # Examples + /// + /// ```rust + /// let mut lec = League { + /// id: Default::default(), + /// ext_id: 1, + /// slug: "LEC".to_string(), + /// name: "League Europe Champions".to_string(), + /// region: "EU West".to_string(), + /// image_url: "https://lec.eu".to_string(), + /// }; + /// + /// println!("Before insert: {:?}", lec); + /// + /// match lec.insert().await { + /// Ok(_) => println!("After insert: {:?}", lec), + /// Err(e) => eprintln!("Insert failed: {:?}", e), + /// } + /// ``` + /// + /// # Notes + /// + /// Ensure that the default datasource is correctly configured in your application settings. + /// The primary key field must be set to some column before calling `insert`, otherwise, the + /// operation will be launched anyway and will insert all the fields, so ensure that your table + /// your [`Canyon`] annotations matches your database definitions fn insert<'a>( &'a mut self, ) -> impl Future>> + Send; + /// # Brief + /// + /// This operation is the same as [`self.insert()`](method@self.insert) + /// + /// + /// Inserts the current instance into the specified datasource. + /// + /// Similar to [`insert`](Self::insert), but allows specifying the datasource to use for the operation. + /// + /// # Parameters + /// + /// - `input`: An implementation of [`DbConnection`] representing the target datasource. + /// + /// # Behavior + /// + /// - Requires a mutable reference to `self` (`&mut self`) because the method updates the primary key field. + /// - Uses the provided `DbConnection` instead of the default datasource. + /// - Returns a `Result` indicating success or failure of the operation. + /// + /// # Errors + /// + /// Returns an error if: + /// - The insertion fails due to database constraints or connectivity issues. + /// - The provided datasource is not properly configured or unavailable. + /// + /// # Examples + /// + /// ```ignore + /// let mut lec = League { + /// id: Default::default(), + /// ext_id: 1, + /// slug: "LEC".to_string(), + /// name: "League Europe Champions".to_string(), + /// region: "EU West".to_string(), + /// image_url: "https://lec.eu".to_string(), + /// }; + /// + /// let custom_connection = canyon_sql::core::Canyon()::instance()? + /// .get_default_connection()? + /// .lock() + /// .await; + /// + /// match lec.insert_with(custom_connection).await { + /// Ok(_) => println!("Insert successful"), + /// Err(e) => eprintln!("Insert failed: {:?}", e), + /// } + /// ``` + /// + /// # Notes + /// + /// Use this method when you need to insert data into a specific datasource other than the default, + /// or when you have an actual mock of the [`DbConnection`] implementor and you're interested in + /// unit testing your procedure. fn insert_with<'a, I>( &mut self, input: I, diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 70f5cbc0..2041ac31 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -2,39 +2,21 @@ use crate::utils::macro_tokens::MacroTokens; use proc_macro2::TokenStream; use quote::quote; -/// Generates the TokenStream for the _insert_result() CRUD operation -pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &String) -> TokenStream { - let mut insert_ops_tokens = TokenStream::new(); - - let ty = macro_data.ty; - let is_mapper_ty_present = macro_data.retrieve_mapping_target_type().is_some(); - let (_, ty_generics, _) = macro_data.generics.split_for_impl(); - - // Retrieves the fields of the Struct as a collection of Strings, already parsed - // the condition of remove the primary key if it's present and it's autoincremental - let insert_columns = macro_data.get_column_names_pk_parsed().join(", "); - - // Returns a String with the generic $x placeholder for the query parameters. - let placeholders = macro_data.placeholders_generator(); - - // Retrieves the fields of the Struct - let fields = macro_data.get_columns_pk_parsed(); - - let insert_values = fields.iter().map(|field| { - let field = field.ident.as_ref().unwrap(); - quote! { &self.#field } - }); - - let primary_key = macro_data.get_primary_key_annotation(); - let pk_ident_type = macro_data - .fields_with_types() - .into_iter() - .find(|(i, _t)| Some(i.to_string()) == primary_key); +pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &str) -> TokenStream { + let insert_method_ops = generate_insert_method_tokens(macro_data, table_schema_data); + // let multi_insert_tokens = generate_multiple_insert_tokens(macro_data, table_schema_data); - let ins_values = quote! { - let values: &[&dyn canyon_sql::query::QueryParameter<'_>] = &[#(#insert_values),*]; - }; + quote! { + #insert_method_ops + // #multi_insert_tokens + } +} +// Generates the TokenStream for the _insert operation +pub fn generate_insert_method_tokens( + macro_data: &MacroTokens, + table_schema_data: &str, +) -> TokenStream { let insert_signature = quote! { async fn insert<'a>(&'a mut self) -> Result<(), Box> @@ -46,147 +28,134 @@ pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &Stri I: canyon_sql::connection::DbConnection + Send + 'a }; - let stmt = format!( - "INSERT INTO {} ({}) VALUES ({})", - table_schema_data, insert_columns, placeholders - ); + let insert_body; + let insert_with_body; + let insert_values; - let insert_body = if let Some(pk_data) = pk_ident_type { - let pk_ident = pk_data.0; - let pk_type = pk_data.1; + let is_mapper_ty_present = macro_data.retrieve_mapping_target_type().is_some(); + if is_mapper_ty_present { + let raised_err = __details::generate_unsupported_operation_err(); + insert_body = raised_err.clone(); // TODO: Can't we do it better? + insert_with_body = raised_err; + insert_values = quote! {}; + } else { + let stmt = __details::generate_insert_sql_statement(macro_data, table_schema_data); + insert_values = __details::generate_insert_fn_values_slice_expr(macro_data); + insert_body = __details::generate_insert_fn_body_tokens(macro_data, &stmt, false); + insert_with_body = __details::generate_insert_fn_body_tokens(macro_data, &stmt, true); + }; - quote! { - #ins_values + quote! { + #insert_signature { + #insert_values + #insert_body + } - let stmt = format!("{} RETURNING {}", #stmt , #primary_key); + #insert_with_signature { + #insert_values + #insert_with_body + } + } +} - self.#pk_ident = <#ty #ty_generics as canyon_sql::core::Transaction>::query_one_for::< - String, - &[&dyn canyon_sql::query::QueryParameter<'_>], - #pk_type - >( - stmt, - values, - input - ).await?; +mod __details { + use super::*; + + pub(crate) fn generate_insert_fn_body_tokens( + macro_data: &MacroTokens, + stmt: &str, + is_with_method: bool, + ) -> TokenStream { + let primary_key = macro_data.get_primary_key_annotation(); + let pk_ident_and_type = macro_data + .fields_with_types() + .into_iter() + .find(|(i, _t)| Some(i.to_string()) == primary_key); + + let db_conn = if is_with_method { + quote! {input} + } else { + quote! { default_db_conn } + }; + + let mut insert_body_tokens = TokenStream::new(); + if !is_with_method { + insert_body_tokens.extend(quote! { + let default_db_conn = canyon_sql::core::Canyon::instance()? + .get_default_connection()? + .lock() + .await; + }); + } - Ok(()) + if let Some(pk_data) = pk_ident_and_type { + let pk_ident = pk_data.0; + let pk_type = pk_data.1; + + insert_body_tokens.extend(quote! { + self.#pk_ident = #db_conn.query_one_for::<#pk_type>(#stmt, values).await?; + Ok(()) + }); + } else { + insert_body_tokens.extend(quote! { + let _ = #db_conn.execute(#stmt, values).await?; + Ok(()) + }); } - } else { + + insert_body_tokens + } + + pub(crate) fn generate_insert_fn_values_slice_expr(macro_data: &MacroTokens) -> TokenStream { + // Retrieves the fields of the Struct + let fields = macro_data.get_columns_pk_parsed(); + + let insert_values = fields.iter().map(|field| { + let field = field.ident.as_ref().unwrap(); + quote! { &self.#field } + }); + quote! { - #ins_values - <#ty #ty_generics as canyon_sql::core::Transaction>::query_rows( // TODO: this should be execute - #stmt, - values, - input - ).await?; + let values: &[&dyn canyon_sql::query::QueryParameter<'_>] = &[#(#insert_values),*]; + } + } - Ok(()) + pub(crate) fn generate_insert_sql_statement( + macro_data: &MacroTokens, + table_schema_data: &str, + ) -> String { + // Retrieves the fields of the Struct as a collection of Strings, already parsed + // the condition of remove the primary key if it's present, and it's auto incremental + let insert_columns = macro_data.get_column_names_pk_parsed().join(", "); + + // Returns a String with the generic $x placeholder for the query parameters. + // Already takes in consideration if there's pk annotation + let placeholders = macro_data.placeholders_generator(); + + let mut stmt = format!( + "INSERT INTO {} ({}) VALUES ({})", + table_schema_data, insert_columns, placeholders + ); + + if let Some(primary_key) = macro_data.get_primary_key_annotation() { + stmt.push_str(format!(" RETURNING {}", primary_key).as_str()); } - }; - let insert_transaction = if is_mapper_ty_present { + stmt + } + + pub(crate) fn generate_unsupported_operation_err() -> TokenStream { quote! { Err( std::io::Error::new( std::io::ErrorKind::Unsupported, - "Can't use the 'Insert' family transactions if your T type in CrudOperations is the same type that implements RowMapper" + "Can't use the 'Insert' family transactions as a method (that receives self as first parameter) \ + if your T type in CrudOperations is NOT the same type that implements RowMapper. \ + Consider to use instead the provided insert_entity or insert_entity_with functions." ).into_inner().unwrap() ) } - } else { - quote! { #insert_body } - }; - - insert_ops_tokens.extend(quote! { - /// Inserts into a database entity the current data in `self`, generating a new - /// entry (row), returning the `PRIMARY KEY` = `self.` with the specified - /// datasource by its `datasource name`, defined in the configuration file. - /// - /// This `insert` operation needs a `&mut` reference. That's because typically, - /// an insert operation represents *new* data stored in the database, so, when - /// inserted, the database will generate a unique new value for the - /// `pk` field, having a unique identifier for every record, and it will - /// automatically assign that returned pk to `self.`. So, after the `insert` - /// operation, you instance will have the correct value that is the *PRIMARY KEY* - /// of the database row that represents. - /// - /// This operation returns a result type, indicating a possible failure querying the database. - /// - /// ## *Examples* - ///``` - /// let mut lec: League = League { - /// id: Default::default(), - /// ext_id: 1, - /// slug: "LEC".to_string(), - /// name: "League Europe Champions".to_string(), - /// region: "EU West".to_string(), - /// image_url: "https://lec.eu".to_string(), - /// }; - /// - /// println!("LEC before: {:?}", &lec); - /// - /// let ins_result = lec.insert_result().await; - /// - /// // Now, we can handle the result returned, because it can contain a - /// // critical error that may lead your program to panic - /// if let Ok(_) = ins_result { - /// println!("LEC after: {:?}", &lec); - /// } else { - /// eprintln!("{:?}", ins_result.err()) - /// } - /// ``` - /// - #insert_signature { - let input = ""; - #insert_transaction - } - - /// Inserts into a database entity the current data in `self`, generating a new - /// entry (row), returning the `PRIMARY KEY` = `self.` with the specified - /// datasource by its `datasource name`, defined in the configuration file. - /// - /// This `insert` operation needs a `&mut` reference. That's because typically, - /// an insert operation represents *new* data stored in the database, so, when - /// inserted, the database will generate a unique new value for the - /// `pk` field, having a unique identifier for every record, and it will - /// automatically assign that returned pk to `self.`. So, after the `insert` - /// operation, your instance will have the correct value that is the *PRIMARY KEY* - /// of the database row that represents. - /// - /// This operation returns a result type, indicating a possible failure querying the database. - /// - /// ## *Examples* - ///``` - /// let mut lec: League = League { - /// id: Default::default(), - /// ext_id: 1, - /// slug: "LEC".to_string(), - /// name: "League Europe Champions".to_string(), - /// region: "EU West".to_string(), - /// image_url: "https://lec.eu".to_string(), - /// }; - /// - /// println!("LEC before: {:?}", &lec); - /// - /// let ins_result = lec.insert_result().await; - /// - /// // Now, we can handle the result returned, because it can contains a - /// // critical error that may leads your program to panic - /// if let Ok(_) = ins_result { - /// println!("LEC after: {:?}", &lec); - /// } else { - /// eprintln!("{:?}", ins_result.err()) - /// } - /// ``` - /// - #insert_with_signature { #insert_transaction } - }); - - // let multi_insert_tokens = generate_multiple_insert_tokens(macro_data, table_schema_data); - // insert_ops_tokens.extend(multi_insert_tokens); - - insert_ops_tokens + } } /// Generates the TokenStream for the __insert() CRUD operation, but being available diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index f82f9892..38c28655 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -16,11 +16,9 @@ pub fn generate_read_operations_tokens( .as_ref() .unwrap_or(ty); - let find_all_tokens = - generate_find_all_operations_tokens(mapper_ty, table_schema_data); + let find_all_tokens = generate_find_all_operations_tokens(mapper_ty, table_schema_data); let count_tokens = generate_count_operations_tokens(table_schema_data); - let find_by_pk_tokens = - generate_find_by_pk_operations_tokens(macro_data, table_schema_data); + let find_by_pk_tokens = generate_find_by_pk_operations_tokens(macro_data, table_schema_data); let read_querybuilder_ops = generate_select_querybuilder_tokens(table_schema_data); quote! { @@ -40,8 +38,7 @@ fn generate_find_all_operations_tokens( // TODO: bring the helper and convert the SELECT * into the SELECT col_name, col_name2...? // TODO: remember that this queries statements must be autogenerated by some automatic procedure - let find_all = - __details::find_all_generators::create_find_all_macro(mapper_ty, &fa_stmt); + let find_all = __details::find_all_generators::create_find_all_macro(mapper_ty, &fa_stmt); let find_all_with = __details::find_all_generators::create_find_all_with_macro(mapper_ty, &fa_stmt); @@ -86,9 +83,7 @@ fn generate_select_querybuilder_tokens(table_schema_data: &String) -> TokenStrea } } -fn generate_count_operations_tokens( - table_schema_data: &String, -) -> TokenStream { +fn generate_count_operations_tokens(table_schema_data: &String) -> TokenStream { let count_stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); let count = __details::count_generators::create_count_macro(&count_stmt); let count_with = __details::count_generators::create_count_with_macro(&count_stmt); @@ -153,10 +148,7 @@ mod __details { use proc_macro2::TokenStream; use syn::TypeGenerics; - pub fn create_find_all_macro( - mapper_ty: &Ident, - stmt: &str, - ) -> TokenStream { + pub fn create_find_all_macro(mapper_ty: &Ident, stmt: &str) -> TokenStream { quote! { async fn find_all() -> Result, Box<(dyn std::error::Error + Send + Sync)>> @@ -185,9 +177,7 @@ mod __details { use proc_macro2::TokenStream; use syn::TypeGenerics; - pub fn create_count_macro( - stmt: &str, - ) -> TokenStream { + pub fn create_count_macro(stmt: &str) -> TokenStream { quote! { async fn count() -> Result> { let default_db_conn = canyon_sql::core::Canyon::instance()?.get_default_connection()?.lock().await; @@ -329,8 +319,7 @@ mod macro_builder_read_ops_tests { fn test_create_find_by_pk_macro() { let mapper_ty = syn::parse_str::("User").unwrap(); let pk_runtime_error = None; - let tokens = - create_find_by_pk_macro(&mapper_ty, FIND_BY_PK_STMT, &pk_runtime_error); + let tokens = create_find_by_pk_macro(&mapper_ty, FIND_BY_PK_STMT, &pk_runtime_error); let generated = tokens.to_string(); assert!(generated.contains("async fn find_by_pk")); diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index 5624c642..dd99535e 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -1,4 +1,3 @@ -use std::ops::DerefMut; use crate::{ canyon_crud::DatabaseType, constants, @@ -18,6 +17,7 @@ use canyon_core::{ }; use canyon_entities::CANYON_REGISTER_ENTITIES; use partialdebug::placeholder::PartialDebug; +use std::ops::DerefMut; #[derive(PartialDebug)] pub struct Migrations; diff --git a/tests/migrations/mod.rs b/tests/migrations/mod.rs index a334a5dd..f84cf169 100644 --- a/tests/migrations/mod.rs +++ b/tests/migrations/mod.rs @@ -18,12 +18,16 @@ fn test_migrations_postgresql_status_query() { let ds = ds.unwrap(); let ds_name = &ds.name; - let db_conn = canyon.get_connection(ds_name).unwrap_or_else(|_| { - panic!( - "Unable to get a database connection on Canyon Memory: {:?}", - ds_name - ) - }).lock().await; + let db_conn = canyon + .get_connection(ds_name) + .unwrap_or_else(|_| { + panic!( + "Unable to get a database connection on Canyon Memory: {:?}", + ds_name + ) + }) + .lock() + .await; let results = db_conn .query_rows(constants::FETCH_PUBLIC_SCHEMA, &[]) From 758933c021e0abac389ea5407f93a2b3f06504c3 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 22 May 2025 08:10:54 +0200 Subject: [PATCH 129/155] feat(wip)!: adding the insert entity variants for insert data given some T with CrudOperations that inserts any other RowMapper type --- canyon_core/src/query/bounds.rs | 11 ++++- canyon_crud/src/crud.rs | 30 ++++++++++++ canyon_macros/src/canyon_mapper_macro.rs | 11 +++++ canyon_macros/src/query_operations/insert.rs | 50 +++++++++++++++++++- canyon_macros/src/query_operations/read.rs | 3 -- tests/crud/hex_arch_example.rs | 18 +++++++ 6 files changed, 117 insertions(+), 6 deletions(-) diff --git a/canyon_core/src/query/bounds.rs b/canyon_core/src/query/bounds.rs index bbebb3ff..84686ecd 100644 --- a/canyon_core/src/query/bounds.rs +++ b/canyon_core/src/query/bounds.rs @@ -1,7 +1,14 @@ use crate::query::parameters::QueryParameter; -use std::any::Any; -pub trait StructMetadata { +/// Contract that provides a way to Canyon to inspect certain property or values at runtime. +/// +/// Typically, these will be used by the macros to gather some information or to create some user code +/// in more complex scenarios, like when insert an entity, when we need to know the value of the fields of +/// the current instance that we'd like to insert +pub trait Inspectionable { + /// Returns an allocated linear collection with the current values of all the fields declared + /// for the implementor, as the result of the evaluation of the &self.#field expression, iteratively + /// over every type member fn type_fields_actual_values(&self) -> Vec<&dyn QueryParameter<'_>>; } diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index ad222d0b..1fe79d96 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -1,6 +1,7 @@ use canyon_core::connection::contracts::DbConnection; use canyon_core::connection::database_type::DatabaseType; use canyon_core::mapper::RowMapper; +use canyon_core::query::bounds::Inspectionable; use canyon_core::query::parameters::QueryParameter; use canyon_core::query::querybuilder::{ DeleteQueryBuilder, SelectQueryBuilder, UpdateQueryBuilder, @@ -8,6 +9,21 @@ use canyon_core::query::querybuilder::{ use std::error::Error; use std::future::Future; +pub trait Insertable { + // Logically, this looks more like Inserter, and Insertable w'd be the ones with &self + fn insert_entity<'a, T>( + entity: &'a T, + ) -> impl Future>> + Send + where + T: RowMapper + Inspectionable; + // fn insert_entity_with<'a, I>( + // &mut self, + // input: I, + // ) -> impl Future>> + Send + // where + // I: DbConnection + Send + 'a; +} + /// *CrudOperations* it's the core part of Canyon-SQL. /// /// Here it's defined and implemented every CRUD operation @@ -169,6 +185,20 @@ where where I: DbConnection + Send + 'a; + fn insert_entity<'a, T>( + entity: &'a T, + ) -> impl Future>> + Send + where + T: RowMapper + Inspectionable + Sync + 'a; + + fn insert_entity_with<'a, T, I>( + entity: &'a T, + input: I, + ) -> impl Future>> + Send + where + T: RowMapper + Inspectionable + Sync + 'a, + I: DbConnection + Send + 'a; + // TODO: the horripilant multi_insert MUST be replaced with a batch insert // fn multi_insert<'a, T>( // instances: &'a mut [&'a mut T], diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index dc5f64d8..9283962a 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -26,6 +26,10 @@ pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { } }); + let fields_values = fields.iter().map(|(_vis, ident, _ty)| { + quote! { &self.#ident } + }); + #[cfg(feature = "postgres")] let pg_implementation = create_postgres_fields_mapping(&ty_str, &fields); #[cfg(feature = "postgres")] @@ -60,10 +64,17 @@ pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { }); quote! { + use crate::canyon_sql::crud::CrudOperations; impl #impl_generics canyon_sql::core::RowMapper for #ty #ty_generics #where_clause { type Output = #ty; #impl_methods } + + impl #impl_generics canyon_sql::query::bounds::Inspectionable for #ty #ty_generics #where_clause { + fn type_fields_actual_values(&self) -> Vec<&dyn canyon_sql::query::QueryParameter<'_>> { + vec![#(#fields_values),*] + } + } } } diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 2041ac31..1d26dfcc 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -4,10 +4,12 @@ use quote::quote; pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &str) -> TokenStream { let insert_method_ops = generate_insert_method_tokens(macro_data, table_schema_data); + let insert_entity_ops = generate_insert_entity_function_tokens(macro_data, table_schema_data); // let multi_insert_tokens = generate_multiple_insert_tokens(macro_data, table_schema_data); quote! { #insert_method_ops + #insert_entity_ops // #multi_insert_tokens } } @@ -58,6 +60,52 @@ pub fn generate_insert_method_tokens( } } +pub fn generate_insert_entity_function_tokens( + macro_data: &MacroTokens, + table_schema_data: &str, +) -> TokenStream { + let insert_entity_signature = quote! { + async fn insert_entity<'canyon_lt, Entity>(entity: &'canyon_lt Entity) + -> Result<(), Box> + where Entity: canyon_sql::core::RowMapper + + canyon_sql::query::bounds::Inspectionable + + Sync + + 'canyon_lt + }; + let insert_entity_with_signature = quote! { + async fn insert_entity_with<'canyon_lt, Entity, Input>(entity: &'canyon_lt Entity, input: Input) + -> Result<(), Box> + where + Entity: canyon_sql::core::RowMapper + + canyon_sql::query::bounds::Inspectionable + + Sync + + 'canyon_lt, + Input: canyon_sql::connection::DbConnection + Send + 'canyon_lt + }; + + // TODO: missing all the PK logic! + // 1. use MacroTokens on RowMapper, so we can discard to add the pk field value to the entity.type_fields_actual_values + let stmt = __details::generate_insert_sql_statement(macro_data, table_schema_data); + + quote! { + #insert_entity_signature { + let values = entity.type_fields_actual_values(); + let default_db_conn = canyon_sql::core::Canyon::instance()? + .get_default_connection()? + .lock() + .await; + let _ = default_db_conn.execute(#stmt, &values).await?; // Should we remove the pk? Or even look for the pk? + Ok(()) + } + + #insert_entity_with_signature { + let values = entity.type_fields_actual_values(); + let _ = input.execute(#stmt, &values).await?; + Ok(()) + } + } +} + mod __details { use super::*; @@ -73,7 +121,7 @@ mod __details { .find(|(i, _t)| Some(i.to_string()) == primary_key); let db_conn = if is_with_method { - quote! {input} + quote! { input } } else { quote! { default_db_conn } }; diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 38c28655..e01a362a 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -146,7 +146,6 @@ mod __details { pub mod find_all_generators { use super::*; use proc_macro2::TokenStream; - use syn::TypeGenerics; pub fn create_find_all_macro(mapper_ty: &Ident, stmt: &str) -> TokenStream { quote! { @@ -175,7 +174,6 @@ mod __details { pub mod count_generators { use super::*; use proc_macro2::TokenStream; - use syn::TypeGenerics; pub fn create_count_macro(stmt: &str) -> TokenStream { quote! { @@ -200,7 +198,6 @@ mod __details { pub mod find_by_pk_generators { use super::*; use proc_macro2::TokenStream; - use syn::TypeGenerics; pub fn create_find_by_pk_macro( mapper_ty: &Ident, diff --git a/tests/crud/hex_arch_example.rs b/tests/crud/hex_arch_example.rs index 6acb8156..2b09664f 100644 --- a/tests/crud/hex_arch_example.rs +++ b/tests/crud/hex_arch_example.rs @@ -34,6 +34,8 @@ pub struct League { pub trait LeagueService { async fn find_all(&self) -> Result, Box>; + async fn create<'a>(&self, league: &'a League) + -> Result<(), Box>; } // As a domain boundary for the application side of the hexagon pub struct LeagueServiceAdapter { @@ -43,10 +45,19 @@ impl LeagueService for LeagueServiceAdapter { async fn find_all(&self) -> Result, Box> { self.league_repository.find_all().await } + + async fn create<'a>( + &self, + league: &'a League, + ) -> Result<(), Box> { + self.league_repository.create(league).await + } } pub trait LeagueRepository { async fn find_all(&self) -> Result, Box>; + async fn create<'a>(&self, league: &'a League) + -> Result<(), Box>; } // As a domain boundary for the infrastructure side of the hexagon #[derive(CanyonCrud)] @@ -60,4 +71,11 @@ impl LeagueRepository for LeagueRepositoryAdapter SelectQueryBuilder::new("league", self.db_conn.get_database_type()?)?.build()?; self.db_conn.query(select_query, &[]).await } + + async fn create<'a>( + &self, + league: &'a League, + ) -> Result<(), Box> { + LeagueRepositoryAdapter::::insert_entity(league).await + } } From c5eed4cc1550364244239067375e02ae852bf6f0 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 22 May 2025 09:12:29 +0200 Subject: [PATCH 130/155] fix: now, if there's some proc_macro_attribute with the maps_to for CanyonCrud, is taken in consideration for the table_schema_data generation before the default one --- canyon_macros/src/utils/helpers.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/canyon_macros/src/utils/helpers.rs b/canyon_macros/src/utils/helpers.rs index 0477ba51..25db91c4 100644 --- a/canyon_macros/src/utils/helpers.rs +++ b/canyon_macros/src/utils/helpers.rs @@ -65,8 +65,11 @@ pub fn table_schema_parser(macro_data: &MacroTokens<'_>) -> Result Date: Thu, 22 May 2025 09:19:55 +0200 Subject: [PATCH 131/155] feat: entity insertions (default and with) available on CrudOperations --- canyon_crud/src/crud.rs | 8 ++++---- canyon_macros/src/query_operations/insert.rs | 14 ++++++++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 1fe79d96..27dc2cc0 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -186,15 +186,15 @@ where I: DbConnection + Send + 'a; fn insert_entity<'a, T>( - entity: &'a T, - ) -> impl Future>> + Send + entity: &'a mut T, + ) -> impl Future>> where T: RowMapper + Inspectionable + Sync + 'a; fn insert_entity_with<'a, T, I>( - entity: &'a T, + entity: &'a mut T, input: I, - ) -> impl Future>> + Send + ) -> impl Future>> where T: RowMapper + Inspectionable + Sync + 'a, I: DbConnection + Send + 'a; diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 1d26dfcc..c954f5e5 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -21,11 +21,11 @@ pub fn generate_insert_method_tokens( ) -> TokenStream { let insert_signature = quote! { async fn insert<'a>(&'a mut self) - -> Result<(), Box> + -> Result<(), Box> }; let insert_with_signature = quote! { async fn insert_with<'a, I>(&mut self, input: I) - -> Result<(), Box> + -> Result<(), Box> where I: canyon_sql::connection::DbConnection + Send + 'a }; @@ -65,16 +65,17 @@ pub fn generate_insert_entity_function_tokens( table_schema_data: &str, ) -> TokenStream { let insert_entity_signature = quote! { - async fn insert_entity<'canyon_lt, Entity>(entity: &'canyon_lt Entity) - -> Result<(), Box> + async fn insert_entity<'canyon_lt, Entity>(entity: &'canyon_lt mut Entity) + -> Result<(), Box> where Entity: canyon_sql::core::RowMapper + canyon_sql::query::bounds::Inspectionable + Sync + 'canyon_lt }; + let insert_entity_with_signature = quote! { - async fn insert_entity_with<'canyon_lt, Entity, Input>(entity: &'canyon_lt Entity, input: Input) - -> Result<(), Box> + async fn insert_entity_with<'canyon_lt, Entity, Input>(entity: &'canyon_lt mut Entity, input: Input) + -> Result<(), Box> where Entity: canyon_sql::core::RowMapper + canyon_sql::query::bounds::Inspectionable @@ -85,6 +86,7 @@ pub fn generate_insert_entity_function_tokens( // TODO: missing all the PK logic! // 1. use MacroTokens on RowMapper, so we can discard to add the pk field value to the entity.type_fields_actual_values + // 2. this standalone isn't valid, since use the macro data for the CrudOperations type, not for the RowMapper one let stmt = __details::generate_insert_sql_statement(macro_data, table_schema_data); quote! { From 77730fb1a803790471cd4f0d9fd18acac9869dc4 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 23 May 2025 08:45:02 +0200 Subject: [PATCH 132/155] feat: avoiding deadlocks by explicitly return the DbConnection from Canyon in the Arc Mutex, so is the user the one responsible for acquiring the lock over the resource --- canyon_core/src/canyon.rs | 8 +-- .../src/connection/contracts/impl/str.rs | 35 ++++-------- canyon_core/src/query/query.rs | 3 +- .../src/query_operations/foreign_key.rs | 13 ++--- canyon_macros/src/query_operations/insert.rs | 12 ++-- canyon_macros/src/query_operations/read.rs | 15 ++--- canyon_macros/src/utils/helpers.rs | 4 +- canyon_macros/src/utils/macro_tokens.rs | 2 +- canyon_migrations/src/migrations/handler.rs | 17 +++--- canyon_migrations/src/migrations/memory.rs | 17 ++++-- canyon_migrations/src/migrations/processor.rs | 13 +++-- tests/crud/hex_arch_example.rs | 57 +++++++++++++------ tests/crud/querybuilder_operations.rs | 4 +- tests/crud/read_operations.rs | 1 - tests/migrations/mod.rs | 20 +++---- 15 files changed, 113 insertions(+), 108 deletions(-) diff --git a/canyon_core/src/canyon.rs b/canyon_core/src/canyon.rs index b6db4a90..5491285d 100644 --- a/canyon_core/src/canyon.rs +++ b/canyon_core/src/canyon.rs @@ -179,14 +179,14 @@ impl Canyon { } // Retrieve a read-only connection from the cache - pub fn get_default_connection(&self) -> Result<&SharedConnection, DatasourceNotFound> { + pub fn get_default_connection(&self) -> Result { self.default_connection - .as_ref() + .clone() .ok_or_else(|| DatasourceNotFound::from(None)) } // Retrieve a read-only connection from the cache - pub fn get_connection(&self, name: &str) -> Result<&SharedConnection, DatasourceNotFound> { + pub fn get_connection(&self, name: &str) -> Result { if name.is_empty() { return self.get_default_connection(); } @@ -196,7 +196,7 @@ impl Canyon { .get(name) .ok_or_else(|| DatasourceNotFound::from(Some(name)))?; - Ok(conn) + Ok(conn.clone()) } } diff --git a/canyon_core/src/connection/contracts/impl/str.rs b/canyon_core/src/connection/contracts/impl/str.rs index b24ee90a..7fa7e522 100644 --- a/canyon_core/src/connection/contracts/impl/str.rs +++ b/canyon_core/src/connection/contracts/impl/str.rs @@ -8,11 +8,8 @@ macro_rules! impl_db_connection { stmt: &str, params: &[&'a dyn crate::query::parameters::QueryParameter<'a>], ) -> Result> { - let conn = crate::connection::Canyon::instance()? - .get_connection(self)? - .lock() - .await; - conn.query_rows(stmt, params).await + let conn = crate::connection::Canyon::instance()?.get_connection(self)?; + conn.lock().await.query_rows(stmt, params).await } async fn query<'a, S, R>( @@ -25,11 +22,8 @@ macro_rules! impl_db_connection { R: crate::mapper::RowMapper, Vec: std::iter::FromIterator<::Output>, { - let conn = crate::connection::Canyon::instance()? - .get_connection(self)? - .lock() - .await; - conn.query(stmt, params).await + let conn = crate::connection::Canyon::instance()?.get_connection(self)?; + conn.lock().await.query(stmt, params).await } async fn query_one<'a, R>( @@ -40,11 +34,8 @@ macro_rules! impl_db_connection { where R: crate::mapper::RowMapper, { - let conn = crate::connection::Canyon::instance()? - .get_connection(self)? - .lock() - .await; - conn.query_one::(stmt, params).await + let conn = crate::connection::Canyon::instance()?.get_connection(self)?; + conn.lock().await.query_one::(stmt, params).await } async fn query_one_for<'a, T: crate::rows::FromSqlOwnedValue>( @@ -52,11 +43,8 @@ macro_rules! impl_db_connection { stmt: &str, params: &[&'a dyn crate::query::parameters::QueryParameter<'a>], ) -> Result> { - let conn = crate::connection::Canyon::instance()? - .get_connection(self)? - .lock() - .await; - conn.query_one_for(stmt, params).await + let conn = crate::connection::Canyon::instance()?.get_connection(self)?; + conn.lock().await.query_one_for(stmt, params).await } async fn execute<'a>( @@ -64,11 +52,8 @@ macro_rules! impl_db_connection { stmt: &str, params: &[&'a dyn crate::query::parameters::QueryParameter<'a>], ) -> Result> { - let conn = crate::connection::Canyon::instance()? - .get_connection(self)? - .lock() - .await; - conn.execute(stmt, params).await + let conn = crate::connection::Canyon::instance()?.get_connection(self)?; + conn.lock().await.execute(stmt, params).await } fn get_database_type( diff --git a/canyon_core/src/query/query.rs b/canyon_core/src/query/query.rs index 9c3670f4..d88191a5 100644 --- a/canyon_core/src/query/query.rs +++ b/canyon_core/src/query/query.rs @@ -42,7 +42,8 @@ impl<'a> Query<'a> { where Vec: FromIterator<::Output>, { - let mut input = Canyon::instance()?.get_default_connection()?.lock().await; + let default_conn = Canyon::instance()?.get_default_connection()?; + let mut input = default_conn.lock().await; ::query(&self.sql, &self.params, input.deref_mut()).await } diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index 9bf559bb..e6b6c5c1 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -100,10 +100,8 @@ fn generate_find_by_foreign_key_tokens( /// Searches the parent entity (if exists) for this type #quoted_method_signature { let default_db_conn = canyon_sql::core::Canyon::instance()? - .get_default_connection()? - .lock() - .await; - default_db_conn.query_one::<#fk_ty>( + .get_default_connection()?; + default_db_conn.lock().await.query_one::<#fk_ty>( #stmt, &[&self.#field_ident as &dyn canyon_sql::query::QueryParameter<'_>] ).await @@ -143,7 +141,6 @@ fn generate_find_by_reverse_foreign_key_tokens( .retrieve_mapping_target_type() .as_ref() .unwrap_or(ty); - let (_, ty_generics, _) = macro_data.generics.split_for_impl(); for (field_ident, fk_annot) in macro_data.get_fk_annotations().iter() { if let EntityFieldAnnotation::ForeignKey(table, column) = fk_annot { @@ -193,10 +190,8 @@ fn generate_find_by_reverse_foreign_key_tokens( { let lookup_value = #lookup_value; let default_db_conn = canyon_sql::core::Canyon::instance()? - .get_default_connection()? - .lock() - .await; - default_db_conn.query::<&str, #mapper_ty>(#stmt, &[lookup_value]).await + .get_default_connection()?; + default_db_conn.lock().await.query::<&str, #mapper_ty>(#stmt, &[lookup_value]).await } }, )); diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index c954f5e5..413ae3a1 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -93,10 +93,8 @@ pub fn generate_insert_entity_function_tokens( #insert_entity_signature { let values = entity.type_fields_actual_values(); let default_db_conn = canyon_sql::core::Canyon::instance()? - .get_default_connection()? - .lock() - .await; - let _ = default_db_conn.execute(#stmt, &values).await?; // Should we remove the pk? Or even look for the pk? + .get_default_connection()?; + let _ = default_db_conn.lock().await.execute(#stmt, &values).await?; // Should we remove the pk? Or even look for the pk? Ok(()) } @@ -125,16 +123,14 @@ mod __details { let db_conn = if is_with_method { quote! { input } } else { - quote! { default_db_conn } + quote! { default_db_conn.lock().await } }; let mut insert_body_tokens = TokenStream::new(); if !is_with_method { insert_body_tokens.extend(quote! { let default_db_conn = canyon_sql::core::Canyon::instance()? - .get_default_connection()? - .lock() - .await; + .get_default_connection()?; }); } diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index e01a362a..441d0c63 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -2,7 +2,6 @@ use crate::query_operations::consts; use crate::utils::macro_tokens::MacroTokens; use proc_macro2::{Ident, TokenStream}; use quote::quote; -use syn::TypeGenerics; /// Facade function that acts as the unique API for export to the real macro implementation /// of all the generated macros for the READ operations @@ -152,8 +151,8 @@ mod __details { async fn find_all() -> Result, Box<(dyn std::error::Error + Send + Sync)>> { - let default_db_conn = canyon_sql::core::Canyon::instance()?.get_default_connection()?.lock().await; - default_db_conn.query(#stmt, &[]).await + let default_db_conn = canyon_sql::core::Canyon::instance()?.get_default_connection()?; + default_db_conn.lock().await.query(#stmt, &[]).await } } } @@ -178,8 +177,8 @@ mod __details { pub fn create_count_macro(stmt: &str) -> TokenStream { quote! { async fn count() -> Result> { - let default_db_conn = canyon_sql::core::Canyon::instance()?.get_default_connection()?.lock().await; - default_db_conn.query_one_for(#stmt, &[]).await + let default_db_conn = canyon_sql::core::Canyon::instance()?.get_default_connection()?; + default_db_conn.lock().await.query_one_for(#stmt, &[]).await } } } @@ -207,10 +206,8 @@ mod __details { let body = if pk_runtime_error.is_none() { quote! { let default_db_conn = canyon_sql::core::Canyon::instance()? - .get_default_connection()? - .lock() - .await; - default_db_conn.query_one::<#mapper_ty>(#stmt, &[value]).await + .get_default_connection()?; + default_db_conn.lock().await.query_one::<#mapper_ty>(#stmt, &[value]).await } } else { quote! { #pk_runtime_error } diff --git a/canyon_macros/src/utils/helpers.rs b/canyon_macros/src/utils/helpers.rs index 25db91c4..2cf4e337 100644 --- a/canyon_macros/src/utils/helpers.rs +++ b/canyon_macros/src/utils/helpers.rs @@ -67,7 +67,9 @@ pub fn table_schema_parser(macro_data: &MacroTokens<'_>) -> Result MacroTokens<'a> { } /// Retrieves the value of the index of an annotated field with #[primary_key] - pub fn get_pk_index(&self) -> Option { + pub fn _get_pk_index(&self) -> Option { let mut pk_index = None; for (idx, field) in self.fields.iter().enumerate() { for attr in &field.attrs { diff --git a/canyon_migrations/src/migrations/handler.rs b/canyon_migrations/src/migrations/handler.rs index dd99535e..8ee729ef 100644 --- a/canyon_migrations/src/migrations/handler.rs +++ b/canyon_migrations/src/migrations/handler.rs @@ -42,7 +42,7 @@ impl Migrations { ); let mut migrations_processor = MigrationsProcessor::default(); - let mut db_conn = Canyon::instance() + let db_conn = Canyon::instance() .unwrap_or_else(|_| panic!("Failure getting db connection: {}", &datasource.name)) .get_connection(&datasource.name) .unwrap_or_else(|_| { @@ -50,17 +50,18 @@ impl Migrations { "Unable to get a database connection on the migrations processor for: {:?}", datasource.name ) - }) - .lock() - .await; + }); let canyon_entities = CANYON_REGISTER_ENTITIES.lock().unwrap().to_vec(); let canyon_memory = CanyonMemory::remember(datasource, &canyon_entities).await; // Tracked entities that must be migrated whenever Canyon starts - let schema_status = - Self::fetch_database(&datasource.name, &mut db_conn, datasource.get_db_type()) - .await; + let schema_status = Self::fetch_database( + &datasource.name, + db_conn.lock().await.deref_mut(), + datasource.get_db_type(), + ) + .await; let database_tables_schema_info = Self::map_rows(schema_status, datasource.get_db_type()); @@ -125,7 +126,7 @@ impl Migrations { #[cfg(feature = "mssql")] CanyonRows::Tiberius(v) => Self::process_tib_rows(v, db_type), #[cfg(feature = "mysql")] - CanyonRows::MySQL(v) => panic!("Not implemented fetch database in mysql"), + CanyonRows::MySQL(_) => panic!("Not implemented fetch database in mysql"), } } diff --git a/canyon_migrations/src/migrations/memory.rs b/canyon_migrations/src/migrations/memory.rs index 87d46f35..e1b1235c 100644 --- a/canyon_migrations/src/migrations/memory.rs +++ b/canyon_migrations/src/migrations/memory.rs @@ -7,6 +7,7 @@ use canyon_crud::{DatabaseType, DatasourceConfig}; use regex::Regex; use std::collections::HashMap; use std::fs; +use std::ops::DerefMut; use std::sync::Mutex; use walkdir::WalkDir; @@ -67,7 +68,7 @@ impl CanyonMemory { datasource: &DatasourceConfig, canyon_entities: &[CanyonRegisterEntity<'_>], ) -> Self { - let mut db_conn = Canyon::instance() + let db_conn = Canyon::instance() .unwrap_or_else(|_| { panic!( "Failure getting db connection: {} on Canyon Memory", @@ -80,15 +81,21 @@ impl CanyonMemory { "Unable to get a database connection on Canyon Memory: {:?}", datasource.name ) - }) - .lock() - .await; + }); // Creates the memory table if not exists - Self::create_memory(&datasource.name, &mut db_conn, &datasource.get_db_type()).await; + Self::create_memory( + &datasource.name, + db_conn.lock().await.deref_mut(), + &datasource.get_db_type(), + ) + .await; // Retrieve the last status data from the `canyon_memory` table let res = db_conn + .lock() + .await + .deref_mut() .query_rows("SELECT * FROM canyon_memory", &[]) .await .expect("Error querying Canyon Memory"); diff --git a/canyon_migrations/src/migrations/processor.rs b/canyon_migrations/src/migrations/processor.rs index c2d2b93f..d7ecbb40 100644 --- a/canyon_migrations/src/migrations/processor.rs +++ b/canyon_migrations/src/migrations/processor.rs @@ -11,7 +11,7 @@ use regex::Regex; use std::collections::HashMap; use std::fmt::Debug; use std::future::Future; -use std::ops::Not; +use std::ops::{DerefMut, Not}; use super::information_schema::{ColumnMetadata, TableMetadata}; use super::memory::CanyonMemory; @@ -592,12 +592,15 @@ impl MigrationsProcessor { "Unable to get a database connection on Canyon Memory: {:?}", datasource_name ) - }) - .lock() - .await; + }); for query_to_execute in datasource.1 { - let res = db_conn.query_rows(query_to_execute, &[]).await; + let res = db_conn + .lock() + .await + .deref_mut() + .query_rows(query_to_execute, &[]) + .await; match res { Ok(_) => println!( "\t[OK] - {:?} - Query: {:?}", diff --git a/tests/crud/hex_arch_example.rs b/tests/crud/hex_arch_example.rs index 2b09664f..9b80f959 100644 --- a/tests/crud/hex_arch_example.rs +++ b/tests/crud/hex_arch_example.rs @@ -1,27 +1,42 @@ +use canyon_sql::connection::DatabaseConnection; use canyon_sql::core::Canyon; use canyon_sql::macros::{CanyonCrud, CanyonMapper, canyon_entity}; use canyon_sql::query::querybuilder::SelectQueryBuilder; +use canyon_sql::runtime::tokio::sync::Mutex; use std::error::Error; +use std::sync::Arc; #[cfg(feature = "postgres")] #[canyon_sql::macros::canyon_tokio_test] fn test_hex_arch_find_all() { - let binding = Canyon::instance() + let default_db_conn = Canyon::instance() .unwrap() .get_default_connection() - .unwrap() - .lock() - .await; + .unwrap(); let league_service = LeagueServiceAdapter { league_repository: LeagueRepositoryAdapter { - db_conn: binding.postgres_connection(), + db_conn: default_db_conn, }, }; + let find_all_result = league_service.find_all().await; // Connection doesn't return an error assert!(find_all_result.is_ok()); - assert!(!find_all_result.unwrap().is_empty()); + let find_all_result = find_all_result.unwrap(); + assert!(!find_all_result.is_empty()); + // If we try to do a call using the adapter, count will use the default datasource, which is locked at this point, + // since we passed the same connection that it will be using here to the repository! + assert_eq!( + LeagueRepositoryAdapter::::count() + .await + .unwrap() as usize, + find_all_result.len() + ); + // assert_eq!(LeagueRepositoryAdapter::::count_with(binding.deref_mut()).await.unwrap() as usize, find_all_result.len()); + // The line above works, because we're using binding, but in a better ideal world, our repository would hold an Arc> with the connection, + // so the user acquire the lock on every query, just cloning the Arc, which if you remember, just increases in one unit the number of active + // references pointing to the resource behind the atomic smart pointer } #[derive(CanyonMapper)] @@ -34,8 +49,10 @@ pub struct League { pub trait LeagueService { async fn find_all(&self) -> Result, Box>; - async fn create<'a>(&self, league: &'a League) - -> Result<(), Box>; + async fn create<'a>( + &self, + league: &'a mut League, + ) -> Result<(), Box>; } // As a domain boundary for the application side of the hexagon pub struct LeagueServiceAdapter { @@ -48,7 +65,7 @@ impl LeagueService for LeagueServiceAdapter { async fn create<'a>( &self, - league: &'a League, + league: &'a mut League, ) -> Result<(), Box> { self.league_repository.create(league).await } @@ -56,26 +73,30 @@ impl LeagueService for LeagueServiceAdapter { pub trait LeagueRepository { async fn find_all(&self) -> Result, Box>; - async fn create<'a>(&self, league: &'a League) - -> Result<(), Box>; + async fn create<'a>( + &self, + league: &'a mut League, + ) -> Result<(), Box>; } // As a domain boundary for the infrastructure side of the hexagon #[derive(CanyonCrud)] #[canyon_crud(maps_to=League)] -pub struct LeagueRepositoryAdapter<'b, T: DbConnection + Send + Sync> { - db_conn: &'b T, +pub struct LeagueRepositoryAdapter { + // db_conn: &'b T, + db_conn: Arc>, } -impl LeagueRepository for LeagueRepositoryAdapter<'_, T> { +impl LeagueRepository for LeagueRepositoryAdapter { async fn find_all(&self) -> Result, Box> { + let db_conn = self.db_conn.lock().await; let select_query = - SelectQueryBuilder::new("league", self.db_conn.get_database_type()?)?.build()?; - self.db_conn.query(select_query, &[]).await + SelectQueryBuilder::new("league", db_conn.get_database_type()?)?.build()?; + db_conn.query(select_query, &[]).await } async fn create<'a>( &self, - league: &'a League, + league: &'a mut League, ) -> Result<(), Box> { - LeagueRepositoryAdapter::::insert_entity(league).await + Self::insert_entity(league).await } } diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index c5aeb5c6..e530001b 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -20,9 +20,7 @@ use canyon_sql::query::operators::{Comp, Like}; /// use canyon_sql::{ crud::CrudOperations, - query::querybuilder::{ - QueryBuilder, QueryBuilderOps, SelectQueryBuilderOps, UpdateQueryBuilderOps, - }, + query::querybuilder::{QueryBuilderOps, SelectQueryBuilderOps, UpdateQueryBuilderOps}, }; use crate::tests_models::league::*; diff --git a/tests/crud/read_operations.rs b/tests/crud/read_operations.rs index 7f34f637..a711b1b4 100644 --- a/tests/crud/read_operations.rs +++ b/tests/crud/read_operations.rs @@ -12,7 +12,6 @@ use canyon_sql::crud::CrudOperations; use crate::tests_models::league::*; use crate::tests_models::player::*; -use crate::tests_models::tournament::Tournament; /// Tests the behaviour of a SELECT * FROM {table_name} within Canyon, through the /// `::find_all()` associated function derived with the `CanyonCrud` derive proc-macro diff --git a/tests/migrations/mod.rs b/tests/migrations/mod.rs index f84cf169..4475182e 100644 --- a/tests/migrations/mod.rs +++ b/tests/migrations/mod.rs @@ -6,6 +6,7 @@ use canyon_sql::core::Canyon; /// Integration tests for the migrations feature of `Canyon-SQL` use canyon_sql::core::Transaction; use canyon_sql::migrations::handler::Migrations; +use std::ops::DerefMut; /// Brings the information of the `PostgreSQL` requested schema #[cfg(all(feature = "postgres", feature = "migrations"))] @@ -18,18 +19,17 @@ fn test_migrations_postgresql_status_query() { let ds = ds.unwrap(); let ds_name = &ds.name; - let db_conn = canyon - .get_connection(ds_name) - .unwrap_or_else(|_| { - panic!( - "Unable to get a database connection on Canyon Memory: {:?}", - ds_name - ) - }) - .lock() - .await; + let db_conn = canyon.get_connection(ds_name).unwrap_or_else(|_| { + panic!( + "Unable to get a database connection on Canyon Memory: {:?}", + ds_name + ) + }); let results = db_conn + .lock() + .await + .deref_mut() .query_rows(constants::FETCH_PUBLIC_SCHEMA, &[]) .await; assert!(results.is_ok()); From cc053309c0e984f8f877f5e2fc77d3556ab8f9f3 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 23 May 2025 09:10:01 +0200 Subject: [PATCH 133/155] feat: Query constructor --- canyon_core/src/canyon.rs | 6 +++++- canyon_core/src/query/query.rs | 10 +++++----- canyon_core/src/query/querybuilder/types/mod.rs | 5 +---- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/canyon_core/src/canyon.rs b/canyon_core/src/canyon.rs index 5491285d..f231fcda 100644 --- a/canyon_core/src/canyon.rs +++ b/canyon_core/src/canyon.rs @@ -185,7 +185,11 @@ impl Canyon { .ok_or_else(|| DatasourceNotFound::from(None)) } - // Retrieve a read-only connection from the cache + /// Quickly retrieves the default shared database connection. + /// + /// This is a fast and efficient operation: cloning the [`SharedConnection`] + /// simply increases the reference count [`Arc`] without duplicating the underlying + /// [`DatabaseConnection`]. Returns an error if no default connection is configured. pub fn get_connection(&self, name: &str) -> Result { if name.is_empty() { return self.get_default_connection(); diff --git a/canyon_core/src/query/query.rs b/canyon_core/src/query/query.rs index d88191a5..54c8ae47 100644 --- a/canyon_core/src/query/query.rs +++ b/canyon_core/src/query/query.rs @@ -27,11 +27,11 @@ impl AsRef for Query<'_> { } impl<'a> Query<'a> { - pub fn new(sql: String) -> Query<'a> { - Self { - sql, - params: vec![], - } + /// Constructs a new [`Self`] but receiving the number of expected query parameters, allowing + /// to pre-allocate the underlying linear collection that holds the arguments to the exact capacity, + /// potentially saving re-allocations when the query is created + pub fn new(sql: String, params: Vec<&'a dyn QueryParameter<'a>>) -> Query<'a> { + Self { sql, params } } /// Launches the generated query against the database assuming the default diff --git a/canyon_core/src/query/querybuilder/types/mod.rs b/canyon_core/src/query/querybuilder/types/mod.rs index 7332eea1..8057ab41 100644 --- a/canyon_core/src/query/querybuilder/types/mod.rs +++ b/canyon_core/src/query/querybuilder/types/mod.rs @@ -35,10 +35,7 @@ impl<'a> QueryBuilder<'a> { pub fn build(mut self) -> Result, Box<(dyn Error + Send + Sync)>> { // TODO: here we should check for our invariants self.sql.push(';'); - Ok(Query { - sql: self.sql, - params: self.params, - }) + Ok(Query::new(self.sql, self.params)) } pub fn r#where>(&mut self, r#where: Z, op: impl Operator) { From 13010e5f1206cdfe830ac98beb4e69cde244408e Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 23 May 2025 09:44:21 +0200 Subject: [PATCH 134/155] feat: Row Mapper tokens are now handled by MacroTokens --- canyon_macros/src/canyon_mapper_macro.rs | 25 +++++++++++------------- canyon_macros/src/lib.rs | 8 +++++++- canyon_macros/src/utils/macro_tokens.rs | 9 ++++++++- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index 9283962a..b6ba43b6 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -11,22 +11,18 @@ use syn::{DeriveInput, Type, Visibility}; #[cfg(feature = "mssql")] const BY_VALUE_CONVERSION_TARGETS: [&str; 1] = ["String"]; -pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { - let ty = &ast.ident; +pub fn canyon_mapper_impl_tokens(ast: MacroTokens) -> TokenStream { + let ty = &ast.ty; let ty_str = ty.to_string(); let (impl_generics, ty_generics, where_clause) = &ast.generics.split_for_impl(); let mut impl_methods = TokenStream::new(); - // Recovers the identifiers of the structs members - let fields = fields_with_types(match ast.data { - syn::Data::Struct(ref s) => &s.fields, - _ => { - return syn::Error::new(ast.ident.span(), "CanyonMapper only works with Structs") - .to_compile_error(); - } - }); + let fields = ast.fields(); - let fields_values = fields.iter().map(|(_vis, ident, _ty)| { + // Recovers the identifiers of the structs members, adding or removing the pk field + // This is only useful for the impl of Inspectionable + let binding = ast.get_columns_pk_parsed(); + let fields_values = binding.iter().map(|ident| { quote! { &self.#ident } }); @@ -81,7 +77,7 @@ pub fn canyon_mapper_impl_tokens(ast: DeriveInput) -> TokenStream { #[cfg(feature = "postgres")] fn create_postgres_fields_mapping<'a>( ty: &'a str, - fields: &'a [(Visibility, Ident, Type)], + fields: &'a [(&Visibility, &Ident, &Type)], ) -> impl Iterator + use<'a> { fields.iter().map(|(_vis, ident, _ty)| { let ident_name = ident.to_string(); @@ -95,7 +91,7 @@ fn create_postgres_fields_mapping<'a>( #[cfg(feature = "mysql")] fn create_mysql_fields_mapping<'a>( ty: &'a str, - fields: &'a [(Visibility, Ident, Type)], + fields: &'a [(&Visibility, &Ident, &Type)], ) -> impl Iterator + use<'a> { fields.iter().map(|(_vis, ident, _ty)| { let ident_name = ident.to_string(); @@ -109,7 +105,7 @@ fn create_mysql_fields_mapping<'a>( #[cfg(feature = "mssql")] fn create_sqlserver_fields_mapping<'a>( struct_ty: &'a str, - fields: &'a [(Visibility, Ident, Type)], + fields: &'a [(&Visibility, &Ident, &Type)], ) -> impl Iterator + use<'a> { fields.iter().map(move |(_vis, ident, ty)| { let ident_name = ident.to_string(); @@ -194,6 +190,7 @@ fn __get_deserializing_type_str(target_type: &str) -> String { .collect::() } +use crate::utils::macro_tokens::MacroTokens; use canyon_core::connection::database_type::DatabaseType; #[cfg(feature = "mssql")] use quote::ToTokens; diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index 9a72fa14..0edc8d56 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -161,7 +161,13 @@ pub fn implement_foreignkeyable_for_type( #[proc_macro_derive(CanyonMapper)] pub fn implement_row_mapper_for_type(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast: DeriveInput = syn::parse(input).unwrap(); - canyon_mapper_impl_tokens(ast).into() + let macro_data = MacroTokens::new(&ast); + let macro_data = if let Err(err) = macro_data { + return err.to_compile_error().into(); + } else { + macro_data.unwrap() + }; + canyon_mapper_impl_tokens(macro_data).into() } #[proc_macro_derive(Fields)] diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index 60e94cde..ec23cc52 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -20,7 +20,7 @@ pub struct MacroTokens<'a> { } impl<'a> MacroTokens<'a> { - pub fn new(ast: &'a DeriveInput) -> Result { + pub fn new(ast: &'a DeriveInput) -> Result { // TODO: impl syn::parse instead if let syn::Data::Struct(ref s) = ast.data { let attrs = &ast.attrs; let mut canyon_crud_attribute = None; @@ -54,6 +54,13 @@ impl<'a> MacroTokens<'a> { } } + pub fn fields(&self) -> Vec<(&Visibility, &Ident, &Type)> { + self.fields + .iter() + .map(|field| (&field.vis, field.ident.as_ref().unwrap(), &field.ty)) + .collect::>() + } + /// Gives a Vec of tuples that contains the name and /// the type of every field on a Struct pub fn fields_with_types(&self) -> Vec<(&Ident, &Type)> { From 9874902d05cc98034b0b262f69f3397c046627a9 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 23 May 2025 13:06:30 +0200 Subject: [PATCH 135/155] feat: easier way of handling the fields_values returned by Inspectionable --- canyon_macros/src/canyon_mapper_macro.rs | 17 +++++++---------- canyon_macros/src/utils/macro_tokens.rs | 22 +++++++++++++++++++--- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index b6ba43b6..064ae9e6 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -19,13 +19,6 @@ pub fn canyon_mapper_impl_tokens(ast: MacroTokens) -> TokenStream { let fields = ast.fields(); - // Recovers the identifiers of the structs members, adding or removing the pk field - // This is only useful for the impl of Inspectionable - let binding = ast.get_columns_pk_parsed(); - let fields_values = binding.iter().map(|ident| { - quote! { &self.#ident } - }); - #[cfg(feature = "postgres")] let pg_implementation = create_postgres_fields_mapping(&ty_str, &fields); #[cfg(feature = "postgres")] @@ -59,6 +52,10 @@ pub fn canyon_mapper_impl_tokens(ast: MacroTokens) -> TokenStream { } }); + let fields_values = ast.get_fields_idents_pk_parsed().into_iter().map(|ident| { + quote! { &self.#ident } + }); + quote! { use crate::canyon_sql::crud::CrudOperations; impl #impl_generics canyon_sql::core::RowMapper for #ty #ty_generics #where_clause { @@ -77,7 +74,7 @@ pub fn canyon_mapper_impl_tokens(ast: MacroTokens) -> TokenStream { #[cfg(feature = "postgres")] fn create_postgres_fields_mapping<'a>( ty: &'a str, - fields: &'a [(&Visibility, &Ident, &Type)], + fields: &'a [(Visibility, Ident, Type)], ) -> impl Iterator + use<'a> { fields.iter().map(|(_vis, ident, _ty)| { let ident_name = ident.to_string(); @@ -91,7 +88,7 @@ fn create_postgres_fields_mapping<'a>( #[cfg(feature = "mysql")] fn create_mysql_fields_mapping<'a>( ty: &'a str, - fields: &'a [(&Visibility, &Ident, &Type)], + fields: &'a [(Visibility, Ident, Type)], ) -> impl Iterator + use<'a> { fields.iter().map(|(_vis, ident, _ty)| { let ident_name = ident.to_string(); @@ -105,7 +102,7 @@ fn create_mysql_fields_mapping<'a>( #[cfg(feature = "mssql")] fn create_sqlserver_fields_mapping<'a>( struct_ty: &'a str, - fields: &'a [(&Visibility, &Ident, &Type)], + fields: &'a [(Visibility, Ident, Type)], ) -> impl Iterator + use<'a> { fields.iter().map(move |(_vis, ident, ty)| { let ident_name = ident.to_string(); diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index ec23cc52..9bba99d8 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -20,7 +20,8 @@ pub struct MacroTokens<'a> { } impl<'a> MacroTokens<'a> { - pub fn new(ast: &'a DeriveInput) -> Result { // TODO: impl syn::parse instead + pub fn new(ast: &'a DeriveInput) -> Result { + // TODO: impl syn::parse instead if let syn::Data::Struct(ref s) = ast.data { let attrs = &ast.attrs; let mut canyon_crud_attribute = None; @@ -54,10 +55,16 @@ impl<'a> MacroTokens<'a> { } } - pub fn fields(&self) -> Vec<(&Visibility, &Ident, &Type)> { + pub fn fields(&self) -> Vec<(Visibility, Ident, Type)> { self.fields .iter() - .map(|field| (&field.vis, field.ident.as_ref().unwrap(), &field.ty)) + .map(|field| { + ( + field.vis.clone(), + field.ident.clone().unwrap(), + field.clone().ty, + ) + }) .collect::>() } @@ -104,6 +111,15 @@ impl<'a> MacroTokens<'a> { .collect::>() } + /// Returns a collection with all the [`syn::Ident`] for all the type members, skipping (if present) + /// the field which is annotated with #[primary_key] + pub fn get_fields_idents_pk_parsed(&self) -> Vec<&Ident> { + self.get_columns_pk_parsed() + .iter() + .map(|field| field.ident.as_ref().unwrap()) + .collect::>() + } + /// Returns a Vec populated with the name of the fields of the struct /// already quote scaped for avoid the upper case column name mangling. /// From 737637baa45dc8087cee604d35091ee083435228 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 23 May 2025 13:55:06 +0200 Subject: [PATCH 136/155] refactor: macro tokens method operations decoupled to helpers, so it can be used by another trait impls --- canyon_core/src/query/bounds.rs | 12 ++++- canyon_macros/src/canyon_mapper_macro.rs | 3 +- canyon_macros/src/query_operations/insert.rs | 6 +-- canyon_macros/src/utils/helpers.rs | 54 ++++++++++++++++++-- canyon_macros/src/utils/macro_tokens.rs | 35 +++---------- 5 files changed, 72 insertions(+), 38 deletions(-) diff --git a/canyon_core/src/query/bounds.rs b/canyon_core/src/query/bounds.rs index 84686ecd..3a123e94 100644 --- a/canyon_core/src/query/bounds.rs +++ b/canyon_core/src/query/bounds.rs @@ -8,8 +8,16 @@ use crate::query::parameters::QueryParameter; pub trait Inspectionable { /// Returns an allocated linear collection with the current values of all the fields declared /// for the implementor, as the result of the evaluation of the &self.#field expression, iteratively - /// over every type member - fn type_fields_actual_values(&self) -> Vec<&dyn QueryParameter<'_>>; + /// over every type member, but if the type contains in some field the #[primary_key] annotation, + /// this will be skipped!! + /// + /// This is mostly because this operation now is only useful on the insert_entity family operations, + /// and is a fixed invariant in our logic nowadays. + /// + /// # Warning + /// This may change in the future, so that's why this operation shouldn't be used, nor it's + /// recommended to use it publicly as an end-user. + fn fields_actual_values(&self) -> Vec<&dyn QueryParameter<'_>>; } pub trait TableMetadata: std::fmt::Display { diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index 064ae9e6..c29bafdb 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -1,6 +1,5 @@ #![allow(unused_imports)] -use crate::utils::helpers::fields_with_types; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use regex::Regex; @@ -64,7 +63,7 @@ pub fn canyon_mapper_impl_tokens(ast: MacroTokens) -> TokenStream { } impl #impl_generics canyon_sql::query::bounds::Inspectionable for #ty #ty_generics #where_clause { - fn type_fields_actual_values(&self) -> Vec<&dyn canyon_sql::query::QueryParameter<'_>> { + fn fields_actual_values(&self) -> Vec<&dyn canyon_sql::query::QueryParameter<'_>> { vec![#(#fields_values),*] } } diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 413ae3a1..555a9f5d 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -85,13 +85,13 @@ pub fn generate_insert_entity_function_tokens( }; // TODO: missing all the PK logic! - // 1. use MacroTokens on RowMapper, so we can discard to add the pk field value to the entity.type_fields_actual_values + // 1. use MacroTokens on RowMapper, so we can discard to add the pk field value to the entity.fields_actual_values // 2. this standalone isn't valid, since use the macro data for the CrudOperations type, not for the RowMapper one let stmt = __details::generate_insert_sql_statement(macro_data, table_schema_data); quote! { #insert_entity_signature { - let values = entity.type_fields_actual_values(); + let values = entity.fields_actual_values(); let default_db_conn = canyon_sql::core::Canyon::instance()? .get_default_connection()?; let _ = default_db_conn.lock().await.execute(#stmt, &values).await?; // Should we remove the pk? Or even look for the pk? @@ -99,7 +99,7 @@ pub fn generate_insert_entity_function_tokens( } #insert_entity_with_signature { - let values = entity.type_fields_actual_values(); + let values = entity.fields_actual_values(); let _ = input.execute(#stmt, &values).await?; Ok(()) } diff --git a/canyon_macros/src/utils/helpers.rs b/canyon_macros/src/utils/helpers.rs index 2cf4e337..74950850 100644 --- a/canyon_macros/src/utils/helpers.rs +++ b/canyon_macros/src/utils/helpers.rs @@ -1,8 +1,7 @@ +use std::fmt::Write; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use syn::{ - Attribute, Fields, MetaNameValue, Token, Type, TypeGenerics, Visibility, punctuated::Punctuated, -}; +use syn::{Attribute, Fields, MetaNameValue, Token, Type, TypeGenerics, Visibility, punctuated::Punctuated, Field}; use super::macro_tokens::MacroTokens; @@ -27,7 +26,7 @@ pub fn filter_fields(fields: &Fields) -> Vec<(Visibility, Ident)> { .collect::>() } -pub fn fields_with_types(fields: &Fields) -> Vec<(Visibility, Ident, Type)> { +pub fn __fields_with_types(fields: &Fields) -> Vec<(Visibility, Ident, Type)> { fields .iter() .map(|field| { @@ -40,6 +39,28 @@ pub fn fields_with_types(fields: &Fields) -> Vec<(Visibility, Ident, Type)> { .collect::>() } +pub fn placeholders_generator(num_values: usize) -> String { + let mut placeholders = String::new(); + for (i, n) in (1..num_values).enumerate() { + if i > 0 { + placeholders.push_str(", "); + } + write!(placeholders, "${}", n).unwrap(); + } + + placeholders +} + +pub fn field_has_target_attribute(field: &Field, target_attribute: &str) -> bool { + field.attrs.iter().any(|attr| { + attr.path + .segments + .first() + .map(|segment| segment.ident == target_attribute) + .unwrap_or(false) + }) +} + /// If the `canyon_entity` macro has valid attributes attached, and those attrs are the /// user's desired `table_name` and/or the `schema_name`, this method returns its /// correct form to be wired as the table name that the CRUD methods requires for generate @@ -213,3 +234,28 @@ fn test_entity_database_name_defaulter() { "MajorLeague".to_owned() ); } +#[cfg(test)] +mod tests { + use super::*; + use syn::{parse_str, ItemStruct}; + + #[test] + fn detects_target_attribute_correctly() { + let input = r#" + struct Test { + #[my_attr] + field1: String, + field2: i32, + } + "#; + + // Parse the struct + let item: ItemStruct = parse_str(input).expect("Failed to parse struct"); + let fields: Vec<_> = item.fields.iter().collect(); + + // Check the field with #[my_attr] + assert!(field_has_target_attribute(fields[0], "my_attr")); + // Check the field without the attribute + assert!(!field_has_target_attribute(fields[1], "my_attr")); + } +} \ No newline at end of file diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index 9bba99d8..afbb0940 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -5,6 +5,7 @@ use crate::utils::canyon_crud_attribute::CanyonCrudAttribute; use canyon_entities::field_annotation::EntityFieldAnnotation; use proc_macro2::{Ident, Span}; use syn::{Attribute, DeriveInput, Field, Fields, Generics, Type, Visibility}; +use crate::utils::helpers; /// Provides a convenient way of store the data for the TokenStream /// received on a macro @@ -143,10 +144,8 @@ impl<'a> MacroTokens<'a> { .get_struct_fields() .iter() .map(|ident| ident.to_owned().to_string()) - .collect::>() - .iter() .map(|column| column.to_owned() + ", ") - .collect::(); + .collect(); let mut column_names_as_chars = column_names.chars(); column_names_as_chars.next_back(); @@ -160,7 +159,9 @@ impl<'a> MacroTokens<'a> { let mut pk_index = None; for (idx, field) in self.fields.iter().enumerate() { for attr in &field.attrs { - if attr.path.segments[0].clone().ident == "primary_key" { + if attr.path.segments.first() + .map(|segment| segment.ident == "primary_key")? + { pk_index = Some(idx); } } @@ -172,13 +173,7 @@ impl<'a> MacroTokens<'a> { /// column name (field) which belongs pub fn get_primary_key_annotation(&self) -> Option { let f = self.fields.iter().find(|field| { - field - .attrs - .iter() - .map(|attr| attr.path.segments[0].clone().ident) - .map(|ident| ident.to_string()) - .find(|a| a == "primary_key") - == Some("primary_key".to_string()) + helpers::field_has_target_attribute(field, "primary_key") }); f.map(|v| v.ident.clone().unwrap().to_string()) @@ -208,13 +203,7 @@ impl<'a> MacroTokens<'a> { /// annotation. False otherwise. pub fn type_has_primary_key(&self) -> bool { self.fields.iter().any(|field| { - field - .attrs - .iter() - .map(|attr| attr.path.segments[0].clone().ident) - .map(|ident| ident.to_string()) - .find(|a| a == "primary_key") - == Some("primary_key".to_string()) + helpers::field_has_target_attribute(field, "primary_key") }) } @@ -230,14 +219,6 @@ impl<'a> MacroTokens<'a> { self.fields.len() + 1 }; - let mut placeholders = String::new(); - for (i, n) in (1..range_upper_bound).enumerate() { - if i > 0 { - placeholders.push_str(", "); - } - write!(placeholders, "${}", n).unwrap(); - } - - placeholders + helpers::placeholders_generator(range_upper_bound) } } From 439f5b09c8e4ee49891e0ad5f9d3e221e6226d9d Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 23 May 2025 14:25:26 +0200 Subject: [PATCH 137/155] refactor: reducing the number of collections allocations by returning iterators from th e helpers and allocating only when needed --- canyon_core/src/query/bounds.rs | 1 + canyon_macros/src/query_operations/insert.rs | 8 ++--- canyon_macros/src/query_operations/update.rs | 4 +-- canyon_macros/src/utils/macro_tokens.rs | 32 +++++--------------- 4 files changed, 15 insertions(+), 30 deletions(-) diff --git a/canyon_core/src/query/bounds.rs b/canyon_core/src/query/bounds.rs index 3a123e94..394eebd7 100644 --- a/canyon_core/src/query/bounds.rs +++ b/canyon_core/src/query/bounds.rs @@ -18,6 +18,7 @@ pub trait Inspectionable { /// This may change in the future, so that's why this operation shouldn't be used, nor it's /// recommended to use it publicly as an end-user. fn fields_actual_values(&self) -> Vec<&dyn QueryParameter<'_>>; + } pub trait TableMetadata: std::fmt::Display { diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 555a9f5d..26a2fc30 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -156,7 +156,7 @@ mod __details { // Retrieves the fields of the Struct let fields = macro_data.get_columns_pk_parsed(); - let insert_values = fields.iter().map(|field| { + let insert_values = fields.map(|field| { let field = field.ident.as_ref().unwrap(); quote! { &self.#field } }); @@ -172,7 +172,7 @@ mod __details { ) -> String { // Retrieves the fields of the Struct as a collection of Strings, already parsed // the condition of remove the primary key if it's present, and it's auto incremental - let insert_columns = macro_data.get_column_names_pk_parsed().join(", "); + let insert_columns = macro_data.get_struct_fields_as_comma_sep_string(); // Returns a String with the generic $x placeholder for the query parameters. // Already takes in consideration if there's pk annotation @@ -218,12 +218,12 @@ fn _generate_multiple_insert_tokens( let (_, ty_generics, _) = macro_data.generics.split_for_impl(); // Retrieves the fields of the Struct as continuous String - let column_names = macro_data._get_struct_fields_as_strings(); + let column_names = macro_data.get_struct_fields_as_comma_sep_string(); // Retrieves the fields of the Struct let fields = macro_data.get_struct_fields(); - let macro_fields = fields.iter().map(|field| quote! { &instance.#field }); + let macro_fields: Vec = fields.map(|field| quote! { &instance.#field }).collect(); let macro_fields_cloned = macro_fields.clone(); let pk = macro_data.get_primary_key_annotation().unwrap_or_default(); diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index f87c84c7..afdf87c6 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -13,14 +13,14 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let fields = macro_data.get_struct_fields(); let mut vec_columns_values: Vec = Vec::new(); - for (i, column_name) in update_columns.iter().enumerate() { + for (i, column_name) in update_columns.enumerate() { let column_equal_value = format!("{} = ${}", column_name.to_owned(), i + 2); vec_columns_values.push(column_equal_value) } let str_columns_values = vec_columns_values.join(", "); - let update_values = fields.iter().map(|ident| { + let update_values = fields.map(|ident| { quote! { &self.#ident } }); diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index afbb0940..8ce8a00d 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -1,6 +1,4 @@ use std::convert::TryFrom; -use std::fmt::Write; - use crate::utils::canyon_crud_attribute::CanyonCrudAttribute; use canyon_entities::field_annotation::EntityFieldAnnotation; use proc_macro2::{Ident, Span}; @@ -79,11 +77,10 @@ impl<'a> MacroTokens<'a> { } /// Gives a Vec of Ident with the fields of a Struct - pub fn get_struct_fields(&self) -> Vec { + pub fn get_struct_fields(&self) -> impl Iterator { self.fields .iter() .map(|field| field.ident.as_ref().unwrap().clone()) - .collect::>() } /// Returns a Vec populated with the fields of the struct @@ -95,7 +92,7 @@ impl<'a> MacroTokens<'a> { /// to the same behaviour. /// /// Returns every field if there's no PK, or if it's present but autoincremental = false - pub fn get_columns_pk_parsed(&self) -> Vec<&Field> { + pub fn get_columns_pk_parsed(&self) -> impl Iterator { self.fields .iter() .filter(|field| { @@ -109,14 +106,12 @@ impl<'a> MacroTokens<'a> { true } }) - .collect::>() } /// Returns a collection with all the [`syn::Ident`] for all the type members, skipping (if present) /// the field which is annotated with #[primary_key] pub fn get_fields_idents_pk_parsed(&self) -> Vec<&Ident> { self.get_columns_pk_parsed() - .iter() .map(|field| field.ident.as_ref().unwrap()) .collect::>() } @@ -131,27 +126,16 @@ impl<'a> MacroTokens<'a> { /// to the same behaviour. /// /// Returns every field if there's no PK, or if it's present but autoincremental = false - pub fn get_column_names_pk_parsed(&self) -> Vec { + pub fn get_column_names_pk_parsed(&self) -> impl Iterator { self.get_columns_pk_parsed() - .iter() .map(|c| format!("\"{}\"", c.ident.as_ref().unwrap())) - .collect::>() } /// Retrieves the fields of the Struct as continuous String, comma separated - pub fn _get_struct_fields_as_strings(&self) -> String { - let column_names: String = self - .get_struct_fields() - .iter() - .map(|ident| ident.to_owned().to_string()) - .map(|column| column.to_owned() + ", ") - .collect(); - - let mut column_names_as_chars = column_names.chars(); - column_names_as_chars.next_back(); - column_names_as_chars.next_back(); - - column_names_as_chars.as_str().to_owned() + pub fn get_struct_fields_as_comma_sep_string(&self) -> String { + self.get_column_names_pk_parsed() + .collect::>() + .join(", ") } /// Retrieves the value of the index of an annotated field with #[primary_key] @@ -159,7 +143,7 @@ impl<'a> MacroTokens<'a> { let mut pk_index = None; for (idx, field) in self.fields.iter().enumerate() { for attr in &field.attrs { - if attr.path.segments.first() + if attr.path.segments.first() .map(|segment| segment.ident == "primary_key")? { pk_index = Some(idx); From b1b29db5f204081b77d9395e25f860480cddbd43 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 23 May 2025 17:05:09 +0200 Subject: [PATCH 138/155] fix: now the insert entity ops receives the correct values for the queryparameters to be updated --- canyon_core/src/query/bounds.rs | 13 +++-- canyon_macros/src/canyon_mapper_macro.rs | 18 +++++++ canyon_macros/src/query_operations/insert.rs | 50 +++++++++++++++----- canyon_macros/src/utils/helpers.rs | 11 +++-- canyon_macros/src/utils/macro_tokens.rs | 46 +++++++++--------- tests/crud/hex_arch_example.rs | 2 +- 6 files changed, 98 insertions(+), 42 deletions(-) diff --git a/canyon_core/src/query/bounds.rs b/canyon_core/src/query/bounds.rs index 394eebd7..4eb3c3af 100644 --- a/canyon_core/src/query/bounds.rs +++ b/canyon_core/src/query/bounds.rs @@ -10,15 +10,20 @@ pub trait Inspectionable { /// for the implementor, as the result of the evaluation of the &self.#field expression, iteratively /// over every type member, but if the type contains in some field the #[primary_key] annotation, /// this will be skipped!! - /// + /// /// This is mostly because this operation now is only useful on the insert_entity family operations, - /// and is a fixed invariant in our logic nowadays. - /// + /// and is a fixed invariant in our logic nowadays. + /// /// # Warning /// This may change in the future, so that's why this operation shouldn't be used, nor it's /// recommended to use it publicly as an end-user. fn fields_actual_values(&self) -> Vec<&dyn QueryParameter<'_>>; - + + fn fields_as_comma_sep_string(&self) -> &'static str; + + fn queries_placeholders(&self) -> &'static str; + + fn primary_key(&self) -> Option<&'static str>; } pub trait TableMetadata: std::fmt::Display { diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index c29bafdb..f47f894a 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -54,6 +54,12 @@ pub fn canyon_mapper_impl_tokens(ast: MacroTokens) -> TokenStream { let fields_values = ast.get_fields_idents_pk_parsed().into_iter().map(|ident| { quote! { &self.#ident } }); + let fields_as_comma_sep_string = ast.get_struct_fields_as_comma_sep_string(); + let queries_placeholders = ast.placeholders_generator(); + let pk = match ast.get_primary_key_annotation() { + Some(primary_key) => quote! { Some(#primary_key) }, + None => quote! { None }, + }; quote! { use crate::canyon_sql::crud::CrudOperations; @@ -66,6 +72,18 @@ pub fn canyon_mapper_impl_tokens(ast: MacroTokens) -> TokenStream { fn fields_actual_values(&self) -> Vec<&dyn canyon_sql::query::QueryParameter<'_>> { vec![#(#fields_values),*] } + + fn fields_as_comma_sep_string(&self) -> &'static str { + #fields_as_comma_sep_string + } + + fn queries_placeholders(&self) -> &'static str { + #queries_placeholders + } + + fn primary_key(&self) -> Option<&'static str> { + #pk + } } } } diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 26a2fc30..8b86f699 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -4,7 +4,7 @@ use quote::quote; pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &str) -> TokenStream { let insert_method_ops = generate_insert_method_tokens(macro_data, table_schema_data); - let insert_entity_ops = generate_insert_entity_function_tokens(macro_data, table_schema_data); + let insert_entity_ops = generate_insert_entity_function_tokens(table_schema_data); // let multi_insert_tokens = generate_multiple_insert_tokens(macro_data, table_schema_data); quote! { @@ -60,10 +60,7 @@ pub fn generate_insert_method_tokens( } } -pub fn generate_insert_entity_function_tokens( - macro_data: &MacroTokens, - table_schema_data: &str, -) -> TokenStream { +pub fn generate_insert_entity_function_tokens(table_schema_data: &str) -> TokenStream { let insert_entity_signature = quote! { async fn insert_entity<'canyon_lt, Entity>(entity: &'canyon_lt mut Entity) -> Result<(), Box> @@ -84,23 +81,42 @@ pub fn generate_insert_entity_function_tokens( Input: canyon_sql::connection::DbConnection + Send + 'canyon_lt }; - // TODO: missing all the PK logic! - // 1. use MacroTokens on RowMapper, so we can discard to add the pk field value to the entity.fields_actual_values - // 2. this standalone isn't valid, since use the macro data for the CrudOperations type, not for the RowMapper one - let stmt = __details::generate_insert_sql_statement(macro_data, table_schema_data); + let no_fields_to_insert_err = __details::no_fields_to_insert_err(); + + let stmt_ctr = quote! { + let insert_columns = entity.fields_as_comma_sep_string(); + + if insert_columns.is_empty() { + return #no_fields_to_insert_err; + } + + let placeholders = entity.queries_placeholders(); + + let mut stmt = format!( + "INSERT INTO {} ({}) VALUES ({})", + #table_schema_data, insert_columns, placeholders + ); + + if let Some(primary_key) = entity.primary_key() { + stmt.push_str(" RETURNING {}"); + stmt.push_str(primary_key); + } + }; quote! { #insert_entity_signature { + #stmt_ctr; let values = entity.fields_actual_values(); let default_db_conn = canyon_sql::core::Canyon::instance()? .get_default_connection()?; - let _ = default_db_conn.lock().await.execute(#stmt, &values).await?; // Should we remove the pk? Or even look for the pk? + let _ = default_db_conn.lock().await.execute(&stmt, &values).await?; // Should we remove the pk? Or even look for the pk? Ok(()) } #insert_entity_with_signature { + #stmt_ctr; let values = entity.fields_actual_values(); - let _ = input.execute(#stmt, &values).await?; + let _ = input.execute(&stmt, &values).await?; Ok(()) } } @@ -202,6 +218,18 @@ mod __details { ) } } + + pub(crate) fn no_fields_to_insert_err() -> TokenStream { + quote! { + Err( + std::io::Error::new( + std::io::ErrorKind::Unsupported, + "The type has either zero fields or exactly one that is annotated with #[primary_key].\ + That's makes it ineligibly to be used in the insert_entity family of operations." + ).into_inner().unwrap() + ) + } + } } /// Generates the TokenStream for the __insert() CRUD operation, but being available diff --git a/canyon_macros/src/utils/helpers.rs b/canyon_macros/src/utils/helpers.rs index 74950850..b2f4bae7 100644 --- a/canyon_macros/src/utils/helpers.rs +++ b/canyon_macros/src/utils/helpers.rs @@ -1,7 +1,10 @@ -use std::fmt::Write; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use syn::{Attribute, Fields, MetaNameValue, Token, Type, TypeGenerics, Visibility, punctuated::Punctuated, Field}; +use std::fmt::Write; +use syn::{ + Attribute, Field, Fields, MetaNameValue, Token, Type, TypeGenerics, Visibility, + punctuated::Punctuated, +}; use super::macro_tokens::MacroTokens; @@ -237,7 +240,7 @@ fn test_entity_database_name_defaulter() { #[cfg(test)] mod tests { use super::*; - use syn::{parse_str, ItemStruct}; + use syn::{ItemStruct, parse_str}; #[test] fn detects_target_attribute_correctly() { @@ -258,4 +261,4 @@ mod tests { // Check the field without the attribute assert!(!field_has_target_attribute(fields[1], "my_attr")); } -} \ No newline at end of file +} diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index 8ce8a00d..75a2a80e 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -1,9 +1,9 @@ -use std::convert::TryFrom; use crate::utils::canyon_crud_attribute::CanyonCrudAttribute; +use crate::utils::helpers; use canyon_entities::field_annotation::EntityFieldAnnotation; use proc_macro2::{Ident, Span}; +use std::convert::TryFrom; use syn::{Attribute, DeriveInput, Field, Fields, Generics, Type, Visibility}; -use crate::utils::helpers; /// Provides a convenient way of store the data for the TokenStream /// received on a macro @@ -93,19 +93,17 @@ impl<'a> MacroTokens<'a> { /// /// Returns every field if there's no PK, or if it's present but autoincremental = false pub fn get_columns_pk_parsed(&self) -> impl Iterator { - self.fields - .iter() - .filter(|field| { - if !field.attrs.is_empty() { - field.attrs.iter().any(|attr| { - let a = attr.path.segments[0].clone().ident; - let b = attr.tokens.to_string(); - !(a == "primary_key" || b.contains("false")) - }) - } else { - true - } - }) + self.fields.iter().filter(|field| { + if !field.attrs.is_empty() { + field.attrs.iter().any(|attr| { + let a = attr.path.segments[0].clone().ident; + let b = attr.tokens.to_string(); + !(a == "primary_key" || b.contains("false")) + }) + } else { + true + } + }) } /// Returns a collection with all the [`syn::Ident`] for all the type members, skipping (if present) @@ -143,7 +141,10 @@ impl<'a> MacroTokens<'a> { let mut pk_index = None; for (idx, field) in self.fields.iter().enumerate() { for attr in &field.attrs { - if attr.path.segments.first() + if attr + .path + .segments + .first() .map(|segment| segment.ident == "primary_key")? { pk_index = Some(idx); @@ -156,9 +157,10 @@ impl<'a> MacroTokens<'a> { /// Utility for find the primary key attribute (if exists) and the /// column name (field) which belongs pub fn get_primary_key_annotation(&self) -> Option { - let f = self.fields.iter().find(|field| { - helpers::field_has_target_attribute(field, "primary_key") - }); + let f = self + .fields + .iter() + .find(|field| helpers::field_has_target_attribute(field, "primary_key")); f.map(|v| v.ident.clone().unwrap().to_string()) } @@ -186,9 +188,9 @@ impl<'a> MacroTokens<'a> { /// Boolean that returns true if the type contains a `#[primary_key]` /// annotation. False otherwise. pub fn type_has_primary_key(&self) -> bool { - self.fields.iter().any(|field| { - helpers::field_has_target_attribute(field, "primary_key") - }) + self.fields + .iter() + .any(|field| helpers::field_has_target_attribute(field, "primary_key")) } /// Returns a String ready to be inserted on the VALUES Sql clause diff --git a/tests/crud/hex_arch_example.rs b/tests/crud/hex_arch_example.rs index 9b80f959..436ef072 100644 --- a/tests/crud/hex_arch_example.rs +++ b/tests/crud/hex_arch_example.rs @@ -8,7 +8,7 @@ use std::sync::Arc; #[cfg(feature = "postgres")] #[canyon_sql::macros::canyon_tokio_test] -fn test_hex_arch_find_all() { +fn test_hex_arch_ops() { let default_db_conn = Canyon::instance() .unwrap() .get_default_connection() From f6fdc2eb290bc4458c842acb878ca2ce9b092107 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 25 May 2025 10:21:03 +0200 Subject: [PATCH 139/155] feat: update entity implementations --- canyon_core/src/query/bounds.rs | 3 + canyon_crud/src/crud.rs | 16 +++ canyon_macros/src/canyon_mapper_macro.rs | 104 +++++++++++----- canyon_macros/src/query_operations/insert.rs | 2 +- canyon_macros/src/query_operations/update.rs | 124 +++++++++++++++++-- 5 files changed, 208 insertions(+), 41 deletions(-) diff --git a/canyon_core/src/query/bounds.rs b/canyon_core/src/query/bounds.rs index 4eb3c3af..d2c9def0 100644 --- a/canyon_core/src/query/bounds.rs +++ b/canyon_core/src/query/bounds.rs @@ -19,11 +19,14 @@ pub trait Inspectionable { /// recommended to use it publicly as an end-user. fn fields_actual_values(&self) -> Vec<&dyn QueryParameter<'_>>; + /// Returns a linear collection with the names of every field for the implementor as a String + fn fields_names(&self) -> &[&'static str]; fn fields_as_comma_sep_string(&self) -> &'static str; fn queries_placeholders(&self) -> &'static str; fn primary_key(&self) -> Option<&'static str>; + fn primary_key_actual_value(&self) -> &dyn QueryParameter<'_>; } pub trait TableMetadata: std::fmt::Display { diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 27dc2cc0..d5647ae1 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -211,6 +211,8 @@ where // where // I: DbConnection + Send + 'a; + /// Updates a database record that matches the current instance of a T type, returning a + /// result indicating a possible failure querying the database. fn update(&self) -> impl Future>> + Send; fn update_with<'a, I>( @@ -220,6 +222,20 @@ where where I: DbConnection + Send + 'a; + fn update_entity<'a, T>( + entity: &'a T, + ) -> impl Future>> + where + T: RowMapper + Inspectionable + Sync + 'a; + + fn update_entity_with<'a, T, I>( + entity: &'a T, + input: I, + ) -> impl Future>> + where + T: RowMapper + Inspectionable + Sync + 'a, + I: DbConnection + Send + 'a; + fn update_query<'a>() -> Result, Box<(dyn Error + Send + Sync + 'a)>>; fn update_query_with<'a>( diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index f47f894a..2349348f 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -11,12 +11,14 @@ use syn::{DeriveInput, Type, Visibility}; const BY_VALUE_CONVERSION_TARGETS: [&str; 1] = ["String"]; pub fn canyon_mapper_impl_tokens(ast: MacroTokens) -> TokenStream { - let ty = &ast.ty; + let mut row_mapper_tokens = TokenStream::new(); + + let ty = ast.ty; let ty_str = ty.to_string(); + let fields = ast.fields(); let (impl_generics, ty_generics, where_clause) = &ast.generics.split_for_impl(); - let mut impl_methods = TokenStream::new(); - let fields = ast.fields(); + let mut impl_methods = TokenStream::new(); #[cfg(feature = "postgres")] let pg_implementation = create_postgres_fields_mapping(&ty_str, &fields); @@ -51,41 +53,21 @@ pub fn canyon_mapper_impl_tokens(ast: MacroTokens) -> TokenStream { } }); - let fields_values = ast.get_fields_idents_pk_parsed().into_iter().map(|ident| { - quote! { &self.#ident } - }); - let fields_as_comma_sep_string = ast.get_struct_fields_as_comma_sep_string(); - let queries_placeholders = ast.placeholders_generator(); - let pk = match ast.get_primary_key_annotation() { - Some(primary_key) => quote! { Some(#primary_key) }, - None => quote! { None }, - }; - - quote! { + row_mapper_tokens.extend(quote! { use crate::canyon_sql::crud::CrudOperations; impl #impl_generics canyon_sql::core::RowMapper for #ty #ty_generics #where_clause { type Output = #ty; #impl_methods } + }); - impl #impl_generics canyon_sql::query::bounds::Inspectionable for #ty #ty_generics #where_clause { - fn fields_actual_values(&self) -> Vec<&dyn canyon_sql::query::QueryParameter<'_>> { - vec![#(#fields_values),*] - } - - fn fields_as_comma_sep_string(&self) -> &'static str { - #fields_as_comma_sep_string - } - - fn queries_placeholders(&self) -> &'static str { - #queries_placeholders - } + let inspectionable_impl_tokens = + __details::inspectionable_macro::generate_inspectionable_impl_tokens(&ast); + row_mapper_tokens.extend(quote! { + #inspectionable_impl_tokens + }); - fn primary_key(&self) -> Option<&'static str> { - #pk - } - } - } + row_mapper_tokens } #[cfg(feature = "postgres")] @@ -267,3 +249,63 @@ mod mapper_macro_tests { ); } } + +mod __details { + use super::*; + pub(crate) mod inspectionable_macro { + use super::*; + pub(crate) fn generate_inspectionable_impl_tokens(ast: &MacroTokens) -> TokenStream { + let ty = ast.ty; + let (impl_generics, ty_generics, where_clause) = &ast.generics.split_for_impl(); + + let fields = ast.get_fields_idents_pk_parsed().into_iter(); + let fields_values = fields.clone().map(|ident| { + quote! { &self.#ident } + }); + let fields_names = fields.map(|ident| ident.to_string()).collect::>(); + + let fields_as_comma_sep_string = ast.get_struct_fields_as_comma_sep_string(); + let queries_placeholders = ast.placeholders_generator(); + + let pk = match ast.get_primary_key_annotation() { + Some(primary_key) => quote! { Some(#primary_key) }, + None => quote! { None }, + }; + let pk_actual_value = match ast.get_primary_key_annotation() { + Some(primary_key) => { + let pk_ident = Ident::new(&primary_key, Span::call_site()); + quote! { &self.#pk_ident } + } + None => quote! { &-1 }, // TODO: yeah, big todo :) + }; + + quote! { + impl #impl_generics canyon_sql::query::bounds::Inspectionable for #ty #ty_generics #where_clause { + fn fields_actual_values(&self) -> Vec<&dyn canyon_sql::query::QueryParameter<'_>> { + vec![#(#fields_values),*] + } + + fn fields_names(&self) -> &[&'static str] { + &[#(#fields_names),*] + } + + fn fields_as_comma_sep_string(&self) -> &'static str { + #fields_as_comma_sep_string + } + + fn queries_placeholders(&self) -> &'static str { + #queries_placeholders + } + + fn primary_key(&self) -> Option<&'static str> { + #pk + } + + fn primary_key_actual_value(&self) -> &dyn canyon_sql::query::QueryParameter<'_> { + #pk_actual_value + } + } + } + } + } +} diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 8b86f699..bfccee92 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -92,7 +92,7 @@ pub fn generate_insert_entity_function_tokens(table_schema_data: &str) -> TokenS let placeholders = entity.queries_placeholders(); - let mut stmt = format!( + let mut stmt = format!( // TODO: use the InsertQueryBuilder when created ;) "INSERT INTO {} ({}) VALUES ({})", #table_schema_data, insert_columns, placeholders ); diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index afdf87c6..da9278ef 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -3,8 +3,19 @@ use crate::utils::macro_tokens::MacroTokens; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -/// Generates the TokenStream for the __update() CRUD operation -pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &String) -> TokenStream { +pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &str) -> TokenStream { + let update_method_ops = generate_update_method_tokens(macro_data, table_schema_data); + let update_entity_ops = generate_update_entity_tokens(table_schema_data); + let update_querybuilder_tokens = generate_update_querybuilder_tokens(table_schema_data); + + quote! { + #update_method_ops + #update_entity_ops + #update_querybuilder_tokens + } +} + +fn generate_update_method_tokens(macro_data: &MacroTokens, table_schema_data: &str) -> TokenStream { let mut update_ops_tokens = TokenStream::new(); let ty = macro_data.ty; @@ -14,7 +25,7 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri let mut vec_columns_values: Vec = Vec::new(); for (i, column_name) in update_columns.enumerate() { - let column_equal_value = format!("{} = ${}", column_name.to_owned(), i + 2); + let column_equal_value = format!("{} = ${}", column_name, i + 2); vec_columns_values.push(column_equal_value) } @@ -25,8 +36,6 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri }); let update_signature = quote! { - /// Updates a database record that matches the current instance of a T type, returning a - /// result indicating a possible failure querying the database. async fn update(&self) -> Result> }; let update_with_signature = quote! { @@ -74,15 +83,42 @@ pub fn generate_update_tokens(macro_data: &MacroTokens, table_schema_data: &Stri }); } - let querybuilder_update_tokens = generate_update_querybuilder_tokens(table_schema_data); - update_ops_tokens.extend(querybuilder_update_tokens); - update_ops_tokens } +fn generate_update_entity_tokens(table_schema_data: &str) -> TokenStream { + let update_entity_signature = quote! { + async fn update_entity<'canyon_lt, Entity>(entity: &'canyon_lt Entity) + -> Result<(), Box> + where Entity: canyon_sql::core::RowMapper + + canyon_sql::query::bounds::Inspectionable + + Sync + + 'canyon_lt + }; + + let update_entity_with_signature = quote! { + async fn update_entity_with<'canyon_lt, Entity, Input>(entity: &'canyon_lt Entity, input: Input) + -> Result<(), Box> + where + Entity: canyon_sql::core::RowMapper + + canyon_sql::query::bounds::Inspectionable + + Sync + + 'canyon_lt, + Input: canyon_sql::connection::DbConnection + Send + 'canyon_lt + }; + + let update_entity_body = __details::generate_update_entity_body(table_schema_data); + let update_entity_with_body = __details::generate_update_entity_with_body(table_schema_data); + + quote! { + #update_entity_signature { #update_entity_body } + #update_entity_with_signature { #update_entity_with_body } + } +} + /// Generates the TokenStream for the __update() CRUD operation /// being the query generated with the [`QueryBuilder`] -fn generate_update_querybuilder_tokens(table_schema_data: &String) -> TokenStream { +fn generate_update_querybuilder_tokens(table_schema_data: &str) -> TokenStream { quote! { /// Generates a [`canyon_sql::query::querybuilder::UpdateQueryBuilder`] /// that allows you to customize the query by adding parameters and constrains dynamically. @@ -117,6 +153,76 @@ fn generate_update_querybuilder_tokens(table_schema_data: &String) -> TokenStrea } } +mod __details { + use super::*; + + pub(crate) fn generate_update_entity_body(table_schema_data: &str) -> TokenStream { + let update_entity_core_logic = generate_update_entity_pk_body_logic(table_schema_data); + let no_pk_err = generate_no_pk_error(); + + quote! { + if let Some(primary_key) = entity.primary_key() { + #update_entity_core_logic + + let default_db_conn = canyon_sql::core::Canyon::instance()? + .get_default_connection()?; + let _ = default_db_conn.lock().await.execute(&stmt, &update_values).await?; + Ok(()) + } else { + #no_pk_err + } + } + } + + pub(crate) fn generate_update_entity_with_body(table_schema_data: &str) -> TokenStream { + let update_entity_core_logic = generate_update_entity_pk_body_logic(table_schema_data); + let no_pk_err = generate_no_pk_error(); + + quote! { + if let Some(primary_key) = entity.primary_key() { + #update_entity_core_logic + + let _ = input.execute(&stmt, &update_values).await?; + Ok(()) + } else { + #no_pk_err + } + } + } + + fn generate_update_entity_pk_body_logic(table_schema_data: &str) -> TokenStream { + quote! { + let pk_actual_value = entity.primary_key_actual_value(); + let update_columns = entity.fields_names(); + let update_values = entity.fields_actual_values(); + + let mut vec_columns_values: Vec = Vec::new(); + for (i, column_name) in update_columns.to_vec().iter().enumerate() { + let column_equal_value = format!("{} = ${}", column_name, i + 2); + vec_columns_values.push(column_equal_value) + } + let str_columns_values = vec_columns_values.join(", "); + + let stmt = format!( + "UPDATE {} SET {} WHERE {} = ${:?}", + #table_schema_data, str_columns_values, primary_key, pk_actual_value + ); + } + } + + pub(crate) fn generate_no_pk_error() -> TokenStream { + let err_msg = consts::UNAVAILABLE_CRUD_OP_ON_INSTANCE; + quote! { + return Err( + std::io::Error::new( + std::io::ErrorKind::Unsupported, + #err_msg + ).into_inner().unwrap() + ); + } + } +} + // // #[cfg(test)] // mod update_tokens_tests { From a676b3707eddb92375737944c8d1103fed496a85 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Sun, 25 May 2025 10:54:21 +0200 Subject: [PATCH 140/155] feat: delete entity implementations --- canyon_crud/src/crud.rs | 14 +++ canyon_macros/src/query_operations/consts.rs | 13 +++ canyon_macros/src/query_operations/delete.rs | 98 +++++++++++++++++++- 3 files changed, 121 insertions(+), 4 deletions(-) diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index d5647ae1..c9d15ef1 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -251,6 +251,20 @@ where where I: DbConnection + Send + 'a; + fn delete_entity<'a, T>( + entity: &'a T, + ) -> impl Future>> + where + T: RowMapper + Inspectionable + Sync + 'a; + + fn delete_entity_with<'a, T, I>( + entity: &'a T, + input: I, + ) -> impl Future>> + where + T: RowMapper + Inspectionable + Sync + 'a, + I: DbConnection + Send + 'a; + fn delete_query<'a>() -> Result, Box<(dyn Error + Send + Sync + 'a)>>; fn delete_query_with<'a>( diff --git a/canyon_macros/src/query_operations/consts.rs b/canyon_macros/src/query_operations/consts.rs index 38619bd7..791371b5 100644 --- a/canyon_macros/src/query_operations/consts.rs +++ b/canyon_macros/src/query_operations/consts.rs @@ -2,6 +2,7 @@ use std::cell::RefCell; +use crate::query_operations::consts; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{Ident, Type}; @@ -10,6 +11,18 @@ pub const UNAVAILABLE_CRUD_OP_ON_INSTANCE: &str = "Operation is unavailable. T d annotation. You must construct the query with the QueryBuilder type\ (_query method for the CrudOperations implementors"; +pub(crate) fn generate_no_pk_error() -> TokenStream { + let err_msg = consts::UNAVAILABLE_CRUD_OP_ON_INSTANCE; + quote! { + return Err( + std::io::Error::new( + std::io::ErrorKind::Unsupported, + #err_msg + ).into_inner().unwrap() + ); + } +} + thread_local! { pub static USER_MOCK_TY: RefCell = RefCell::new(Ident::new("User", Span::call_site())); pub static USER_MOCK_MAPPER_TY: RefCell = RefCell::new(Ident::new("User", Span::call_site())); diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index 7f2550ca..a8e6d8d3 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -2,9 +2,24 @@ use crate::utils::macro_tokens::MacroTokens; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; +pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &str) -> TokenStream { + let delete_method_ops = generate_delete_method_tokens(macro_data, table_schema_data); + let delete_entity_ops = generate_delete_entity_tokens(table_schema_data); + let delete_querybuilder_tokens = generate_delete_querybuilder_tokens(table_schema_data); + + quote! { + #delete_method_ops + #delete_entity_ops + #delete_querybuilder_tokens + } +} + /// Generates the TokenStream for the __delete() CRUD operation /// returning a result, indicating a possible failure querying the database -pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &String) -> TokenStream { +pub fn generate_delete_method_tokens( + macro_data: &MacroTokens, + table_schema_data: &str, +) -> TokenStream { let mut delete_ops_tokens = TokenStream::new(); let ty = macro_data.ty; @@ -63,12 +78,39 @@ pub fn generate_delete_tokens(macro_data: &MacroTokens, table_schema_data: &Stri }); } - let delete_with_querybuilder = generate_delete_querybuilder_tokens(table_schema_data); - delete_ops_tokens.extend(delete_with_querybuilder); - delete_ops_tokens } +pub fn generate_delete_entity_tokens(table_schema_data: &str) -> TokenStream { + let delete_entity_signature = quote! { + async fn delete_entity<'canyon_lt, Entity>(entity: &'canyon_lt Entity) + -> Result<(), Box> + where Entity: canyon_sql::core::RowMapper + + canyon_sql::query::bounds::Inspectionable + + Sync + + 'canyon_lt + }; + + let delete_entity_with_signature = quote! { + async fn delete_entity_with<'canyon_lt, Entity, Input>(entity: &'canyon_lt Entity, input: Input) + -> Result<(), Box> + where + Entity: canyon_sql::core::RowMapper + + canyon_sql::query::bounds::Inspectionable + + Sync + + 'canyon_lt, + Input: canyon_sql::connection::DbConnection + Send + 'canyon_lt + }; + + let delete_entity_body = __details::generate_delete_entity_body(table_schema_data); + let delete_entity_with_body = __details::generate_delete_entity_with_body(table_schema_data); + + quote! { + #delete_entity_signature { #delete_entity_body } + #delete_entity_with_signature { #delete_entity_with_body } + } +} + /// Generates the TokenStream for the __delete() CRUD operation as a /// [`query_elements::query_builder::QueryBuilder<'a, #ty>`] fn generate_delete_querybuilder_tokens(table_schema_data: &str) -> TokenStream { @@ -106,6 +148,54 @@ fn generate_delete_querybuilder_tokens(table_schema_data: &str) -> TokenStream { } } } + +mod __details { + use super::*; + use crate::query_operations::consts; + + pub(crate) fn generate_delete_entity_body(table_schema_data: &str) -> TokenStream { + let delete_entity_core_logic = generate_delete_entity_pk_body_logic(table_schema_data); + let no_pk_err = consts::generate_no_pk_error(); + + quote! { + if let Some(primary_key) = entity.primary_key() { + #delete_entity_core_logic + let default_db_conn = canyon_sql::core::Canyon::instance()? + .get_default_connection()?; + let _ = default_db_conn.lock().await.execute(&delete_stmt, &[pk_actual_value]).await?; + Ok(()) + } else { + #no_pk_err + } + } + } + + pub(crate) fn generate_delete_entity_with_body(table_schema_data: &str) -> TokenStream { + let delete_entity_core_logic = generate_delete_entity_pk_body_logic(table_schema_data); + let no_pk_err = consts::generate_no_pk_error(); + + quote! { + if let Some(primary_key) = entity.primary_key() { + #delete_entity_core_logic + let _ = input.execute(&delete_stmt, &[pk_actual_value]).await?; + Ok(()) + } else { + #no_pk_err + } + } + } + + fn generate_delete_entity_pk_body_logic(table_schema_data: &str) -> TokenStream { + quote! { + let pk_actual_value = entity.primary_key_actual_value(); + let delete_stmt = format!( + "DELETE FROM {} WHERE {:?} = $1", + #table_schema_data, primary_key + ); + } + } +} + // // // NOTE: The delete operations shouldn't be using TransactionMethod::QueryRows // // This should be refactored on the future From 9535e9e7847458446cebc80ebe5640052f949b59 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 28 May 2025 08:48:45 +0200 Subject: [PATCH 141/155] feat(wip)!: setting the value returned of the pk on the insert entity operations, but lifetime problems arises --- canyon_core/src/query/bounds.rs | 8 +- canyon_core/src/query/parameters.rs | 312 ++++++++---------- canyon_crud/src/crud.rs | 28 +- canyon_macros/src/canyon_mapper_macro.rs | 45 ++- canyon_macros/src/query_operations/consts.rs | 11 +- canyon_macros/src/query_operations/delete.rs | 5 +- canyon_macros/src/query_operations/insert.rs | 41 ++- canyon_macros/src/query_operations/read.rs | 98 +++--- canyon_macros/src/query_operations/update.rs | 6 +- canyon_macros/src/utils/macro_tokens.rs | 38 ++- canyon_macros/src/utils/mod.rs | 1 + .../src/utils/primary_key_attribute.rs | 37 +++ tests/crud/hex_arch_example.rs | 110 ++++-- 13 files changed, 424 insertions(+), 316 deletions(-) create mode 100644 canyon_macros/src/utils/primary_key_attribute.rs diff --git a/canyon_core/src/query/bounds.rs b/canyon_core/src/query/bounds.rs index d2c9def0..d1f02d04 100644 --- a/canyon_core/src/query/bounds.rs +++ b/canyon_core/src/query/bounds.rs @@ -5,7 +5,8 @@ use crate::query::parameters::QueryParameter; /// Typically, these will be used by the macros to gather some information or to create some user code /// in more complex scenarios, like when insert an entity, when we need to know the value of the fields of /// the current instance that we'd like to insert -pub trait Inspectionable { +pub trait Inspectionable<'a> { + /// Returns an allocated linear collection with the current values of all the fields declared /// for the implementor, as the result of the evaluation of the &self.#field expression, iteratively /// over every type member, but if the type contains in some field the #[primary_key] annotation, @@ -26,7 +27,10 @@ pub trait Inspectionable { fn queries_placeholders(&self) -> &'static str; fn primary_key(&self) -> Option<&'static str>; - fn primary_key_actual_value(&self) -> &dyn QueryParameter<'_>; + fn primary_key_st() -> Option<&'static str>; + fn primary_key_actual_value(&self) -> &'a (dyn QueryParameter<'_> + 'a); + // fn set_primary_key_actual_value(&mut self, value: Self::PrimaryKeyType); + fn set_primary_key_actual_value(&mut self, value: &'a (dyn QueryParameter<'a> + 'static)) -> Result<(), Box>; } pub trait TableMetadata: std::fmt::Display { diff --git a/canyon_core/src/query/parameters.rs b/canyon_core/src/query/parameters.rs index 16b479da..5d72b1e0 100644 --- a/canyon_core/src/query/parameters.rs +++ b/canyon_core/src/query/parameters.rs @@ -1,3 +1,4 @@ +use std::any::Any; #[cfg(feature = "mysql")] use mysql_async::{self, prelude::ToValue}; #[cfg(feature = "mssql")] @@ -8,9 +9,25 @@ use tokio_postgres::{self, types::ToSql}; // TODO: cfg feature for this re-exports, as date-time or something use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Utc}; +pub trait QueryParameterValue<'a> { + fn downcast_ref(&'a self) -> Option<&'a T>; +} +impl<'a> QueryParameterValue<'a> for dyn QueryParameter<'a> { + fn downcast_ref(&'a self) -> Option<&'a T> { + self.as_any().downcast_ref() + } +} +impl<'a> QueryParameterValue<'a> for &'a dyn QueryParameter<'a> { + fn downcast_ref(&'a self) -> Option<&'a T> { + self.as_any().downcast_ref() + } +} + /// Defines a trait for represent type bounds against the allowed /// data types supported by Canyon to be used as query parameters. pub trait QueryParameter<'a>: std::fmt::Debug + Send + Sync { + fn as_any(&'a self) -> &'a dyn Any; + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync); #[cfg(feature = "mssql")] @@ -28,15 +45,19 @@ pub trait QueryParameter<'a>: std::fmt::Debug + Send + Sync { /// that is not dependent of the specific type of the argument that holds /// the query parameters of the database connectors #[cfg(feature = "mssql")] -impl<'a> IntoSql<'a> for &'a dyn QueryParameter<'a> { - fn into_sql(self) -> ColumnData<'a> { +impl<'b> IntoSql<'b> for &'b dyn QueryParameter<'b> { + fn into_sql(self) -> ColumnData<'b> { self.as_sqlserver_param() } } //TODO Pending to review and see if it is necessary to apply something similar to the previous implementation. -impl QueryParameter<'_> for bool { +impl<'a> QueryParameter<'a> for bool { + fn as_any(&'a self) -> &'a dyn Any { + self + } + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -52,51 +73,29 @@ impl QueryParameter<'_> for bool { } impl QueryParameter<'_> for i16 { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + fn as_any(&'_ self) -> &'_ dyn Any { self } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::I16(Some(*self)) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl QueryParameter<'_> for &i16 { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self } #[cfg(feature = "mssql")] fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::I16(Some(**self)) + ColumnData::I16(Option::from(*self)) } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { self } } -impl QueryParameter<'_> for Option { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { +impl<'a> QueryParameter<'a> for Option<&'static i16> { + fn as_any(&'a self) -> &'a dyn Any { self } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::I16(*self) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} - -impl QueryParameter<'_> for Option<&i16> { + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -106,12 +105,16 @@ impl QueryParameter<'_> for Option<&i16> { ColumnData::I16(Some(*self.unwrap())) } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { self } } impl QueryParameter<'_> for i32 { + fn as_any(&self) -> &dyn Any { + self + } + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -121,27 +124,16 @@ impl QueryParameter<'_> for i32 { ColumnData::I32(Some(*self)) } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { self } } -impl QueryParameter<'_> for &i32 { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { +impl<'a> QueryParameter<'a> for Option { + fn as_any(&'a self) -> &'a dyn Any { self } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::I32(Some(**self)) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl QueryParameter<'_> for Option { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -151,27 +143,16 @@ impl QueryParameter<'_> for Option { ColumnData::I32(*self) } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { self } } -impl QueryParameter<'_> for Option<&i32> { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::I32(Some(*self.unwrap())) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { +impl QueryParameter<'_> for f32 { + fn as_any(&self) -> &dyn Any { self } -} -impl QueryParameter<'_> for f32 { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -181,27 +162,17 @@ impl QueryParameter<'_> for f32 { ColumnData::F32(Some(*self)) } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { self } } -impl QueryParameter<'_> for &f32 { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::F32(Some(**self)) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + +impl<'a> QueryParameter<'a> for Option { + fn as_any(&self) -> &dyn Any { self } -} -impl QueryParameter<'_> for Option { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -211,29 +182,17 @@ impl QueryParameter<'_> for Option { ColumnData::F32(*self) } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { self } } -impl QueryParameter<'_> for Option<&f32> { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::F32(Some( - *self.expect("Error on an f32 value on QueryParameter<'_>"), - )) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn ToValue { + +impl<'a> QueryParameter<'a> for f64 { + fn as_any(&self) -> &dyn Any { self } -} -impl QueryParameter<'_> for f64 { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -243,27 +202,17 @@ impl QueryParameter<'_> for f64 { ColumnData::F64(Some(*self)) } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { self } } -impl QueryParameter<'_> for &f64 { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::F64(Some(**self)) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + +impl<'a> QueryParameter<'a> for Option { + fn as_any(&self) -> &dyn Any { self } -} -impl QueryParameter<'_> for Option { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -273,29 +222,16 @@ impl QueryParameter<'_> for Option { ColumnData::F64(*self) } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { self } } -impl QueryParameter<'_> for Option<&f64> { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::F64(Some( - *self.expect("Error on an f64 value on QueryParameter<'_>"), - )) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { +impl<'a> QueryParameter<'a> for i64 { + fn as_any(&self) -> &dyn Any { self } -} -impl QueryParameter<'_> for i64 { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -305,27 +241,16 @@ impl QueryParameter<'_> for i64 { ColumnData::I64(Some(*self)) } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { self } } -impl QueryParameter<'_> for &i64 { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { +impl<'a> QueryParameter<'a> for Option { + fn as_any(&self) -> &dyn Any { self } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::I64(Some(**self)) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl QueryParameter<'_> for Option { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -335,27 +260,16 @@ impl QueryParameter<'_> for Option { ColumnData::I64(*self) } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { self } } -impl QueryParameter<'_> for Option<&i64> { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { +impl<'a> QueryParameter<'a> for String { + fn as_any(&self) -> &dyn Any { self } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::I64(Some(*self.unwrap())) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { - self - } -} -impl QueryParameter<'_> for String { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -365,27 +279,16 @@ impl QueryParameter<'_> for String { ColumnData::String(Some(std::borrow::Cow::Owned(self.to_owned()))) } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { self } } -impl QueryParameter<'_> for &String { - #[cfg(feature = "postgres")] - fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { - self - } - #[cfg(feature = "mssql")] - fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::String(Some(std::borrow::Cow::Borrowed(self))) - } - #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { +impl<'a> QueryParameter<'a> for Option { + fn as_any(&self) -> &dyn Any { self } -} -impl QueryParameter<'_> for Option { #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -398,12 +301,16 @@ impl QueryParameter<'_> for Option { } } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { self } } -impl QueryParameter<'_> for Option<&String> { +impl<'a> QueryParameter<'a> for Option<&'static String> { + fn as_any(&'a self) -> &'a dyn Any { + self + } + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -416,27 +323,36 @@ impl QueryParameter<'_> for Option<&String> { } } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { self } } -impl QueryParameter<'_> for &'_ str { +impl QueryParameter<'_> for &'static str { + fn as_any(& self) -> &dyn Any { + self + } + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self } #[cfg(feature = "mssql")] fn as_sqlserver_param(&self) -> ColumnData<'_> { - ColumnData::String(Some(std::borrow::Cow::Borrowed(*self))) + ColumnData::String(Some(std::borrow::Cow::Borrowed(self))) } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { self } } -impl QueryParameter<'_> for Option<&'_ str> { + +impl QueryParameter<'_> for Option<&'static str> { + fn as_any(&'_ self) -> &'_ dyn Any { + self + } + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -455,6 +371,10 @@ impl QueryParameter<'_> for Option<&'_ str> { } impl QueryParameter<'_> for NaiveDate { + fn as_any(&'_ self) -> &'_ dyn Any { + self + } + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -464,12 +384,16 @@ impl QueryParameter<'_> for NaiveDate { self.into_sql() } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { self } } impl QueryParameter<'_> for Option { + fn as_any(&'_ self) -> &'_ dyn Any { + self + } + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -479,12 +403,16 @@ impl QueryParameter<'_> for Option { self.into_sql() } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { self } } impl QueryParameter<'_> for NaiveTime { + fn as_any(&'_ self) -> &'_ dyn Any { + self + } + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -494,12 +422,16 @@ impl QueryParameter<'_> for NaiveTime { self.into_sql() } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { self } } impl QueryParameter<'_> for Option { + fn as_any(&'_ self) -> &'_ dyn Any { + self + } + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -509,12 +441,16 @@ impl QueryParameter<'_> for Option { self.into_sql() } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { self } } impl QueryParameter<'_> for NaiveDateTime { + fn as_any(&'_ self) -> &'_ dyn Any { + self + } + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -530,6 +466,10 @@ impl QueryParameter<'_> for NaiveDateTime { } impl QueryParameter<'_> for Option { + fn as_any(&'_ self) -> &'_ dyn Any { + self + } + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -539,13 +479,17 @@ impl QueryParameter<'_> for Option { self.into_sql() } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { self } } //TODO pending impl QueryParameter<'_> for DateTime { + fn as_any(&'_ self) -> &'_ dyn Any { + self + } + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -555,12 +499,16 @@ impl QueryParameter<'_> for DateTime { self.into_sql() } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { todo!() } } impl QueryParameter<'_> for Option> { + fn as_any(&'_ self) -> &'_ dyn Any { + self + } + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -570,12 +518,16 @@ impl QueryParameter<'_> for Option> { self.into_sql() } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { todo!() } } impl QueryParameter<'_> for DateTime { + fn as_any(&'_ self) -> &'_ dyn Any { + self + } + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -585,12 +537,16 @@ impl QueryParameter<'_> for DateTime { self.into_sql() } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { todo!() } } impl QueryParameter<'_> for Option> { + fn as_any(&'_ self) -> &'_ dyn Any { + self + } + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -600,7 +556,7 @@ impl QueryParameter<'_> for Option> { self.into_sql() } #[cfg(feature = "mysql")] - fn as_mysql_param(&self) -> &dyn mysql_async::prelude::ToValue { + fn as_mysql_param(&self) -> &dyn ToValue { todo!() } } diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index c9d15ef1..e2382af7 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -9,20 +9,6 @@ use canyon_core::query::querybuilder::{ use std::error::Error; use std::future::Future; -pub trait Insertable { - // Logically, this looks more like Inserter, and Insertable w'd be the ones with &self - fn insert_entity<'a, T>( - entity: &'a T, - ) -> impl Future>> + Send - where - T: RowMapper + Inspectionable; - // fn insert_entity_with<'a, I>( - // &mut self, - // input: I, - // ) -> impl Future>> + Send - // where - // I: DbConnection + Send + 'a; -} /// *CrudOperations* it's the core part of Canyon-SQL. /// @@ -97,7 +83,7 @@ where /// /// # Examples /// - /// ```rust + /// ```ignore /// let mut lec = League { /// id: Default::default(), /// ext_id: 1, @@ -189,14 +175,14 @@ where entity: &'a mut T, ) -> impl Future>> where - T: RowMapper + Inspectionable + Sync + 'a; + T: RowMapper + Inspectionable<'a> + Sync + 'a; fn insert_entity_with<'a, T, I>( entity: &'a mut T, input: I, ) -> impl Future>> where - T: RowMapper + Inspectionable + Sync + 'a, + T: RowMapper + Inspectionable<'a> + Sync + 'a, I: DbConnection + Send + 'a; // TODO: the horripilant multi_insert MUST be replaced with a batch insert @@ -226,14 +212,14 @@ where entity: &'a T, ) -> impl Future>> where - T: RowMapper + Inspectionable + Sync + 'a; + T: RowMapper + Inspectionable<'a> + Sync + 'a; fn update_entity_with<'a, T, I>( entity: &'a T, input: I, ) -> impl Future>> where - T: RowMapper + Inspectionable + Sync + 'a, + T: RowMapper + Inspectionable<'a> + Sync + 'a, I: DbConnection + Send + 'a; fn update_query<'a>() -> Result, Box<(dyn Error + Send + Sync + 'a)>>; @@ -255,14 +241,14 @@ where entity: &'a T, ) -> impl Future>> where - T: RowMapper + Inspectionable + Sync + 'a; + T: RowMapper + Inspectionable<'a> + Sync + 'a; fn delete_entity_with<'a, T, I>( entity: &'a T, input: I, ) -> impl Future>> where - T: RowMapper + Inspectionable + Sync + 'a, + T: RowMapper + Inspectionable<'a> + Sync + 'a, I: DbConnection + Send + 'a; fn delete_query<'a>() -> Result, Box<(dyn Error + Send + Sync + 'a)>>; diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index 2349348f..1f7704f0 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -256,31 +256,48 @@ mod __details { use super::*; pub(crate) fn generate_inspectionable_impl_tokens(ast: &MacroTokens) -> TokenStream { let ty = ast.ty; + let pk = ast.get_primary_key_field_annotation(); + let pk_ident_ts= pk.map(|pk| pk.ident); + let pk_ty_ts = pk.map(|pk| pk.ty); let (impl_generics, ty_generics, where_clause) = &ast.generics.split_for_impl(); - let fields = ast.get_fields_idents_pk_parsed().into_iter(); - let fields_values = fields.clone().map(|ident| { + let fields = ast.get_fields_idents_pk_parsed().collect::>(); + let fields_values = fields.iter().map(|ident| { quote! { &self.#ident } }); - let fields_names = fields.map(|ident| ident.to_string()).collect::>(); + let fields_names = fields.iter().map(|ident| ident.to_string()).collect::>(); let fields_as_comma_sep_string = ast.get_struct_fields_as_comma_sep_string(); let queries_placeholders = ast.placeholders_generator(); - let pk = match ast.get_primary_key_annotation() { + let pk_opt_val = match ast.get_primary_key_annotation() { Some(primary_key) => quote! { Some(#primary_key) }, None => quote! { None }, }; let pk_actual_value = match ast.get_primary_key_annotation() { Some(primary_key) => { let pk_ident = Ident::new(&primary_key, Span::call_site()); - quote! { &self.#pk_ident } + quote! { self.#pk_ident } } - None => quote! { &-1 }, // TODO: yeah, big todo :) + None => quote! { -1 }, // TODO: yeah, big todo :) }; + let set_pk_val_method = if let Some(pk_ident) = pk_ident_ts { + quote! { + use canyon_sql::query::parameters::QueryParameterValue; + self.#pk_ident = value.downcast_ref::() + .ok_or_else(|| "Error downcasting the pk value passed")? + .clone(); + Ok(()) + } + } else { + quote! { Ok(()) /* TODO: with err */ } + }; + println!("Seeing set pk method for ty: {:?}: {:?}", ty, set_pk_val_method.to_string()); + quote! { - impl #impl_generics canyon_sql::query::bounds::Inspectionable for #ty #ty_generics #where_clause { + impl<'a> canyon_sql::query::bounds::Inspectionable<'a> for #ty #ty_generics #where_clause { + fn fields_actual_values(&self) -> Vec<&dyn canyon_sql::query::QueryParameter<'_>> { vec![#(#fields_values),*] } @@ -298,11 +315,19 @@ mod __details { } fn primary_key(&self) -> Option<&'static str> { - #pk + #pk_opt_val + } + + fn primary_key_st() -> Option<&'static str> { + #pk_opt_val + } + + fn primary_key_actual_value(&self) -> &'a (dyn canyon_sql::query::QueryParameter + 'a) { + &#pk_actual_value } - fn primary_key_actual_value(&self) -> &dyn canyon_sql::query::QueryParameter<'_> { - #pk_actual_value + fn set_primary_key_actual_value(&mut self, value: &'a (dyn canyon_sql::query::QueryParameter<'a> + 'static)) -> Result<(), Box> { + #set_pk_val_method } } } diff --git a/canyon_macros/src/query_operations/consts.rs b/canyon_macros/src/query_operations/consts.rs index 791371b5..d4235178 100644 --- a/canyon_macros/src/query_operations/consts.rs +++ b/canyon_macros/src/query_operations/consts.rs @@ -2,7 +2,6 @@ use std::cell::RefCell; -use crate::query_operations::consts; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{Ident, Type}; @@ -12,7 +11,7 @@ pub const UNAVAILABLE_CRUD_OP_ON_INSTANCE: &str = "Operation is unavailable. T d (_query method for the CrudOperations implementors"; pub(crate) fn generate_no_pk_error() -> TokenStream { - let err_msg = consts::UNAVAILABLE_CRUD_OP_ON_INSTANCE; + let err_msg = UNAVAILABLE_CRUD_OP_ON_INSTANCE; quote! { return Err( std::io::Error::new( @@ -23,6 +22,14 @@ pub(crate) fn generate_no_pk_error() -> TokenStream { } } +pub(crate) fn generate_default_db_conn_tokens() -> TokenStream { + quote! { + let default_db_conn = canyon_sql::core::Canyon::instance()? + .get_default_connection()?; + default_db_conn.lock().await + } +} + thread_local! { pub static USER_MOCK_TY: RefCell = RefCell::new(Ident::new("User", Span::call_site())); pub static USER_MOCK_MAPPER_TY: RefCell = RefCell::new(Ident::new("User", Span::call_site())); diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index a8e6d8d3..76161492 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -86,7 +86,7 @@ pub fn generate_delete_entity_tokens(table_schema_data: &str) -> TokenStream { async fn delete_entity<'canyon_lt, Entity>(entity: &'canyon_lt Entity) -> Result<(), Box> where Entity: canyon_sql::core::RowMapper - + canyon_sql::query::bounds::Inspectionable + + canyon_sql::query::bounds::Inspectionable<'canyon_lt> + Sync + 'canyon_lt }; @@ -96,7 +96,7 @@ pub fn generate_delete_entity_tokens(table_schema_data: &str) -> TokenStream { -> Result<(), Box> where Entity: canyon_sql::core::RowMapper - + canyon_sql::query::bounds::Inspectionable + + canyon_sql::query::bounds::Inspectionable<'canyon_lt> + Sync + 'canyon_lt, Input: canyon_sql::connection::DbConnection + Send + 'canyon_lt @@ -187,6 +187,7 @@ mod __details { fn generate_delete_entity_pk_body_logic(table_schema_data: &str) -> TokenStream { quote! { + // let pk_actual_value = &entity.primary_key_actual_value() as &dyn canyon_sql::query::QueryParameter<'canyon_lt>; let pk_actual_value = entity.primary_key_actual_value(); let delete_stmt = format!( "DELETE FROM {} WHERE {:?} = $1", diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index bfccee92..07226637 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -65,7 +65,7 @@ pub fn generate_insert_entity_function_tokens(table_schema_data: &str) -> TokenS async fn insert_entity<'canyon_lt, Entity>(entity: &'canyon_lt mut Entity) -> Result<(), Box> where Entity: canyon_sql::core::RowMapper - + canyon_sql::query::bounds::Inspectionable + + canyon_sql::query::bounds::Inspectionable<'canyon_lt> + Sync + 'canyon_lt }; @@ -75,7 +75,7 @@ pub fn generate_insert_entity_function_tokens(table_schema_data: &str) -> TokenS -> Result<(), Box> where Entity: canyon_sql::core::RowMapper - + canyon_sql::query::bounds::Inspectionable + + canyon_sql::query::bounds::Inspectionable<'canyon_lt> + Sync + 'canyon_lt, Input: canyon_sql::connection::DbConnection + Send + 'canyon_lt @@ -89,34 +89,43 @@ pub fn generate_insert_entity_function_tokens(table_schema_data: &str) -> TokenS if insert_columns.is_empty() { return #no_fields_to_insert_err; } - + let values = entity.fields_actual_values(); let placeholders = entity.queries_placeholders(); let mut stmt = format!( // TODO: use the InsertQueryBuilder when created ;) "INSERT INTO {} ({}) VALUES ({})", #table_schema_data, insert_columns, placeholders ); - - if let Some(primary_key) = entity.primary_key() { - stmt.push_str(" RETURNING {}"); - stmt.push_str(primary_key); - } + }; + let add_returning_clause = quote! { + stmt.push_str(" RETURNING "); + stmt.push_str(pk); }; quote! { #insert_entity_signature { - #stmt_ctr; - let values = entity.fields_actual_values(); let default_db_conn = canyon_sql::core::Canyon::instance()? .get_default_connection()?; - let _ = default_db_conn.lock().await.execute(&stmt, &values).await?; // Should we remove the pk? Or even look for the pk? + #stmt_ctr; + + if let Some(pk) = entity.primary_key() { + #add_returning_clause + let r = default_db_conn.lock().await.execute(&stmt, &values).await? as i64; + // entity.set_primary_key_actual_value(&r); + } else { + let _ = default_db_conn.lock().await.execute(&stmt, &values).await?; + } + // println!("Insert query {:?}", &stmt); Ok(()) } #insert_entity_with_signature { #stmt_ctr; - let values = entity.fields_actual_values(); - let _ = input.execute(&stmt, &values).await?; + if let Some(pk) = entity.primary_key() { + #add_returning_clause + } else { + let _ = input.execute(&stmt, &values).await?; + } Ok(()) } } @@ -130,11 +139,7 @@ mod __details { stmt: &str, is_with_method: bool, ) -> TokenStream { - let primary_key = macro_data.get_primary_key_annotation(); - let pk_ident_and_type = macro_data - .fields_with_types() - .into_iter() - .find(|(i, _t)| Some(i.to_string()) == primary_key); + let pk_ident_and_type = macro_data.get_primary_key_ident_and_type(); let db_conn = if is_with_method { quote! { input } diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 441d0c63..7db05079 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -47,7 +47,7 @@ fn generate_find_all_operations_tokens( } } -fn generate_select_querybuilder_tokens(table_schema_data: &String) -> TokenStream { +fn generate_select_querybuilder_tokens(table_schema_data: &str) -> TokenStream { quote! { /// Generates a [`canyon_sql::query::querybuilder::SelectQueryBuilder`] /// that allows you to customize the query by adding parameters and constrains dynamically. @@ -82,7 +82,7 @@ fn generate_select_querybuilder_tokens(table_schema_data: &String) -> TokenStrea } } -fn generate_count_operations_tokens(table_schema_data: &String) -> TokenStream { +fn generate_count_operations_tokens(table_schema_data: &str) -> TokenStream { let count_stmt = format!("SELECT COUNT(*) FROM {table_schema_data}"); let count = __details::count_generators::create_count_macro(&count_stmt); let count_with = __details::count_generators::create_count_with_macro(&count_stmt); @@ -95,42 +95,41 @@ fn generate_count_operations_tokens(table_schema_data: &String) -> TokenStream { fn generate_find_by_pk_operations_tokens( macro_data: &MacroTokens<'_>, - table_schema_data: &String, + table_schema_data: &str, ) -> TokenStream { let ty = macro_data.ty; - let mapper_ty = macro_data - .retrieve_mapping_target_type() - .as_ref() - .unwrap_or(ty); + let mapper_ty = macro_data.retrieve_mapping_target_type().as_ref(); let pk = macro_data.get_primary_key_annotation(); - let no_pk_runtime_err = if pk.is_some() { - None - } else { - let err_msg = consts::FIND_BY_PK_ERR_NO_PK; + + let base_body = if let Some(compile_time_known_pk) = pk { Some(quote! { - Err( - std::io::Error::new( - std::io::ErrorKind::Unsupported, - #err_msg, - ).into_inner().unwrap() - ) + let stmt = format!( + "SELECT * FROM {} WHERE {} = $1", + #table_schema_data, #compile_time_known_pk + ); }) + } else { + let is_with_mapper_ty = mapper_ty.is_some(); + if is_with_mapper_ty { + Some(quote! { + use canyon_sql::query::bounds::Inspectionable; + let stmt = format!( + "SELECT * FROM {} WHERE {} = $1", + #table_schema_data, + <#mapper_ty as Inspectionable>::primary_key_st() + .ok_or_else(|| "No primary key found for this instance")? // TODO: better fmtted error msg + ); + }) + } else { + None + } }; - let stmt = format!( - "SELECT * FROM {table_schema_data} WHERE {} = $1", - pk.unwrap_or_default() - ); - - let find_by_pk = __details::find_by_pk_generators::create_find_by_pk_macro( - mapper_ty, - &stmt, - &no_pk_runtime_err, - ); - let find_by_pk_with = __details::find_by_pk_generators::create_find_by_pk_with( - mapper_ty, - &stmt, - &no_pk_runtime_err, - ); + + let mapper_ty = mapper_ty.unwrap_or(ty); + let find_by_pk = + __details::find_by_pk_generators::create_find_by_pk_macro(mapper_ty, &base_body); + let find_by_pk_with = + __details::find_by_pk_generators::create_find_by_pk_with(mapper_ty, &base_body); quote! { #find_by_pk @@ -196,21 +195,24 @@ mod __details { pub mod find_by_pk_generators { use super::*; + use crate::query_operations::consts; use proc_macro2::TokenStream; pub fn create_find_by_pk_macro( mapper_ty: &Ident, - stmt: &str, - pk_runtime_error: &Option, + base_body: &Option, ) -> TokenStream { - let body = if pk_runtime_error.is_none() { + let body = if let Some(body) = base_body { + let default_db_conn_call = consts::generate_default_db_conn_tokens(); quote! { - let default_db_conn = canyon_sql::core::Canyon::instance()? - .get_default_connection()?; - default_db_conn.lock().await.query_one::<#mapper_ty>(#stmt, &[value]).await + #body; + #default_db_conn_call + .query_one::<#mapper_ty>(&stmt, &[value]) + .await } } else { - quote! { #pk_runtime_error } + let unsupported_op_err = consts::generate_no_pk_error(); + quote! { #unsupported_op_err } }; quote! { @@ -224,15 +226,16 @@ mod __details { pub fn create_find_by_pk_with( mapper_ty: &Ident, - stmt: &str, - pk_runtime_error: &Option, + base_body: &Option, ) -> TokenStream { - let body = if pk_runtime_error.is_none() { + let body = if let Some(body) = base_body { quote! { - input.query_one::<#mapper_ty>(#stmt, &[value]).await + #body; + input.query_one::<#mapper_ty>(&stmt, &[value]).await } } else { - quote! { #pk_runtime_error } + let unsupported_op_err = consts::generate_no_pk_error(); + quote! { #unsupported_op_err } }; quote! { @@ -288,7 +291,6 @@ mod macro_builder_read_ops_tests { #[test] fn test_create_count_macro() { - let ty = syn::parse_str::("User").unwrap(); let tokens = create_count_macro(COUNT_STMT); let generated = tokens.to_string(); @@ -312,8 +314,7 @@ mod macro_builder_read_ops_tests { #[test] fn test_create_find_by_pk_macro() { let mapper_ty = syn::parse_str::("User").unwrap(); - let pk_runtime_error = None; - let tokens = create_find_by_pk_macro(&mapper_ty, FIND_BY_PK_STMT, &pk_runtime_error); + let tokens = create_find_by_pk_macro(&mapper_ty, &None); let generated = tokens.to_string(); assert!(generated.contains("async fn find_by_pk")); @@ -324,8 +325,7 @@ mod macro_builder_read_ops_tests { #[test] fn test_create_find_by_pk_with_macro() { let mapper_ty = syn::parse_str::("User").unwrap(); - let pk_runtime_error = None; - let tokens = create_find_by_pk_with(&mapper_ty, FIND_BY_PK_STMT, &pk_runtime_error); + let tokens = create_find_by_pk_with(&mapper_ty, &None); let generated = tokens.to_string(); assert!(generated.contains("async fn find_by_pk_with")); diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index da9278ef..eb3eca34 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -91,7 +91,7 @@ fn generate_update_entity_tokens(table_schema_data: &str) -> TokenStream { async fn update_entity<'canyon_lt, Entity>(entity: &'canyon_lt Entity) -> Result<(), Box> where Entity: canyon_sql::core::RowMapper - + canyon_sql::query::bounds::Inspectionable + + canyon_sql::query::bounds::Inspectionable<'canyon_lt> + Sync + 'canyon_lt }; @@ -101,7 +101,7 @@ fn generate_update_entity_tokens(table_schema_data: &str) -> TokenStream { -> Result<(), Box> where Entity: canyon_sql::core::RowMapper - + canyon_sql::query::bounds::Inspectionable + + canyon_sql::query::bounds::Inspectionable<'canyon_lt> + Sync + 'canyon_lt, Input: canyon_sql::connection::DbConnection + Send + 'canyon_lt @@ -192,7 +192,7 @@ mod __details { fn generate_update_entity_pk_body_logic(table_schema_data: &str) -> TokenStream { quote! { - let pk_actual_value = entity.primary_key_actual_value(); + let pk_actual_value = &entity.primary_key_actual_value(); let update_columns = entity.fields_names(); let update_values = entity.fields_actual_values(); diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index 75a2a80e..068d575c 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -4,6 +4,7 @@ use canyon_entities::field_annotation::EntityFieldAnnotation; use proc_macro2::{Ident, Span}; use std::convert::TryFrom; use syn::{Attribute, DeriveInput, Field, Fields, Generics, Type, Visibility}; +use crate::utils::primary_key_attribute::PrimaryKeyAttribute; /// Provides a convenient way of store the data for the TokenStream /// received on a macro @@ -15,7 +16,8 @@ pub struct MacroTokens<'a> { pub attrs: &'a Vec, pub fields: &'a Fields, // -------- the new fields that must help to avoid recalculations every time that the user compiles - pub(crate) canyon_crud_attribute: Option, + pub(crate) canyon_crud_attribute: Option, // Type level + pub(crate) primary_key_attribute: Option>, // Field level, quick access without iterations } impl<'a> MacroTokens<'a> { @@ -23,6 +25,10 @@ impl<'a> MacroTokens<'a> { // TODO: impl syn::parse instead if let syn::Data::Struct(ref s) = ast.data { let attrs = &ast.attrs; + + let primary_key_attribute = Self::find_primary_key_field_annotation(&s.fields) + .map(|f| PrimaryKeyAttribute { ident: f.ident.as_ref().unwrap(), ty: &f.ty, name: f.ident.as_ref().unwrap().to_string() }); + let mut canyon_crud_attribute = None; for attr in attrs { if attr.path.is_ident("canyon_crud") { @@ -37,6 +43,7 @@ impl<'a> MacroTokens<'a> { attrs: &ast.attrs, fields: &s.fields, canyon_crud_attribute, + primary_key_attribute }) } else { Err(syn::Error::new( @@ -108,10 +115,9 @@ impl<'a> MacroTokens<'a> { /// Returns a collection with all the [`syn::Ident`] for all the type members, skipping (if present) /// the field which is annotated with #[primary_key] - pub fn get_fields_idents_pk_parsed(&self) -> Vec<&Ident> { + pub fn get_fields_idents_pk_parsed(&self) -> impl Iterator { self.get_columns_pk_parsed() .map(|field| field.ident.as_ref().unwrap()) - .collect::>() } /// Returns a Vec populated with the name of the fields of the struct @@ -154,15 +160,31 @@ impl<'a> MacroTokens<'a> { pk_index } + pub fn get_primary_key_field_annotation(&self) -> Option<&PrimaryKeyAttribute<'a>> { + self.primary_key_attribute.as_ref() + } + + pub fn find_primary_key_field_annotation(fields: &'a Fields) -> Option<&'a Field> { + fields.iter().find(|field| helpers::field_has_target_attribute(field, "primary_key")) + } + /// Utility for find the primary key attribute (if exists) and the /// column name (field) which belongs pub fn get_primary_key_annotation(&self) -> Option { - let f = self - .fields - .iter() - .find(|field| helpers::field_has_target_attribute(field, "primary_key")); + self.get_primary_key_field_annotation().map(|attr| { + attr.ident.clone().to_string() + }) + } - f.map(|v| v.ident.clone().unwrap().to_string()) + pub fn get_primary_key_ident_and_type(&self) -> Option<(&Ident, &Type)> { + let primary_key = self.get_primary_key_annotation(); + if let Some(primary_key) = primary_key { + self.fields_with_types() + .into_iter() + .find(|(i, _t)| i.to_string() == primary_key) + } else { + None + } } /// Utility for find the `foreign_key` attributes (if exists) diff --git a/canyon_macros/src/utils/mod.rs b/canyon_macros/src/utils/mod.rs index 883f2c0a..1de702be 100644 --- a/canyon_macros/src/utils/mod.rs +++ b/canyon_macros/src/utils/mod.rs @@ -2,3 +2,4 @@ mod canyon_crud_attribute; pub mod function_parser; pub mod helpers; pub mod macro_tokens; +mod primary_key_attribute; diff --git a/canyon_macros/src/utils/primary_key_attribute.rs b/canyon_macros/src/utils/primary_key_attribute.rs new file mode 100644 index 00000000..2e3e054a --- /dev/null +++ b/canyon_macros/src/utils/primary_key_attribute.rs @@ -0,0 +1,37 @@ +use proc_macro2::TokenStream; +use quote::quote; +use proc_macro2::Ident; +use quote::__private::Span; +use syn::{Field, Type}; + +pub(crate) struct PrimaryKeyAttribute<'a> { + pub ident: &'a Ident, + pub ty: &'a Type, + pub name: String, +} + +// impl<'a> Default for &'a PrimaryKeyAttribute<'a> { +// fn default() -> Self { +// Self { +// ident: &Ident::new_raw("Non existent", Span::call_site()), +// ty: &Type::Verbatim(quote!{}.into()), +// name: "".to_string(), +// } +// } +// } + +impl<'a> PrimaryKeyAttribute<'a> { + pub(crate) fn get_ident_as_token_stream(&self) -> TokenStream { + let ident = self.ident; + quote! { #ident } + } + pub(crate) fn get_type_as_token_stream(&self) -> TokenStream { + let ty = self.ty; + quote! { #ty } + } +} +impl<'a> From<&'a Field> for PrimaryKeyAttribute<'a> { + fn from(value: &'a Field) -> Self { + Self { ident: value.ident.as_ref().unwrap(), ty: &value.ty, name: value.ident.as_ref().unwrap().to_string() } + } +} \ No newline at end of file diff --git a/tests/crud/hex_arch_example.rs b/tests/crud/hex_arch_example.rs index 436ef072..84ed4067 100644 --- a/tests/crud/hex_arch_example.rs +++ b/tests/crud/hex_arch_example.rs @@ -1,7 +1,7 @@ use canyon_sql::connection::DatabaseConnection; use canyon_sql::core::Canyon; use canyon_sql::macros::{CanyonCrud, CanyonMapper, canyon_entity}; -use canyon_sql::query::querybuilder::SelectQueryBuilder; +use canyon_sql::query::{QueryParameter, querybuilder::SelectQueryBuilder}; use canyon_sql::runtime::tokio::sync::Mutex; use std::error::Error; use std::sync::Arc; @@ -13,8 +13,8 @@ fn test_hex_arch_ops() { .unwrap() .get_default_connection() .unwrap(); - let league_service = LeagueServiceAdapter { - league_repository: LeagueRepositoryAdapter { + let league_service = LeagueHexServiceAdapter { + league_repository: LeagueHexRepositoryAdapter { db_conn: default_db_conn, }, }; @@ -28,65 +28,120 @@ fn test_hex_arch_ops() { // If we try to do a call using the adapter, count will use the default datasource, which is locked at this point, // since we passed the same connection that it will be using here to the repository! assert_eq!( - LeagueRepositoryAdapter::::count() + LeagueHexRepositoryAdapter::::count() .await .unwrap() as usize, find_all_result.len() ); - // assert_eq!(LeagueRepositoryAdapter::::count_with(binding.deref_mut()).await.unwrap() as usize, find_all_result.len()); + // assert_eq!(LeagueHexRepositoryAdapter::::count_with(binding.deref_mut()).await.unwrap() as usize, find_all_result.len()); // The line above works, because we're using binding, but in a better ideal world, our repository would hold an Arc> with the connection, // so the user acquire the lock on every query, just cloning the Arc, which if you remember, just increases in one unit the number of active // references pointing to the resource behind the atomic smart pointer } -#[derive(CanyonMapper)] +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_hex_arch_find_insert_ops() { + let default_db_conn = Canyon::instance() + .unwrap() + .get_default_connection() + .unwrap(); + let league_service = LeagueHexServiceAdapter { + league_repository: LeagueHexRepositoryAdapter { + db_conn: default_db_conn, + }, + }; + + let mut other_league: LeagueHex = LeagueHex { + id: Default::default(), + ext_id: Default::default(), + slug: "leaguehex-slug".to_string(), + name: "Test LeagueHex on layered".to_string(), + region: "LeagueHex Region".to_string(), + image_url: "http://example.com/image.png".to_string(), + }; + league_service.create(&mut other_league).await.unwrap(); + println!("New league inserted with: {:#?}", other_league.id); + + let find_new_league = league_service.get(&other_league.id).await.unwrap(); + assert!(find_new_league.is_some()); + assert_eq!( + find_new_league.unwrap().name, + String::from("Test LeagueHex on layered") + ); +} + +#[derive(CanyonMapper, Debug)] #[canyon_entity] -pub struct League { - // The core model of the 'League' domain +pub struct LeagueHex { + // The core model of the 'LeagueHex' domain #[primary_key] pub id: i32, + pub ext_id: i64, + pub slug: String, + pub name: String, + pub region: String, + pub image_url: String, } -pub trait LeagueService { - async fn find_all(&self) -> Result, Box>; +pub trait LeagueHexService { + async fn find_all(&self) -> Result, Box>; async fn create<'a>( &self, - league: &'a mut League, + league: &'a mut LeagueHex, ) -> Result<(), Box>; + + async fn get<'a, Pk: QueryParameter<'a>>( + &self, + id: &'a Pk, + ) -> Result, Box>; } // As a domain boundary for the application side of the hexagon -pub struct LeagueServiceAdapter { +pub struct LeagueHexServiceAdapter { league_repository: T, } -impl LeagueService for LeagueServiceAdapter { - async fn find_all(&self) -> Result, Box> { +impl LeagueHexService for LeagueHexServiceAdapter { + async fn find_all(&self) -> Result, Box> { self.league_repository.find_all().await } async fn create<'a>( &self, - league: &'a mut League, + league: &'a mut LeagueHex, ) -> Result<(), Box> { self.league_repository.create(league).await } + + async fn get<'a, Pk: QueryParameter<'a>>( + &self, + id: &'a Pk, + ) -> Result, Box> { + self.league_repository.get(id).await + } } -pub trait LeagueRepository { - async fn find_all(&self) -> Result, Box>; +pub trait LeagueHexRepository { + async fn find_all(&self) -> Result, Box>; async fn create<'a>( &self, - league: &'a mut League, + league: &'a mut LeagueHex, ) -> Result<(), Box>; + + async fn get<'a, Pk: QueryParameter<'a>>( + &self, + id: &'a Pk, + ) -> Result, Box>; } // As a domain boundary for the infrastructure side of the hexagon #[derive(CanyonCrud)] -#[canyon_crud(maps_to=League)] -pub struct LeagueRepositoryAdapter { +#[canyon_crud(maps_to=LeagueHex)] +#[canyon_entity(table_name = "league")] +pub struct LeagueHexRepositoryAdapter { // db_conn: &'b T, db_conn: Arc>, } -impl LeagueRepository for LeagueRepositoryAdapter { - async fn find_all(&self) -> Result, Box> { +impl LeagueHexRepository for LeagueHexRepositoryAdapter { + async fn find_all(&self) -> Result, Box> { let db_conn = self.db_conn.lock().await; let select_query = SelectQueryBuilder::new("league", db_conn.get_database_type()?)?.build()?; @@ -95,8 +150,17 @@ impl LeagueRepository for LeagueRepositoryAdapter async fn create<'a>( &self, - league: &'a mut League, + league: &'a mut LeagueHex, ) -> Result<(), Box> { Self::insert_entity(league).await } + + async fn get<'a, Pk: QueryParameter<'a>>( + &self, + id: &'a Pk, + ) -> Result, Box> { + let r = Self::find_by_pk(id).await; + println!("FIND BY PK ON GET err: {:?}", r); + r + } } From 969b1af45f7b5d5e2ce686cf0399fcea74c6cccb Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Thu, 29 May 2025 16:11:11 +0200 Subject: [PATCH 142/155] feat(wip)!: saving intermediate smokes --- canyon_core/src/query/bounds.rs | 10 ++- canyon_core/src/query/parameters.rs | 50 ++++++++++++--- canyon_macros/src/canyon_mapper_macro.rs | 61 +++++++++++++++---- canyon_macros/src/query_operations/insert.rs | 13 ++-- canyon_macros/src/query_operations/read.rs | 23 +++---- .../src/utils/primary_key_attribute.rs | 10 ++- 6 files changed, 125 insertions(+), 42 deletions(-) diff --git a/canyon_core/src/query/bounds.rs b/canyon_core/src/query/bounds.rs index d1f02d04..2d498356 100644 --- a/canyon_core/src/query/bounds.rs +++ b/canyon_core/src/query/bounds.rs @@ -6,6 +6,7 @@ use crate::query::parameters::QueryParameter; /// in more complex scenarios, like when insert an entity, when we need to know the value of the fields of /// the current instance that we'd like to insert pub trait Inspectionable<'a> { + type PrimaryKeyType; /// Returns an allocated linear collection with the current values of all the fields declared /// for the implementor, as the result of the evaluation of the &self.#field expression, iteratively @@ -29,8 +30,13 @@ pub trait Inspectionable<'a> { fn primary_key(&self) -> Option<&'static str>; fn primary_key_st() -> Option<&'static str>; fn primary_key_actual_value(&self) -> &'a (dyn QueryParameter<'_> + 'a); - // fn set_primary_key_actual_value(&mut self, value: Self::PrimaryKeyType); - fn set_primary_key_actual_value(&mut self, value: &'a (dyn QueryParameter<'a> + 'static)) -> Result<(), Box>; + fn set_primary_key_actual_value(&mut self, value: Self::PrimaryKeyType) -> Result<(), Box>; + // fn set_primary_key_actual_value(&mut self, value: Box) -> Result<(), Box>; + // fn set_primary_key_actual_value(&mut self, value: Box + 'a>) -> Result<(), Box>; +// fn set_primary_key_actual_value(&mut self, value: T) -> Result<(), Box> +// where Self::PrimaryKeyType: From; +// fn set_primary_key_actual_value(&mut self, value: Z) -> Result<(), Box> +// where Z: Into; } pub trait TableMetadata: std::fmt::Display { diff --git a/canyon_core/src/query/parameters.rs b/canyon_core/src/query/parameters.rs index 5d72b1e0..6916d0ef 100644 --- a/canyon_core/src/query/parameters.rs +++ b/canyon_core/src/query/parameters.rs @@ -1,4 +1,5 @@ use std::any::Any; +use std::fmt::Debug; #[cfg(feature = "mysql")] use mysql_async::{self, prelude::ToValue}; #[cfg(feature = "mssql")] @@ -11,21 +12,54 @@ use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Utc}; pub trait QueryParameterValue<'a> { fn downcast_ref(&'a self) -> Option<&'a T>; + fn to_owned_any(&'a self) -> Box; } impl<'a> QueryParameterValue<'a> for dyn QueryParameter<'a> { fn downcast_ref(&'a self) -> Option<&'a T> { self.as_any().downcast_ref() } + + fn to_owned_any(&'a self) -> Box { + Box::new(self.downcast_ref::().cloned().unwrap()) + } } impl<'a> QueryParameterValue<'a> for &'a dyn QueryParameter<'a> { fn downcast_ref(&'a self) -> Option<&'a T> { self.as_any().downcast_ref() } + + fn to_owned_any(&self) -> Box { + todo!() + } } +// Define a zero-sized type to represent the absence of a primary key +// #[derive(Debug, Clone, Copy)] +// pub struct NoPrimaryKey; +// +// // Implement the QueryParameter<'a> trait for the zero-sized type +// impl<'a> QueryParameter<'a> for NoPrimaryKey { +// fn as_any(&'a self) -> &'a dyn Any { +// todo!() +// } +// +// fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { +// todo!() +// } +// +// fn as_sqlserver_param(&self) -> ColumnData<'_> { +// todo!() +// } +// +// fn as_mysql_param(&self) -> &dyn ToValue { +// todo!() +// } +// } +// + /// Defines a trait for represent type bounds against the allowed /// data types supported by Canyon to be used as query parameters. -pub trait QueryParameter<'a>: std::fmt::Debug + Send + Sync { +pub trait QueryParameter<'a>: Debug + Send + Sync { fn as_any(&'a self) -> &'a dyn Any; #[cfg(feature = "postgres")] @@ -95,7 +129,7 @@ impl<'a> QueryParameter<'a> for Option<&'static i16> { fn as_any(&'a self) -> &'a dyn Any { self } - + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -431,7 +465,7 @@ impl QueryParameter<'_> for Option { fn as_any(&'_ self) -> &'_ dyn Any { self } - + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -469,7 +503,7 @@ impl QueryParameter<'_> for Option { fn as_any(&'_ self) -> &'_ dyn Any { self } - + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -489,7 +523,7 @@ impl QueryParameter<'_> for DateTime { fn as_any(&'_ self) -> &'_ dyn Any { self } - + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -508,7 +542,7 @@ impl QueryParameter<'_> for Option> { fn as_any(&'_ self) -> &'_ dyn Any { self } - + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -527,7 +561,7 @@ impl QueryParameter<'_> for DateTime { fn as_any(&'_ self) -> &'_ dyn Any { self } - + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self @@ -546,7 +580,7 @@ impl QueryParameter<'_> for Option> { fn as_any(&'_ self) -> &'_ dyn Any { self } - + #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { self diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index 1f7704f0..7fb5e48c 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -281,23 +281,43 @@ mod __details { } None => quote! { -1 }, // TODO: yeah, big todo :) }; - - let set_pk_val_method = if let Some(pk_ident) = pk_ident_ts { - quote! { - use canyon_sql::query::parameters::QueryParameterValue; - self.#pk_ident = value.downcast_ref::() - .ok_or_else(|| "Error downcasting the pk value passed")? - .clone(); - Ok(()) + // + // let set_pk_val_method = if let Some(pk_ident) = pk_ident_ts { + // quote! { + // // Convert the i64 value to the primary key type and assign it + // self.#pk_ident = Self::PrimaryKeyType::from(value); + // Ok(()) + // } + // } else { + // quote! { + // Err(Box::new(std::io::Error::new( + // std::io::ErrorKind::InvalidInput, + // "No primary key field defined for this entity" + // )) as Box) + // } + // }; + let set_pk_val_method = if let Some(pk_ty) = pk_ty_ts { + quote! { + self.#pk_ident_ts = value.into(); + Ok(()) + } + } else { + quote! { Ok(()) } + }; + let pk_assoc_ty = if let Some(pk_ident) = pk_ident_ts { + quote! { + #pk_ty_ts } } else { - quote! { Ok(()) /* TODO: with err */ } + quote! { i64 } }; println!("Seeing set pk method for ty: {:?}: {:?}", ty, set_pk_val_method.to_string()); quote! { impl<'a> canyon_sql::query::bounds::Inspectionable<'a> for #ty #ty_generics #where_clause { - + + type PrimaryKeyType = #pk_assoc_ty; + fn fields_actual_values(&self) -> Vec<&dyn canyon_sql::query::QueryParameter<'_>> { vec![#(#fields_values),*] } @@ -326,9 +346,24 @@ mod __details { &#pk_actual_value } - fn set_primary_key_actual_value(&mut self, value: &'a (dyn canyon_sql::query::QueryParameter<'a> + 'static)) -> Result<(), Box> { - #set_pk_val_method - } + // // fn set_primary_key_actual_value(&mut self, value: &'a (dyn canyon_sql::query::QueryParameter<'a> + 'static)) -> Result<(), Box> { + // fn set_primary_key_actual_value(&mut self, value: Box + 'a>) -> Result<(), Box> { + // #set_pk_val_method + // } + // fn set_primary_key_actual_value(&mut self, value: Z) -> Result<(), Box> + // where Self::PrimaryKeyType: From { + // #set_pk_val_method + // } + // fn set_primary_key_actual_value(&mut self, value: Box) -> Result<(), Box> { + // #set_pk_val_method + // } + fn set_primary_key_actual_value(&mut self, value: Self::PrimaryKeyType) -> Result<(), Box> { + #set_pk_val_method + } +// fn set_primary_key_actual_value(&mut self, value: Z) -> Result<(), Box> +// where Z: Into { +// #set_pk_val_method +// } } } } diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 07226637..70d8d6ee 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -4,7 +4,7 @@ use quote::quote; pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &str) -> TokenStream { let insert_method_ops = generate_insert_method_tokens(macro_data, table_schema_data); - let insert_entity_ops = generate_insert_entity_function_tokens(table_schema_data); + let insert_entity_ops = generate_insert_entity_function_tokens(table_schema_data, macro_data); // let multi_insert_tokens = generate_multiple_insert_tokens(macro_data, table_schema_data); quote! { @@ -60,7 +60,8 @@ pub fn generate_insert_method_tokens( } } -pub fn generate_insert_entity_function_tokens(table_schema_data: &str) -> TokenStream { +pub fn generate_insert_entity_function_tokens(table_schema_data: &str, macro_tokens: &MacroTokens) -> TokenStream { + let mapper_ty = (¯o_tokens.retrieve_mapping_target_type()).as_ref().unwrap_or_else(|| macro_tokens.ty); let insert_entity_signature = quote! { async fn insert_entity<'canyon_lt, Entity>(entity: &'canyon_lt mut Entity) -> Result<(), Box> @@ -110,12 +111,14 @@ pub fn generate_insert_entity_function_tokens(table_schema_data: &str) -> TokenS if let Some(pk) = entity.primary_key() { #add_returning_clause - let r = default_db_conn.lock().await.execute(&stmt, &values).await? as i64; - // entity.set_primary_key_actual_value(&r); + // let r: <#mapper_ty as canyon_sql::query::bounds::Inspectionable<'canyon_lt>>::PrimaryKeyType = default_db_conn.lock().await.query_one_for(&stmt, &values).await? ; + // entity.set_primary_key_actual_value::<_>(r as <#mapper_ty as canyon_sql::query::bounds::Inspectionable<'canyon_lt>>::PrimaryKeyType); + + let r: <#mapper_ty as canyon_sql::query::bounds::Inspectionable<'canyon_lt>>::PrimaryKeyType = default_db_conn.lock().await.query_one_for(&stmt, &values).await? ; + // entity.set_primary_key_actual_value(r); } else { let _ = default_db_conn.lock().await.execute(&stmt, &values).await?; } - // println!("Insert query {:?}", &stmt); Ok(()) } diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 7db05079..389ba128 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -109,20 +109,17 @@ fn generate_find_by_pk_operations_tokens( ); }) } else { - let is_with_mapper_ty = mapper_ty.is_some(); - if is_with_mapper_ty { + println!("Genera+ting Row Mapper inspectionable at runtime for: {:?}", ty); Some(quote! { - use canyon_sql::query::bounds::Inspectionable; - let stmt = format!( - "SELECT * FROM {} WHERE {} = $1", - #table_schema_data, - <#mapper_ty as Inspectionable>::primary_key_st() - .ok_or_else(|| "No primary key found for this instance")? // TODO: better fmtted error msg - ); - }) - } else { - None - } + let pk = <#mapper_ty as Inspectionable>::primary_key_st() + .ok_or_else(|| "No primary key found for this instance")?; + use canyon_sql::query::bounds::Inspectionable; + let stmt = format!( + "SELECT * FROM {} WHERE {} = $1", + #table_schema_data, + pk + ); + }) }; let mapper_ty = mapper_ty.unwrap_or(ty); diff --git a/canyon_macros/src/utils/primary_key_attribute.rs b/canyon_macros/src/utils/primary_key_attribute.rs index 2e3e054a..940db713 100644 --- a/canyon_macros/src/utils/primary_key_attribute.rs +++ b/canyon_macros/src/utils/primary_key_attribute.rs @@ -1,5 +1,6 @@ +use std::fmt::{Display, Formatter}; use proc_macro2::TokenStream; -use quote::quote; +use quote::{quote, ToTokens}; use proc_macro2::Ident; use quote::__private::Span; use syn::{Field, Type}; @@ -10,6 +11,13 @@ pub(crate) struct PrimaryKeyAttribute<'a> { pub name: String, } +impl<'a> Display for &'a PrimaryKeyAttribute<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let _ = f.write_fmt(format_args!("ident:{},ty:{},name:{}", self.ident.to_string(), self.ty.to_token_stream().to_string(), self.name)); + Ok(()) + } +} + // impl<'a> Default for &'a PrimaryKeyAttribute<'a> { // fn default() -> Self { // Self { From 09f0e78218cb3ed73d8992d20c755cbbfe5f7b7b Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 30 May 2025 11:05:29 +0200 Subject: [PATCH 143/155] fix(wip)!: mapper target type will be correctly calculated when the pk must be known at runtime (entity) --- canyon_macros/src/query_operations/read.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 389ba128..b86df4c8 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -1,7 +1,7 @@ use crate::query_operations::consts; use crate::utils::macro_tokens::MacroTokens; use proc_macro2::{Ident, TokenStream}; -use quote::quote; +use quote::{quote, ToTokens}; /// Facade function that acts as the unique API for export to the real macro implementation /// of all the generated macros for the READ operations @@ -109,17 +109,17 @@ fn generate_find_by_pk_operations_tokens( ); }) } else { - println!("Genera+ting Row Mapper inspectionable at runtime for: {:?}", ty); + let tt = mapper_ty.unwrap_or(ty).to_token_stream(); Some(quote! { - let pk = <#mapper_ty as Inspectionable>::primary_key_st() - .ok_or_else(|| "No primary key found for this instance")?; - use canyon_sql::query::bounds::Inspectionable; - let stmt = format!( - "SELECT * FROM {} WHERE {} = $1", - #table_schema_data, - pk - ); - }) + use canyon_sql::query::bounds::Inspectionable; + let pk = <#tt as Inspectionable>::primary_key_st() + .ok_or_else(|| "No primary key found for this instance")?; + let stmt = format!( + "SELECT * FROM {} WHERE {} = $1", + #table_schema_data, + pk + ); + }) }; let mapper_ty = mapper_ty.unwrap_or(ty); From 6e4c28e66397305e1c9136f70a1a943ba702af0c Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 30 May 2025 11:52:50 +0200 Subject: [PATCH 144/155] feat: entities got the inserted returned value of the pk auto-assigned back --- canyon_core/src/query/bounds.rs | 3 +- canyon_macros/src/canyon_mapper_macro.rs | 45 ++++------------- canyon_macros/src/query_operations/insert.rs | 17 +++---- .../src/utils/canyon_crud_attribute.rs | 49 ++++++++++++++----- tests/tests_models/player.rs | 2 +- 5 files changed, 58 insertions(+), 58 deletions(-) diff --git a/canyon_core/src/query/bounds.rs b/canyon_core/src/query/bounds.rs index 2d498356..6315a7ff 100644 --- a/canyon_core/src/query/bounds.rs +++ b/canyon_core/src/query/bounds.rs @@ -1,4 +1,5 @@ use crate::query::parameters::QueryParameter; +use crate::rows::FromSqlOwnedValue; /// Contract that provides a way to Canyon to inspect certain property or values at runtime. /// @@ -6,7 +7,7 @@ use crate::query::parameters::QueryParameter; /// in more complex scenarios, like when insert an entity, when we need to know the value of the fields of /// the current instance that we'd like to insert pub trait Inspectionable<'a> { - type PrimaryKeyType; + type PrimaryKeyType: FromSqlOwnedValue; /// Returns an allocated linear collection with the current values of all the fields declared /// for the implementor, as the result of the evaluation of the &self.#field expression, iteratively diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index 7fb5e48c..5e5d016a 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -281,37 +281,27 @@ mod __details { } None => quote! { -1 }, // TODO: yeah, big todo :) }; - // - // let set_pk_val_method = if let Some(pk_ident) = pk_ident_ts { - // quote! { - // // Convert the i64 value to the primary key type and assign it - // self.#pk_ident = Self::PrimaryKeyType::from(value); - // Ok(()) - // } - // } else { - // quote! { - // Err(Box::new(std::io::Error::new( - // std::io::ErrorKind::InvalidInput, - // "No primary key field defined for this entity" - // )) as Box) - // } - // }; + let set_pk_val_method = if let Some(pk_ty) = pk_ty_ts { quote! { self.#pk_ident_ts = value.into(); Ok(()) } } else { - quote! { Ok(()) } + quote! { + Err(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "No primary key field defined for this entity" + )) as Box) + } }; let pk_assoc_ty = if let Some(pk_ident) = pk_ident_ts { quote! { #pk_ty_ts } } else { - quote! { i64 } + quote! { ! } }; - println!("Seeing set pk method for ty: {:?}: {:?}", ty, set_pk_val_method.to_string()); quote! { impl<'a> canyon_sql::query::bounds::Inspectionable<'a> for #ty #ty_generics #where_clause { @@ -346,24 +336,9 @@ mod __details { &#pk_actual_value } - // // fn set_primary_key_actual_value(&mut self, value: &'a (dyn canyon_sql::query::QueryParameter<'a> + 'static)) -> Result<(), Box> { - // fn set_primary_key_actual_value(&mut self, value: Box + 'a>) -> Result<(), Box> { - // #set_pk_val_method - // } - // fn set_primary_key_actual_value(&mut self, value: Z) -> Result<(), Box> - // where Self::PrimaryKeyType: From { - // #set_pk_val_method - // } - // fn set_primary_key_actual_value(&mut self, value: Box) -> Result<(), Box> { - // #set_pk_val_method - // } fn set_primary_key_actual_value(&mut self, value: Self::PrimaryKeyType) -> Result<(), Box> { - #set_pk_val_method - } -// fn set_primary_key_actual_value(&mut self, value: Z) -> Result<(), Box> -// where Z: Into { -// #set_pk_val_method -// } + #set_pk_val_method + } } } } diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 70d8d6ee..db9e48f0 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -4,7 +4,7 @@ use quote::quote; pub fn generate_insert_tokens(macro_data: &MacroTokens, table_schema_data: &str) -> TokenStream { let insert_method_ops = generate_insert_method_tokens(macro_data, table_schema_data); - let insert_entity_ops = generate_insert_entity_function_tokens(table_schema_data, macro_data); + let insert_entity_ops = generate_insert_entity_function_tokens(table_schema_data); // let multi_insert_tokens = generate_multiple_insert_tokens(macro_data, table_schema_data); quote! { @@ -60,8 +60,7 @@ pub fn generate_insert_method_tokens( } } -pub fn generate_insert_entity_function_tokens(table_schema_data: &str, macro_tokens: &MacroTokens) -> TokenStream { - let mapper_ty = (¯o_tokens.retrieve_mapping_target_type()).as_ref().unwrap_or_else(|| macro_tokens.ty); +pub fn generate_insert_entity_function_tokens(table_schema_data: &str) -> TokenStream { let insert_entity_signature = quote! { async fn insert_entity<'canyon_lt, Entity>(entity: &'canyon_lt mut Entity) -> Result<(), Box> @@ -105,17 +104,15 @@ pub fn generate_insert_entity_function_tokens(table_schema_data: &str, macro_tok quote! { #insert_entity_signature { - let default_db_conn = canyon_sql::core::Canyon::instance()? - .get_default_connection()?; + let default_db_conn = canyon_sql::core::Canyon::instance()? #stmt_ctr; if let Some(pk) = entity.primary_key() { #add_returning_clause - // let r: <#mapper_ty as canyon_sql::query::bounds::Inspectionable<'canyon_lt>>::PrimaryKeyType = default_db_conn.lock().await.query_one_for(&stmt, &values).await? ; - // entity.set_primary_key_actual_value::<_>(r as <#mapper_ty as canyon_sql::query::bounds::Inspectionable<'canyon_lt>>::PrimaryKeyType); - - let r: <#mapper_ty as canyon_sql::query::bounds::Inspectionable<'canyon_lt>>::PrimaryKeyType = default_db_conn.lock().await.query_one_for(&stmt, &values).await? ; - // entity.set_primary_key_actual_value(r); + use canyon_sql::query::bounds::Inspectionable; + + let pk = default_db_conn.lock().await.query_one_for::<::PrimaryKeyType>(&stmt, &values).await?; + entity.set_primary_key_actual_value(pk)?; } else { let _ = default_db_conn.lock().await.execute(&stmt, &values).await?; } diff --git a/canyon_macros/src/utils/canyon_crud_attribute.rs b/canyon_macros/src/utils/canyon_crud_attribute.rs index 265affe4..532bbb74 100644 --- a/canyon_macros/src/utils/canyon_crud_attribute.rs +++ b/canyon_macros/src/utils/canyon_crud_attribute.rs @@ -1,6 +1,7 @@ use proc_macro2::Ident; use syn::Token; use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; /// Type that helps to parse the: `#[canyon_crud(maps_to = Ident)]` proc macro attribute /// @@ -8,26 +9,52 @@ use syn::parse::{Parse, ParseStream}; /// `CrudOperations` will write the queries as the implementor of [`RowMapper`] pub(crate) struct CanyonCrudAttribute { pub maps_to: Option, + pub pk: Option, + pub pk_type: Option, } impl Parse for CanyonCrudAttribute { fn parse(input: ParseStream<'_>) -> syn::Result { - let arg_name: Ident = input.parse()?; - if arg_name != "maps_to" { - return Err(syn::Error::new_spanned( - arg_name, - "unsupported 'canyon_crud' attribute, expected `maps_to`", - )); + let mut maps_to = None; + let mut pk = None; + let mut pk_type = None; + + let pairs = Punctuated::::parse_terminated(input)?; + + for pair in pairs { + match pair.name.to_string().as_str() { + "maps_to" => maps_to = Some(pair.value), + "primary_key" => pk = Some(pair.value), + "pk_type" => pk_type = Some(pair.value), + other => { + return Err(syn::Error::new_spanned( + pair.name, + format!("Unsupported attribute key: `{}`", other), + )); + } + } } - // Parse (and discard the span of) the `=` token - let _: Token![=] = input.parse()?; + Ok(Self { + maps_to, + pk, + pk_type, + }) + } +} - // Parse the argument value - let name = input.parse()?; +struct MetaEntry { + name: Ident, + eq_token: Token![=], + value: Ident, +} +impl Parse for MetaEntry { + fn parse(input: ParseStream<'_>) -> syn::Result { Ok(Self { - maps_to: Some(name), + name: input.parse()?, + eq_token: input.parse()?, + value: input.parse()?, }) } } diff --git a/tests/tests_models/player.rs b/tests/tests_models/player.rs index 0cba50ec..3bdc251e 100644 --- a/tests/tests_models/player.rs +++ b/tests/tests_models/player.rs @@ -13,7 +13,7 @@ use canyon_sql::macros::*; /// does not have all the CRUD operations available, only the ones that doesn't /// require of a primary key. pub struct Player { - // #[primary_key] We will omit this to use it as a mock of entities that doesn't declare primary key + // #[primary_key] // We will omit this to use it as a mock of entities that doesn't declare primary key id: i32, ext_id: i64, first_name: String, From ed6becd4e279c93a0b156a740b56912d68910f72 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 30 May 2025 12:12:09 +0200 Subject: [PATCH 145/155] chore: cleanup --- canyon_core/src/query/bounds.rs | 6 ------ canyon_macros/src/canyon_mapper_macro.rs | 2 +- canyon_macros/src/query_operations/insert.rs | 8 ++++---- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/canyon_core/src/query/bounds.rs b/canyon_core/src/query/bounds.rs index 6315a7ff..12383f7b 100644 --- a/canyon_core/src/query/bounds.rs +++ b/canyon_core/src/query/bounds.rs @@ -32,12 +32,6 @@ pub trait Inspectionable<'a> { fn primary_key_st() -> Option<&'static str>; fn primary_key_actual_value(&self) -> &'a (dyn QueryParameter<'_> + 'a); fn set_primary_key_actual_value(&mut self, value: Self::PrimaryKeyType) -> Result<(), Box>; - // fn set_primary_key_actual_value(&mut self, value: Box) -> Result<(), Box>; - // fn set_primary_key_actual_value(&mut self, value: Box + 'a>) -> Result<(), Box>; -// fn set_primary_key_actual_value(&mut self, value: T) -> Result<(), Box> -// where Self::PrimaryKeyType: From; -// fn set_primary_key_actual_value(&mut self, value: Z) -> Result<(), Box> -// where Z: Into; } pub trait TableMetadata: std::fmt::Display { diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index 5e5d016a..f838e6b9 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -300,7 +300,7 @@ mod __details { #pk_ty_ts } } else { - quote! { ! } + quote! { i64 } }; quote! { diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index db9e48f0..44b822fc 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -104,14 +104,12 @@ pub fn generate_insert_entity_function_tokens(table_schema_data: &str) -> TokenS quote! { #insert_entity_signature { - let default_db_conn = canyon_sql::core::Canyon::instance()? + let default_db_conn = canyon_sql::core::Canyon::instance()?.get_default_connection()?; #stmt_ctr; if let Some(pk) = entity.primary_key() { #add_returning_clause - use canyon_sql::query::bounds::Inspectionable; - - let pk = default_db_conn.lock().await.query_one_for::<::PrimaryKeyType>(&stmt, &values).await?; + let pk = default_db_conn.lock().await.query_one_for::<::PrimaryKeyType>(&stmt, &values).await?; entity.set_primary_key_actual_value(pk)?; } else { let _ = default_db_conn.lock().await.execute(&stmt, &values).await?; @@ -123,6 +121,8 @@ pub fn generate_insert_entity_function_tokens(table_schema_data: &str) -> TokenS #stmt_ctr; if let Some(pk) = entity.primary_key() { #add_returning_clause + let pk = input.query_one_for::<::PrimaryKeyType>(&stmt, &values).await?; + entity.set_primary_key_actual_value(pk)?; } else { let _ = input.execute(&stmt, &values).await?; } From 264f4c02687781fcaabf016173267faf9772f279 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 30 May 2025 12:18:03 +0200 Subject: [PATCH 146/155] chore: simplifying back macro tokens --- canyon_core/src/query/bounds.rs | 5 +- canyon_core/src/query/parameters.rs | 20 +++----- canyon_crud/src/crud.rs | 1 - canyon_macros/src/canyon_mapper_macro.rs | 7 ++- canyon_macros/src/query_operations/read.rs | 22 ++++----- .../src/utils/canyon_crud_attribute.rs | 49 +++++-------------- canyon_macros/src/utils/macro_tokens.rs | 25 ++++++---- .../src/utils/primary_key_attribute.rs | 21 +++++--- 8 files changed, 69 insertions(+), 81 deletions(-) diff --git a/canyon_core/src/query/bounds.rs b/canyon_core/src/query/bounds.rs index 12383f7b..fdba324b 100644 --- a/canyon_core/src/query/bounds.rs +++ b/canyon_core/src/query/bounds.rs @@ -31,7 +31,10 @@ pub trait Inspectionable<'a> { fn primary_key(&self) -> Option<&'static str>; fn primary_key_st() -> Option<&'static str>; fn primary_key_actual_value(&self) -> &'a (dyn QueryParameter<'_> + 'a); - fn set_primary_key_actual_value(&mut self, value: Self::PrimaryKeyType) -> Result<(), Box>; + fn set_primary_key_actual_value( + &mut self, + value: Self::PrimaryKeyType, + ) -> Result<(), Box>; } pub trait TableMetadata: std::fmt::Display { diff --git a/canyon_core/src/query/parameters.rs b/canyon_core/src/query/parameters.rs index 6916d0ef..5a03c402 100644 --- a/canyon_core/src/query/parameters.rs +++ b/canyon_core/src/query/parameters.rs @@ -1,7 +1,7 @@ -use std::any::Any; -use std::fmt::Debug; #[cfg(feature = "mysql")] use mysql_async::{self, prelude::ToValue}; +use std::any::Any; +use std::fmt::Debug; #[cfg(feature = "mssql")] use tiberius::{self, ColumnData, IntoSql}; #[cfg(feature = "postgres")] @@ -36,26 +36,26 @@ impl<'a> QueryParameterValue<'a> for &'a dyn QueryParameter<'a> { // Define a zero-sized type to represent the absence of a primary key // #[derive(Debug, Clone, Copy)] // pub struct NoPrimaryKey; -// +// // // Implement the QueryParameter<'a> trait for the zero-sized type // impl<'a> QueryParameter<'a> for NoPrimaryKey { // fn as_any(&'a self) -> &'a dyn Any { // todo!() // } -// +// // fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { // todo!() // } -// +// // fn as_sqlserver_param(&self) -> ColumnData<'_> { // todo!() // } -// +// // fn as_mysql_param(&self) -> &dyn ToValue { // todo!() // } // } -// +// /// Defines a trait for represent type bounds against the allowed /// data types supported by Canyon to be used as query parameters. @@ -201,7 +201,6 @@ impl QueryParameter<'_> for f32 { } } - impl<'a> QueryParameter<'a> for Option { fn as_any(&self) -> &dyn Any { self @@ -221,7 +220,6 @@ impl<'a> QueryParameter<'a> for Option { } } - impl<'a> QueryParameter<'a> for f64 { fn as_any(&self) -> &dyn Any { self @@ -241,7 +239,6 @@ impl<'a> QueryParameter<'a> for f64 { } } - impl<'a> QueryParameter<'a> for Option { fn as_any(&self) -> &dyn Any { self @@ -363,7 +360,7 @@ impl<'a> QueryParameter<'a> for Option<&'static String> { } impl QueryParameter<'_> for &'static str { - fn as_any(& self) -> &dyn Any { + fn as_any(&self) -> &dyn Any { self } @@ -381,7 +378,6 @@ impl QueryParameter<'_> for &'static str { } } - impl QueryParameter<'_> for Option<&'static str> { fn as_any(&'_ self) -> &'_ dyn Any { self diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index e2382af7..fc16d39f 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -9,7 +9,6 @@ use canyon_core::query::querybuilder::{ use std::error::Error; use std::future::Future; - /// *CrudOperations* it's the core part of Canyon-SQL. /// /// Here it's defined and implemented every CRUD operation diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index f838e6b9..aa8069d1 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -257,7 +257,7 @@ mod __details { pub(crate) fn generate_inspectionable_impl_tokens(ast: &MacroTokens) -> TokenStream { let ty = ast.ty; let pk = ast.get_primary_key_field_annotation(); - let pk_ident_ts= pk.map(|pk| pk.ident); + let pk_ident_ts = pk.map(|pk| pk.ident); let pk_ty_ts = pk.map(|pk| pk.ty); let (impl_generics, ty_generics, where_clause) = &ast.generics.split_for_impl(); @@ -265,7 +265,10 @@ mod __details { let fields_values = fields.iter().map(|ident| { quote! { &self.#ident } }); - let fields_names = fields.iter().map(|ident| ident.to_string()).collect::>(); + let fields_names = fields + .iter() + .map(|ident| ident.to_string()) + .collect::>(); let fields_as_comma_sep_string = ast.get_struct_fields_as_comma_sep_string(); let queries_placeholders = ast.placeholders_generator(); diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index b86df4c8..eef04786 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -1,7 +1,7 @@ use crate::query_operations::consts; use crate::utils::macro_tokens::MacroTokens; use proc_macro2::{Ident, TokenStream}; -use quote::{quote, ToTokens}; +use quote::{ToTokens, quote}; /// Facade function that acts as the unique API for export to the real macro implementation /// of all the generated macros for the READ operations @@ -110,16 +110,16 @@ fn generate_find_by_pk_operations_tokens( }) } else { let tt = mapper_ty.unwrap_or(ty).to_token_stream(); - Some(quote! { - use canyon_sql::query::bounds::Inspectionable; - let pk = <#tt as Inspectionable>::primary_key_st() - .ok_or_else(|| "No primary key found for this instance")?; - let stmt = format!( - "SELECT * FROM {} WHERE {} = $1", - #table_schema_data, - pk - ); - }) + Some(quote! { + use canyon_sql::query::bounds::Inspectionable; + let pk = <#tt as Inspectionable>::primary_key_st() + .ok_or_else(|| "No primary key found for this instance")?; + let stmt = format!( + "SELECT * FROM {} WHERE {} = $1", + #table_schema_data, + pk + ); + }) }; let mapper_ty = mapper_ty.unwrap_or(ty); diff --git a/canyon_macros/src/utils/canyon_crud_attribute.rs b/canyon_macros/src/utils/canyon_crud_attribute.rs index 532bbb74..265affe4 100644 --- a/canyon_macros/src/utils/canyon_crud_attribute.rs +++ b/canyon_macros/src/utils/canyon_crud_attribute.rs @@ -1,7 +1,6 @@ use proc_macro2::Ident; use syn::Token; use syn::parse::{Parse, ParseStream}; -use syn::punctuated::Punctuated; /// Type that helps to parse the: `#[canyon_crud(maps_to = Ident)]` proc macro attribute /// @@ -9,52 +8,26 @@ use syn::punctuated::Punctuated; /// `CrudOperations` will write the queries as the implementor of [`RowMapper`] pub(crate) struct CanyonCrudAttribute { pub maps_to: Option, - pub pk: Option, - pub pk_type: Option, } impl Parse for CanyonCrudAttribute { fn parse(input: ParseStream<'_>) -> syn::Result { - let mut maps_to = None; - let mut pk = None; - let mut pk_type = None; - - let pairs = Punctuated::::parse_terminated(input)?; - - for pair in pairs { - match pair.name.to_string().as_str() { - "maps_to" => maps_to = Some(pair.value), - "primary_key" => pk = Some(pair.value), - "pk_type" => pk_type = Some(pair.value), - other => { - return Err(syn::Error::new_spanned( - pair.name, - format!("Unsupported attribute key: `{}`", other), - )); - } - } + let arg_name: Ident = input.parse()?; + if arg_name != "maps_to" { + return Err(syn::Error::new_spanned( + arg_name, + "unsupported 'canyon_crud' attribute, expected `maps_to`", + )); } - Ok(Self { - maps_to, - pk, - pk_type, - }) - } -} + // Parse (and discard the span of) the `=` token + let _: Token![=] = input.parse()?; -struct MetaEntry { - name: Ident, - eq_token: Token![=], - value: Ident, -} + // Parse the argument value + let name = input.parse()?; -impl Parse for MetaEntry { - fn parse(input: ParseStream<'_>) -> syn::Result { Ok(Self { - name: input.parse()?, - eq_token: input.parse()?, - value: input.parse()?, + maps_to: Some(name), }) } } diff --git a/canyon_macros/src/utils/macro_tokens.rs b/canyon_macros/src/utils/macro_tokens.rs index 068d575c..130c4013 100644 --- a/canyon_macros/src/utils/macro_tokens.rs +++ b/canyon_macros/src/utils/macro_tokens.rs @@ -1,10 +1,10 @@ use crate::utils::canyon_crud_attribute::CanyonCrudAttribute; use crate::utils::helpers; +use crate::utils::primary_key_attribute::PrimaryKeyAttribute; use canyon_entities::field_annotation::EntityFieldAnnotation; use proc_macro2::{Ident, Span}; use std::convert::TryFrom; use syn::{Attribute, DeriveInput, Field, Fields, Generics, Type, Visibility}; -use crate::utils::primary_key_attribute::PrimaryKeyAttribute; /// Provides a convenient way of store the data for the TokenStream /// received on a macro @@ -26,9 +26,13 @@ impl<'a> MacroTokens<'a> { if let syn::Data::Struct(ref s) = ast.data { let attrs = &ast.attrs; - let primary_key_attribute = Self::find_primary_key_field_annotation(&s.fields) - .map(|f| PrimaryKeyAttribute { ident: f.ident.as_ref().unwrap(), ty: &f.ty, name: f.ident.as_ref().unwrap().to_string() }); - + let primary_key_attribute = + Self::find_primary_key_field_annotation(&s.fields).map(|f| PrimaryKeyAttribute { + ident: f.ident.as_ref().unwrap(), + ty: &f.ty, + name: f.ident.as_ref().unwrap().to_string(), + }); + let mut canyon_crud_attribute = None; for attr in attrs { if attr.path.is_ident("canyon_crud") { @@ -43,7 +47,7 @@ impl<'a> MacroTokens<'a> { attrs: &ast.attrs, fields: &s.fields, canyon_crud_attribute, - primary_key_attribute + primary_key_attribute, }) } else { Err(syn::Error::new( @@ -115,7 +119,7 @@ impl<'a> MacroTokens<'a> { /// Returns a collection with all the [`syn::Ident`] for all the type members, skipping (if present) /// the field which is annotated with #[primary_key] - pub fn get_fields_idents_pk_parsed(&self) -> impl Iterator { + pub fn get_fields_idents_pk_parsed(&self) -> impl Iterator { self.get_columns_pk_parsed() .map(|field| field.ident.as_ref().unwrap()) } @@ -165,15 +169,16 @@ impl<'a> MacroTokens<'a> { } pub fn find_primary_key_field_annotation(fields: &'a Fields) -> Option<&'a Field> { - fields.iter().find(|field| helpers::field_has_target_attribute(field, "primary_key")) + fields + .iter() + .find(|field| helpers::field_has_target_attribute(field, "primary_key")) } /// Utility for find the primary key attribute (if exists) and the /// column name (field) which belongs pub fn get_primary_key_annotation(&self) -> Option { - self.get_primary_key_field_annotation().map(|attr| { - attr.ident.clone().to_string() - }) + self.get_primary_key_field_annotation() + .map(|attr| attr.ident.clone().to_string()) } pub fn get_primary_key_ident_and_type(&self) -> Option<(&Ident, &Type)> { diff --git a/canyon_macros/src/utils/primary_key_attribute.rs b/canyon_macros/src/utils/primary_key_attribute.rs index 940db713..4b78f095 100644 --- a/canyon_macros/src/utils/primary_key_attribute.rs +++ b/canyon_macros/src/utils/primary_key_attribute.rs @@ -1,8 +1,8 @@ -use std::fmt::{Display, Formatter}; -use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; use proc_macro2::Ident; +use proc_macro2::TokenStream; use quote::__private::Span; +use quote::{ToTokens, quote}; +use std::fmt::{Display, Formatter}; use syn::{Field, Type}; pub(crate) struct PrimaryKeyAttribute<'a> { @@ -13,7 +13,12 @@ pub(crate) struct PrimaryKeyAttribute<'a> { impl<'a> Display for &'a PrimaryKeyAttribute<'a> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let _ = f.write_fmt(format_args!("ident:{},ty:{},name:{}", self.ident.to_string(), self.ty.to_token_stream().to_string(), self.name)); + let _ = f.write_fmt(format_args!( + "ident:{},ty:{},name:{}", + self.ident.to_string(), + self.ty.to_token_stream().to_string(), + self.name + )); Ok(()) } } @@ -40,6 +45,10 @@ impl<'a> PrimaryKeyAttribute<'a> { } impl<'a> From<&'a Field> for PrimaryKeyAttribute<'a> { fn from(value: &'a Field) -> Self { - Self { ident: value.ident.as_ref().unwrap(), ty: &value.ty, name: value.ident.as_ref().unwrap().to_string() } + Self { + ident: value.ident.as_ref().unwrap(), + ty: &value.ty, + name: value.ident.as_ref().unwrap().to_string(), + } } -} \ No newline at end of file +} From 82dcfe87c807ac3400acf21bd67b4a41b1d2bc6d Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Mon, 2 Jun 2025 17:19:38 +0200 Subject: [PATCH 147/155] fix: relaxing lifetime bounds that wasn't correctly specified between in and out params on crud operations --- canyon_crud/src/crud.rs | 26 ++++++++++---------- canyon_macros/src/query_operations/insert.rs | 8 +++--- canyon_macros/src/query_operations/read.rs | 10 ++++---- canyon_macros/src/query_operations/update.rs | 8 +++--- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index fc16d39f..ccf98d55 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -24,7 +24,7 @@ use std::future::Future; /// See it's definition and docs to see the implementations. /// Also, you can find the written macro-code that performs the auto-mapping /// in the *canyon_sql_root::canyon_macros* crates, on the root of this project. -pub trait CrudOperations: Send + Sync +pub trait CrudOperations: Send where R: RowMapper, Vec: FromIterator<::Output>, @@ -51,14 +51,14 @@ where where I: DbConnection + Send + 'a; - fn find_by_pk<'a>( + fn find_by_pk<'a, 'b>( value: &'a dyn QueryParameter<'a>, - ) -> impl Future, Box<(dyn Error + Send + Sync + 'a)>>> + Send; + ) -> impl Future, Box<(dyn Error + Send + Sync + 'b)>>> + Send; - fn find_by_pk_with<'a, I>( + fn find_by_pk_with<'a, 'b, I>( value: &'a dyn QueryParameter<'a>, input: I, - ) -> impl Future, Box<(dyn Error + Send + Sync + 'a)>>> + Send + ) -> impl Future, Box<(dyn Error + Send + Sync + 'b)>>> + Send where I: DbConnection + Send + 'a; @@ -170,16 +170,16 @@ where where I: DbConnection + Send + 'a; - fn insert_entity<'a, T>( + fn insert_entity<'a, 'b, T>( entity: &'a mut T, - ) -> impl Future>> + ) -> impl Future>> where T: RowMapper + Inspectionable<'a> + Sync + 'a; - fn insert_entity_with<'a, T, I>( + fn insert_entity_with<'a, 'b, T, I>( entity: &'a mut T, input: I, - ) -> impl Future>> + ) -> impl Future>> where T: RowMapper + Inspectionable<'a> + Sync + 'a, I: DbConnection + Send + 'a; @@ -207,16 +207,16 @@ where where I: DbConnection + Send + 'a; - fn update_entity<'a, T>( + fn update_entity<'a, 'b, T>( entity: &'a T, - ) -> impl Future>> + ) -> impl Future>> where T: RowMapper + Inspectionable<'a> + Sync + 'a; - fn update_entity_with<'a, T, I>( + fn update_entity_with<'a, 'b, T, I>( entity: &'a T, input: I, - ) -> impl Future>> + ) -> impl Future>> where T: RowMapper + Inspectionable<'a> + Sync + 'a, I: DbConnection + Send + 'a; diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 44b822fc..9cf45784 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -62,8 +62,8 @@ pub fn generate_insert_method_tokens( pub fn generate_insert_entity_function_tokens(table_schema_data: &str) -> TokenStream { let insert_entity_signature = quote! { - async fn insert_entity<'canyon_lt, Entity>(entity: &'canyon_lt mut Entity) - -> Result<(), Box> + async fn insert_entity<'canyon_lt, 'err_lt, Entity>(entity: &'canyon_lt mut Entity) + -> Result<(), Box> where Entity: canyon_sql::core::RowMapper + canyon_sql::query::bounds::Inspectionable<'canyon_lt> + Sync @@ -71,8 +71,8 @@ pub fn generate_insert_entity_function_tokens(table_schema_data: &str) -> TokenS }; let insert_entity_with_signature = quote! { - async fn insert_entity_with<'canyon_lt, Entity, Input>(entity: &'canyon_lt mut Entity, input: Input) - -> Result<(), Box> + async fn insert_entity_with<'canyon_lt, 'err_lt, Entity, Input>(entity: &'canyon_lt mut Entity, input: Input) + -> Result<(), Box> where Entity: canyon_sql::core::RowMapper + canyon_sql::query::bounds::Inspectionable<'canyon_lt> diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index eef04786..6a52e206 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -213,8 +213,8 @@ mod __details { }; quote! { - async fn find_by_pk<'a>(value: &'a dyn canyon_sql::query::QueryParameter<'a>) - -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + async fn find_by_pk<'canyon_lt, 'err_lt>(value: &'canyon_lt dyn canyon_sql::query::QueryParameter<'canyon_lt>) + -> Result, Box<(dyn std::error::Error + Send + Sync + 'err_lt)>> { #body } @@ -236,10 +236,10 @@ mod __details { }; quote! { - async fn find_by_pk_with<'a, I>(value: &'a dyn canyon_sql::query::QueryParameter<'a>, input: I) - -> Result, Box<(dyn std::error::Error + Send + Sync + 'a)>> + async fn find_by_pk_with<'canyon_lt, 'err_lt, I>(value: &'canyon_lt dyn canyon_sql::query::QueryParameter<'canyon_lt>, input: I) + -> Result, Box<(dyn std::error::Error + Send + Sync + 'err_lt)>> where - I: canyon_sql::connection::DbConnection + Send + 'a + I: canyon_sql::connection::DbConnection + Send + 'canyon_lt { #body } diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index eb3eca34..800b31fb 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -88,8 +88,8 @@ fn generate_update_method_tokens(macro_data: &MacroTokens, table_schema_data: &s fn generate_update_entity_tokens(table_schema_data: &str) -> TokenStream { let update_entity_signature = quote! { - async fn update_entity<'canyon_lt, Entity>(entity: &'canyon_lt Entity) - -> Result<(), Box> + async fn update_entity<'canyon_lt, 'err_lt, Entity>(entity: &'canyon_lt Entity) + -> Result<(), Box> where Entity: canyon_sql::core::RowMapper + canyon_sql::query::bounds::Inspectionable<'canyon_lt> + Sync @@ -97,8 +97,8 @@ fn generate_update_entity_tokens(table_schema_data: &str) -> TokenStream { }; let update_entity_with_signature = quote! { - async fn update_entity_with<'canyon_lt, Entity, Input>(entity: &'canyon_lt Entity, input: Input) - -> Result<(), Box> + async fn update_entity_with<'canyon_lt, 'err_lt, Entity, Input>(entity: &'canyon_lt Entity, input: Input) + -> Result<(), Box> where Entity: canyon_sql::core::RowMapper + canyon_sql::query::bounds::Inspectionable<'canyon_lt> From cf5a1b16a1225dbb088470b5e545a3990e58a0ea Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 3 Jun 2025 11:51:55 +0200 Subject: [PATCH 148/155] feat(wip)!: provisional blanket implementation for any T that's DbConnection and is wrapped on Arc Mutex combo --- .../src/connection/contracts/impl/mod.rs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/canyon_core/src/connection/contracts/impl/mod.rs b/canyon_core/src/connection/contracts/impl/mod.rs index 95b6eb11..e85166e0 100644 --- a/canyon_core/src/connection/contracts/impl/mod.rs +++ b/canyon_core/src/connection/contracts/impl/mod.rs @@ -1,3 +1,13 @@ +use crate::canyon::Canyon; +use crate::connection::contracts::DbConnection; +use crate::connection::database_type::DatabaseType; +use crate::mapper::RowMapper; +use crate::query::parameters::QueryParameter; +use crate::rows::{CanyonRows, FromSqlOwnedValue}; +use std::error::Error; +use std::sync::Arc; +use tokio::sync::Mutex; + #[cfg(feature = "mssql")] pub mod mssql; #[cfg(feature = "mysql")] @@ -12,3 +22,61 @@ pub mod database_connection; // Apply the macro to implement DbConnection for &str and str impl_db_connection!(str); impl_db_connection!(&str); + +impl DbConnection for Arc> +where + T: DbConnection + Send, + Self: Clone +{ + async fn query_rows<'a>( + &self, + stmt: &str, + params: &[&'a dyn QueryParameter<'a>], + ) -> Result> { + self.lock().await.query_rows(stmt, params).await + } + + async fn query<'a, S, R>( + &self, + stmt: S, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> Result, Box<(dyn Error + Send + Sync)>> + where + S: AsRef + Send, + R: RowMapper, + Vec: FromIterator<::Output>, + { + self.lock().await.query(stmt, params).await + } + + async fn query_one<'a, R>( + &self, + stmt: &str, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> Result, Box<(dyn Error + Send + Sync)>> + where + R: RowMapper, + { + self.lock().await.query_one::(stmt, params).await + } + + async fn query_one_for<'a, F: FromSqlOwnedValue>( + &self, + stmt: &str, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> Result> { + self.lock().await.query_one_for::(stmt, params).await + } + + async fn execute<'a>( + &self, + stmt: &str, + params: &[&'a (dyn QueryParameter<'a>)], + ) -> Result> { + self.lock().await.execute(stmt, params).await + } + + fn get_database_type(&self) -> Result> { + todo!() + } +} From d216f9fe90b2d343bb41322cf2ebff2735cca45b Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Tue, 3 Jun 2025 11:56:22 +0200 Subject: [PATCH 149/155] feat(wip)!: implementing u32 for QueryParameter types (not for tiberius, as usual, which will panic). We should wrap all the QueryParameter methods output with Result --- canyon_core/src/query/parameters.rs | 39 +++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/canyon_core/src/query/parameters.rs b/canyon_core/src/query/parameters.rs index 5a03c402..62264a29 100644 --- a/canyon_core/src/query/parameters.rs +++ b/canyon_core/src/query/parameters.rs @@ -182,6 +182,45 @@ impl<'a> QueryParameter<'a> for Option { } } +impl QueryParameter<'_> for u32 { + fn as_any(&self) -> &dyn Any { + self + } + + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + panic!("Unsupported sqlserver parameter type "); + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn ToValue { + self + } +} + + +impl<'a> QueryParameter<'a> for Option { + fn as_any(&'a self) -> &'a dyn Any { + self + } + + #[cfg(feature = "postgres")] + fn as_postgres_param(&self) -> &(dyn ToSql + Sync) { + self + } + #[cfg(feature = "mssql")] + fn as_sqlserver_param(&self) -> ColumnData<'_> { + panic!("Unsupported sqlserver parameter type "); + } + #[cfg(feature = "mysql")] + fn as_mysql_param(&self) -> &dyn ToValue { + self + } +} + impl QueryParameter<'_> for f32 { fn as_any(&self) -> &dyn Any { self From 6fff4873d06cc54c73becc063fd54141ba346c35 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 4 Jun 2025 08:20:58 +0200 Subject: [PATCH 150/155] chore: intermediate code cleaning --- .../src/connection/contracts/impl/mod.rs | 1 - canyon_macros/src/canyon_mapper_macro.rs | 12 ++++---- canyon_macros/src/query_operations/read.rs | 1 - .../src/utils/primary_key_attribute.rs | 28 ++----------------- 4 files changed, 9 insertions(+), 33 deletions(-) diff --git a/canyon_core/src/connection/contracts/impl/mod.rs b/canyon_core/src/connection/contracts/impl/mod.rs index e85166e0..4df947e4 100644 --- a/canyon_core/src/connection/contracts/impl/mod.rs +++ b/canyon_core/src/connection/contracts/impl/mod.rs @@ -1,4 +1,3 @@ -use crate::canyon::Canyon; use crate::connection::contracts::DbConnection; use crate::connection::database_type::DatabaseType; use crate::mapper::RowMapper; diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index aa8069d1..3236d82f 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -285,9 +285,9 @@ mod __details { None => quote! { -1 }, // TODO: yeah, big todo :) }; - let set_pk_val_method = if let Some(pk_ty) = pk_ty_ts { + let set_pk_val_method = if let Some(pk_ident) = pk_ident_ts { quote! { - self.#pk_ident_ts = value.into(); + self.#pk_ident = value.into(); Ok(()) } } else { @@ -298,16 +298,16 @@ mod __details { )) as Box) } }; - let pk_assoc_ty = if let Some(pk_ident) = pk_ident_ts { + let pk_assoc_ty = if let Some(pk_ty) = pk_ty_ts { quote! { - #pk_ty_ts + #pk_ty } } else { quote! { i64 } }; quote! { - impl<'a> canyon_sql::query::bounds::Inspectionable<'a> for #ty #ty_generics #where_clause { + impl #impl_generics canyon_sql::query::bounds::Inspectionable<'_> for #ty #ty_generics #where_clause { type PrimaryKeyType = #pk_assoc_ty; @@ -335,7 +335,7 @@ mod __details { #pk_opt_val } - fn primary_key_actual_value(&self) -> &'a (dyn canyon_sql::query::QueryParameter + 'a) { + fn primary_key_actual_value(&self) -> &'_ (dyn canyon_sql::query::QueryParameter + '_) { &#pk_actual_value } diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 6a52e206..963b72f2 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -1,4 +1,3 @@ -use crate::query_operations::consts; use crate::utils::macro_tokens::MacroTokens; use proc_macro2::{Ident, TokenStream}; use quote::{ToTokens, quote}; diff --git a/canyon_macros/src/utils/primary_key_attribute.rs b/canyon_macros/src/utils/primary_key_attribute.rs index 4b78f095..36b77102 100644 --- a/canyon_macros/src/utils/primary_key_attribute.rs +++ b/canyon_macros/src/utils/primary_key_attribute.rs @@ -1,7 +1,5 @@ use proc_macro2::Ident; -use proc_macro2::TokenStream; -use quote::__private::Span; -use quote::{ToTokens, quote}; +use quote::ToTokens; use std::fmt::{Display, Formatter}; use syn::{Field, Type}; @@ -15,34 +13,14 @@ impl<'a> Display for &'a PrimaryKeyAttribute<'a> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let _ = f.write_fmt(format_args!( "ident:{},ty:{},name:{}", - self.ident.to_string(), - self.ty.to_token_stream().to_string(), + self.ident, + self.ty.to_token_stream(), self.name )); Ok(()) } } -// impl<'a> Default for &'a PrimaryKeyAttribute<'a> { -// fn default() -> Self { -// Self { -// ident: &Ident::new_raw("Non existent", Span::call_site()), -// ty: &Type::Verbatim(quote!{}.into()), -// name: "".to_string(), -// } -// } -// } - -impl<'a> PrimaryKeyAttribute<'a> { - pub(crate) fn get_ident_as_token_stream(&self) -> TokenStream { - let ident = self.ident; - quote! { #ident } - } - pub(crate) fn get_type_as_token_stream(&self) -> TokenStream { - let ty = self.ty; - quote! { #ty } - } -} impl<'a> From<&'a Field> for PrimaryKeyAttribute<'a> { fn from(value: &'a Field) -> Self { Self { From 700bc3acc2b739cf99e52525351d0d2d2baf1c83 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Wed, 4 Jun 2025 16:22:05 +0200 Subject: [PATCH 151/155] fix: improper lifetime bounds on the delete methods of the CrudOperations trait --- canyon_crud/src/crud.rs | 20 +++++----- canyon_macros/src/query_operations/delete.rs | 42 ++++++++++---------- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index ccf98d55..0fe609af 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -229,30 +229,32 @@ where fn delete(&self) -> impl Future>> + Send; - fn delete_with<'a, I>( + fn delete_with<'a, 'b, I>( &self, input: I, - ) -> impl Future>> + Send + ) -> impl Future>> + Send where I: DbConnection + Send + 'a; - fn delete_entity<'a, T>( + fn delete_entity<'a, 'b, T>( entity: &'a T, - ) -> impl Future>> + ) -> impl Future>> where T: RowMapper + Inspectionable<'a> + Sync + 'a; - fn delete_entity_with<'a, T, I>( + fn delete_entity_with<'a, 'b, T, I>( entity: &'a T, input: I, - ) -> impl Future>> + ) -> impl Future>> where T: RowMapper + Inspectionable<'a> + Sync + 'a, I: DbConnection + Send + 'a; - fn delete_query<'a>() -> Result, Box<(dyn Error + Send + Sync + 'a)>>; + fn delete_query<'a, 'b>() -> Result, Box<(dyn Error + Send + Sync + 'b)>> + where 'a: 'b; - fn delete_query_with<'a>( + fn delete_query_with<'a, 'b>( database_type: DatabaseType, - ) -> Result, Box<(dyn Error + Send + Sync + 'a)>>; + ) -> Result, Box<(dyn Error + Send + Sync + 'b)>> + where 'a: 'b; } diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index 76161492..7542ffb8 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -36,8 +36,8 @@ pub fn generate_delete_method_tokens( /// Deletes from a database entity the row that matches /// the current instance of a T type, returning a result /// indicating a possible failure querying the database with the specified datasource. - async fn delete_with<'a, I>(&self, input: I) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'a)>> - where I: canyon_sql::connection::DbConnection + Send + 'a + async fn delete_with<'canyon, 'err, I>(&self, input: I) -> Result<(), Box<(dyn std::error::Error + Send + Sync + 'err)>> + where I: canyon_sql::connection::DbConnection + Send + 'canyon }; if let Some(primary_key) = pk { @@ -83,23 +83,23 @@ pub fn generate_delete_method_tokens( pub fn generate_delete_entity_tokens(table_schema_data: &str) -> TokenStream { let delete_entity_signature = quote! { - async fn delete_entity<'canyon_lt, Entity>(entity: &'canyon_lt Entity) - -> Result<(), Box> + async fn delete_entity<'canyon, 'err, Entity>(entity: &'canyon Entity) + -> Result<(), Box> where Entity: canyon_sql::core::RowMapper - + canyon_sql::query::bounds::Inspectionable<'canyon_lt> + + canyon_sql::query::bounds::Inspectionable<'canyon> + Sync - + 'canyon_lt + + 'canyon }; let delete_entity_with_signature = quote! { - async fn delete_entity_with<'canyon_lt, Entity, Input>(entity: &'canyon_lt Entity, input: Input) - -> Result<(), Box> + async fn delete_entity_with<'canyon, 'err, Entity, Input>(entity: &'canyon Entity, input: Input) + -> Result<(), Box> where Entity: canyon_sql::core::RowMapper - + canyon_sql::query::bounds::Inspectionable<'canyon_lt> + + canyon_sql::query::bounds::Inspectionable<'canyon> + Sync - + 'canyon_lt, - Input: canyon_sql::connection::DbConnection + Send + 'canyon_lt + + 'canyon, + Input: canyon_sql::connection::DbConnection + Send + 'canyon }; let delete_entity_body = __details::generate_delete_entity_body(table_schema_data); @@ -122,10 +122,11 @@ fn generate_delete_querybuilder_tokens(table_schema_data: &str) -> TokenStream { /// entity but converted to the corresponding database convention, /// unless concrete values are set on the available parameters of the /// `canyon_macro(table_name = "table_name", schema = "schema")` - fn delete_query<'a>() -> Result< - canyon_sql::query::querybuilder::DeleteQueryBuilder<'a>, - Box<(dyn std::error::Error + Send + Sync + 'a)> - > { + fn delete_query<'canyon, 'err>() -> Result< + canyon_sql::query::querybuilder::DeleteQueryBuilder<'canyon>, + Box<(dyn std::error::Error + Send + Sync + 'err)> + > where + 'canyon: 'err { canyon_sql::query::querybuilder::DeleteQueryBuilder::new(#table_schema_data, canyon_sql::connection::DatabaseType::default_type()?) } @@ -139,11 +140,12 @@ fn generate_delete_querybuilder_tokens(table_schema_data: &str) -> TokenStream { /// /// The query it's made against the database with the configured datasource /// described in the configuration file, selected with the input parameter - fn delete_query_with<'a>(database_type: canyon_sql::connection::DatabaseType) + fn delete_query_with<'canyon, 'err>(database_type: canyon_sql::connection::DatabaseType) -> Result< - canyon_sql::query::querybuilder::DeleteQueryBuilder<'a>, - Box<(dyn std::error::Error + Send + Sync + 'a)> - > { + canyon_sql::query::querybuilder::DeleteQueryBuilder<'canyon>, + Box<(dyn std::error::Error + Send + Sync + 'err)> + > where + 'canyon: 'err { canyon_sql::query::querybuilder::DeleteQueryBuilder::new(#table_schema_data, database_type) } } @@ -187,7 +189,7 @@ mod __details { fn generate_delete_entity_pk_body_logic(table_schema_data: &str) -> TokenStream { quote! { - // let pk_actual_value = &entity.primary_key_actual_value() as &dyn canyon_sql::query::QueryParameter<'canyon_lt>; + // let pk_actual_value = &entity.primary_key_actual_value() as &dyn canyon_sql::query::QueryParameter<'canyon>; let pk_actual_value = entity.primary_key_actual_value(); let delete_stmt = format!( "DELETE FROM {} WHERE {:?} = $1", From fc6af1318ac92c4d999e90821dd50e0f5ca95b96 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 6 Jun 2025 08:28:39 +0200 Subject: [PATCH 152/155] feat: FieldValues autogenerated enums takes the concrete type of the column, not QueryParameter anymore --- canyon_core/src/query/bounds.rs | 4 +- .../src/query/querybuilder/contracts/mod.rs | 6 +- .../src/query/querybuilder/impl/delete.rs | 8 +- .../src/query/querybuilder/impl/select.rs | 6 +- .../src/query/querybuilder/impl/update.rs | 6 +- .../src/query/querybuilder/types/mod.rs | 12 +- canyon_entities/src/entity.rs | 9 +- canyon_entities/src/manager_builder.rs | 12 +- tests/crud/hex_arch_example.rs | 10 +- tests/crud/querybuilder_operations.rs | 119 +++++++++++------- 10 files changed, 118 insertions(+), 74 deletions(-) diff --git a/canyon_core/src/query/bounds.rs b/canyon_core/src/query/bounds.rs index fdba324b..44c3b542 100644 --- a/canyon_core/src/query/bounds.rs +++ b/canyon_core/src/query/bounds.rs @@ -85,7 +85,7 @@ pub trait FieldIdentifier: std::fmt::Display { /// Ex: /// `SELECT * FROM some_table WHERE id = 2` /// -/// That '2' it's extracted from some enum that implements [`FieldValueIdentifier`], +/// That '2' it's extracted from some enum that implements [`FieldValueIdentifier<'a>`], /// where usually the variant w'd be something like: /// /// ``` @@ -94,7 +94,7 @@ pub trait FieldIdentifier: std::fmt::Display { /// } /// ``` pub trait FieldValueIdentifier<'a> { - fn value(self) -> (&'static str, &'a dyn QueryParameter<'a>); + fn value(&'a self) -> (&'static str, &'a dyn QueryParameter<'_>); } /// Bounds to some type T in order to make it callable over some fn parameter T diff --git a/canyon_core/src/query/querybuilder/contracts/mod.rs b/canyon_core/src/query/querybuilder/contracts/mod.rs index 45c75dd3..9492e172 100644 --- a/canyon_core/src/query/querybuilder/contracts/mod.rs +++ b/canyon_core/src/query/querybuilder/contracts/mod.rs @@ -126,7 +126,7 @@ pub trait QueryBuilderOps<'a> { /// column name and the value for the filter /// * `op` - Any element that implements [`Operator`] for create the comparison /// or equality binary operator - fn r#where>(self, column: Z, op: impl Operator) -> Self; + fn r#where>(self, column: &'a Z, op: impl Operator) -> Self; /// Generates an `AND` SQL clause for constraint the query. /// @@ -134,7 +134,7 @@ pub trait QueryBuilderOps<'a> { /// column name and the value for the filter /// * `op` - Any element that implements [`Operator`] for create the comparison /// or equality binary operator - fn and>(self, column: Z, op: impl Operator) -> Self; + fn and>(self, column: &'a Z, op: impl Operator) -> Self; /// Generates an `AND` SQL clause for constraint the query that's being constructed /// @@ -167,7 +167,7 @@ pub trait QueryBuilderOps<'a> { /// column name and the value for the filter /// * `op` - Any element that implements [`Operator`] for create the comparison /// or equality binary operator - fn or>(self, column: Z, op: impl Operator) -> Self; + fn or>(self, column: &'a Z, op: impl Operator) -> Self; /// Generates a `ORDER BY` SQL clause for constraint the query. /// diff --git a/canyon_core/src/query/querybuilder/impl/delete.rs b/canyon_core/src/query/querybuilder/impl/delete.rs index ceda2349..eb030ec7 100644 --- a/canyon_core/src/query/querybuilder/impl/delete.rs +++ b/canyon_core/src/query/querybuilder/impl/delete.rs @@ -18,13 +18,13 @@ impl<'a> QueryBuilderOps<'a> for DeleteQueryBuilder<'a> { } #[inline] - fn r#where>(mut self, r#where: Z, op: impl Operator) -> Self { + fn r#where>(mut self, r#where: &'a Z, op: impl Operator) -> Self { self._inner.r#where(r#where, op); self } #[inline] - fn and>(mut self, column: Z, op: impl Operator) -> Self { + fn and>(mut self, column: &'a Z, op: impl Operator) -> Self { self._inner.and(column, op); self } @@ -35,7 +35,7 @@ impl<'a> QueryBuilderOps<'a> for DeleteQueryBuilder<'a> { Z: FieldIdentifier, Q: QueryParameter<'a>, { - self._inner.or_values_in(and, values); + self._inner.and_values_in(and, values); self } @@ -50,7 +50,7 @@ impl<'a> QueryBuilderOps<'a> for DeleteQueryBuilder<'a> { } #[inline] - fn or>(mut self, column: Z, op: impl Operator) -> Self { + fn or>(mut self, column: &'a Z, op: impl Operator) -> Self { self._inner.or(column, op); self } diff --git a/canyon_core/src/query/querybuilder/impl/select.rs b/canyon_core/src/query/querybuilder/impl/select.rs index c19ab101..eff3897d 100644 --- a/canyon_core/src/query/querybuilder/impl/select.rs +++ b/canyon_core/src/query/querybuilder/impl/select.rs @@ -74,13 +74,13 @@ impl<'a> QueryBuilderOps<'a> for SelectQueryBuilder<'a> { } #[inline] - fn r#where>(mut self, r#where: Z, op: impl Operator) -> Self { + fn r#where>(mut self, r#where: &'a Z, op: impl Operator) -> Self { self._inner.r#where(r#where, op); self } #[inline] - fn and>(mut self, column: Z, op: impl Operator) -> Self { + fn and>(mut self, column: &'a Z, op: impl Operator) -> Self { self._inner.and(column, op); self } @@ -106,7 +106,7 @@ impl<'a> QueryBuilderOps<'a> for SelectQueryBuilder<'a> { } #[inline] - fn or>(mut self, column: Z, op: impl Operator) -> Self { + fn or>(mut self, column: &'a Z, op: impl Operator) -> Self { self._inner.or(column, op); self } diff --git a/canyon_core/src/query/querybuilder/impl/update.rs b/canyon_core/src/query/querybuilder/impl/update.rs index 556717d4..b1ebfec6 100644 --- a/canyon_core/src/query/querybuilder/impl/update.rs +++ b/canyon_core/src/query/querybuilder/impl/update.rs @@ -58,13 +58,13 @@ impl<'a> QueryBuilderOps<'a> for UpdateQueryBuilder<'a> { } #[inline] - fn r#where>(mut self, r#where: Z, op: impl Operator) -> Self { + fn r#where>(mut self, r#where: &'a Z, op: impl Operator) -> Self { self._inner.r#where(r#where, op); self } #[inline] - fn and>(mut self, column: Z, op: impl Operator) -> Self { + fn and>(mut self, column: &'a Z, op: impl Operator) -> Self { self._inner.and(column, op); self } @@ -90,7 +90,7 @@ impl<'a> QueryBuilderOps<'a> for UpdateQueryBuilder<'a> { } #[inline] - fn or>(mut self, column: Z, op: impl Operator) -> Self { + fn or>(mut self, column: &'a Z, op: impl Operator) -> Self { self._inner.or(column, op); self } diff --git a/canyon_core/src/query/querybuilder/types/mod.rs b/canyon_core/src/query/querybuilder/types/mod.rs index 8057ab41..675c20a7 100644 --- a/canyon_core/src/query/querybuilder/types/mod.rs +++ b/canyon_core/src/query/querybuilder/types/mod.rs @@ -38,7 +38,7 @@ impl<'a> QueryBuilder<'a> { Ok(Query::new(self.sql, self.params)) } - pub fn r#where>(&mut self, r#where: Z, op: impl Operator) { + pub fn r#where>(&mut self, r#where: &'a Z, op: impl Operator) { let (column_name, value) = r#where.value(); let where_ = String::from(" WHERE ") @@ -49,7 +49,7 @@ impl<'a> QueryBuilder<'a> { self.params.push(value); } - pub fn and>(&mut self, r#and: Z, op: impl Operator) { + pub fn and>(&mut self, r#and: &'a Z, op: impl Operator) { let (column_name, value) = r#and.value(); let and_ = String::from(" AND ") @@ -110,14 +110,14 @@ impl<'a> QueryBuilder<'a> { self.sql.push(')'); } - pub fn or>(&mut self, r#and: Z, op: impl Operator) { - let (column_name, value) = r#and.value(); + pub fn or>(&mut self, r#or: &'a Z, op: impl Operator) { + let (column_name, value) = r#or.value(); - let and_ = String::from(" OR ") + let or_ = String::from(" OR ") + column_name + &op.as_str(self.params.len() + 1, &self.database_type); - self.sql.push_str(&and_); + self.sql.push_str(&or_); self.params.push(value); } diff --git a/canyon_entities/src/entity.rs b/canyon_entities/src/entity.rs index e1fb3652..3381e9ed 100644 --- a/canyon_entities/src/entity.rs +++ b/canyon_entities/src/entity.rs @@ -44,13 +44,14 @@ impl CanyonEntity { /// which this enum is related to. /// /// Makes a variant `#field_name(#ty)` where `#ty` it's a trait object - /// of type `canyon_core::QueryParameter` + /// of type `canyon_core::QueryParameter` TODO: correct the comment when refactored pub fn get_fields_as_enum_variants_with_value(&self) -> Vec { self.fields .iter() .map(|f| { let field_name = &f.name; - quote! { #field_name(&'a dyn canyon_sql::query::QueryParameter<'a>) } + let field_ty = &f.field_type; + quote! { #field_name(#field_ty) } }) .collect::>() } @@ -91,7 +92,7 @@ impl CanyonEntity { /// Generates an implementation of the match pattern to find whatever variant /// is being requested when the method `.value()` it's invoked over some - /// instance that implements the `canyon_sql_root::crud::bounds::FieldValueIdentifier` trait + /// instance that implements the `canyon_sql_root::crud::bounds::FieldValueIdentifier<'a>` trait pub fn create_match_arm_for_relate_fields_with_values( &self, enum_name: &Ident, @@ -103,7 +104,7 @@ impl CanyonEntity { let field_name_as_string = f.name.to_string(); quote! { - #enum_name::#field_name(v) => (#field_name_as_string, v) + #enum_name::#field_name(v) => (#field_name_as_string, v as &dyn canyon_sql::query::QueryParameter<'_>) } }) .collect::>() diff --git a/canyon_entities/src/manager_builder.rs b/canyon_entities/src/manager_builder.rs index a41e3845..1c616c14 100644 --- a/canyon_entities/src/manager_builder.rs +++ b/canyon_entities/src/manager_builder.rs @@ -197,14 +197,16 @@ pub fn generate_enum_with_fields_values(canyon_entity: &CanyonEntity) -> TokenSt /// opt(Option) /// } /// ``` - #visibility enum #enum_name<'a> { - #(#fields_names),* + #visibility enum #enum_name<'field_value> { + #(#fields_names),*, + None(std::marker::PhantomData<&'field_value ()>) } - impl<'a> canyon_sql::query::bounds::FieldValueIdentifier<'a> for #enum_name<'a> { - fn value(self) -> (&'static str, &'a dyn canyon_sql::query::QueryParameter<'a>) { + impl<'field_value> canyon_sql::query::bounds::FieldValueIdentifier<'field_value> for #enum_name<'field_value> { + fn value(&'field_value self) -> (&'static str, &'field_value dyn canyon_sql::query::QueryParameter<'_>) { match self { - #(#match_arms),* + #(#match_arms),*, + _ => panic!() } } } diff --git a/tests/crud/hex_arch_example.rs b/tests/crud/hex_arch_example.rs index 84ed4067..246fa3b9 100644 --- a/tests/crud/hex_arch_example.rs +++ b/tests/crud/hex_arch_example.rs @@ -66,9 +66,17 @@ fn test_hex_arch_find_insert_ops() { let find_new_league = league_service.get(&other_league.id).await.unwrap(); assert!(find_new_league.is_some()); assert_eq!( - find_new_league.unwrap().name, + find_new_league.as_ref().unwrap().name, String::from("Test LeagueHex on layered") ); + + let mut updt = find_new_league.unwrap(); + updt.ext_id = 5; + let r = LeagueHexRepositoryAdapter::::update_entity(&updt).await; + assert!(r.is_ok()); + + let updated = league_service.get(&other_league.id).await.unwrap(); + assert_eq!(updated.unwrap().ext_id, 5) } #[derive(CanyonMapper, Debug)] diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index e530001b..ecad1ecd 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -4,6 +4,8 @@ use crate::constants::MYSQL_DS; use crate::constants::SQL_SERVER_DS; use canyon_sql::connection::DatabaseType; +use canyon_sql::query::querybuilder::DeleteQueryBuilder; + /// Tests for the QueryBuilder available operations within Canyon. /// /// QueryBuilder are the way of obtain more flexibility that with @@ -31,6 +33,7 @@ use crate::tests_models::tournament::*; /// with the parameters that modifies the base SQL to SELECT * FROM #[canyon_sql::macros::canyon_tokio_test] fn test_generated_sql_by_the_select_querybuilder() { + let fv = LeagueFieldValue::name("KOREA".to_string()); let select_with_joins = League::select_query() .unwrap() .inner_join( @@ -39,8 +42,8 @@ fn test_generated_sql_by_the_select_querybuilder() { TournamentField::league, ) .left_join(PlayerTable::DbName, TournamentField::id, PlayerField::id) - .r#where(LeagueFieldValue::id(&7), Comp::Gt) - .and(LeagueFieldValue::name(&"KOREA"), Comp::Eq) + .r#where(&LeagueFieldValue::id(7), Comp::Gt) + .and(&fv, Comp::Eq) .and_values_in(LeagueField::name, &["LCK", "STRANGER THINGS"]); // NOTE: We don't have in the docker the generated relationships // with the joins, so for now, we are just going to check that the @@ -58,10 +61,11 @@ fn test_generated_sql_by_the_select_querybuilder() { fn test_crud_find_with_querybuilder() { // Find all the leagues with ID less or equals that 7 // and where it's region column value is equals to 'Korea' + let fv = LeagueFieldValue::region("KOREA".to_string()); let filtered_leagues_result: Result, _> = League::select_query() .unwrap() - .r#where(LeagueFieldValue::id(&50), Comp::LtEq) - .and(LeagueFieldValue::region(&"KOREA"), Comp::Eq) + .r#where(&LeagueFieldValue::id(50), Comp::LtEq) + .and(&fv, Comp::Eq) .build() .unwrap() .launch_default() @@ -81,9 +85,10 @@ fn test_crud_find_with_querybuilder() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_fulllike() { // Find all the leagues with "LC" in their name + let binding = LeagueFieldValue::name("LEC".to_string()); let filtered_leagues_result = League::select_query() .unwrap() - .r#where(LeagueFieldValue::name(&"LC"), Like::Full); + .r#where(&binding, Like::Full); assert_eq!( filtered_leagues_result.read_sql(), @@ -97,9 +102,10 @@ fn test_crud_find_with_querybuilder_and_fulllike() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_fulllike_with_mssql() { // Find all the leagues with "LC" in their name + let fv = LeagueFieldValue::name("LEC".to_string()); let filtered_leagues_result = League::select_query_with(DatabaseType::SqlServer) .unwrap() - .r#where(LeagueFieldValue::name(&"LC"), Like::Full); + .r#where(&fv, Like::Full); assert_eq!( filtered_leagues_result.read_sql(), @@ -113,9 +119,10 @@ fn test_crud_find_with_querybuilder_and_fulllike_with_mssql() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_fulllike_with_mysql() { // Find all the leagues with "LC" in their name + let fv = LeagueFieldValue::name("LEC".to_string()); let filtered_leagues_result = League::select_query_with(DatabaseType::MySQL) .unwrap() - .r#where(LeagueFieldValue::name(&"LC"), Like::Full); + .r#where(&fv, Like::Full); assert_eq!( filtered_leagues_result.read_sql(), @@ -129,9 +136,10 @@ fn test_crud_find_with_querybuilder_and_fulllike_with_mysql() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_leftlike() { // Find all the leagues whose name ends with "CK" + let fv = LeagueFieldValue::name("CK".to_string()); let filtered_leagues_result = League::select_query() .unwrap() - .r#where(LeagueFieldValue::name(&"CK"), Like::Left); + .r#where(&fv, Like::Left); assert_eq!( filtered_leagues_result.read_sql(), @@ -145,9 +153,10 @@ fn test_crud_find_with_querybuilder_and_leftlike() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_leftlike_with_mssql() { // Find all the leagues whose name ends with "CK" + let fv = LeagueFieldValue::name("CK".to_string()); let filtered_leagues_result = League::select_query_with(DatabaseType::SqlServer) .unwrap() - .r#where(LeagueFieldValue::name(&"CK"), Like::Left); + .r#where(&fv, Like::Left); assert_eq!( filtered_leagues_result.read_sql(), @@ -161,9 +170,10 @@ fn test_crud_find_with_querybuilder_and_leftlike_with_mssql() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_leftlike_with_mysql() { // Find all the leagues whose name ends with "CK" + let fv = LeagueFieldValue::name("CK".to_string()); let filtered_leagues_result = League::select_query_with(DatabaseType::MySQL) .unwrap() - .r#where(LeagueFieldValue::name(&"CK"), Like::Left); + .r#where(&fv, Like::Left); assert_eq!( filtered_leagues_result.read_sql(), @@ -177,9 +187,10 @@ fn test_crud_find_with_querybuilder_and_leftlike_with_mysql() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_rightlike() { // Find all the leagues whose name starts with "LC" + let fv = LeagueFieldValue::name("LEC".to_string()); let filtered_leagues_result = League::select_query() .unwrap() - .r#where(LeagueFieldValue::name(&"LC"), Like::Right); + .r#where(&fv, Like::Right); assert_eq!( filtered_leagues_result.read_sql(), @@ -193,9 +204,10 @@ fn test_crud_find_with_querybuilder_and_rightlike() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_rightlike_with_mssql() { // Find all the leagues whose name starts with "LC" + let fv = LeagueFieldValue::name("LEC".to_string()); let filtered_leagues_result = League::select_query_with(DatabaseType::SqlServer) .unwrap() - .r#where(LeagueFieldValue::name(&"LC"), Like::Right); + .r#where(&fv, Like::Right); assert_eq!( filtered_leagues_result.read_sql(), @@ -209,9 +221,10 @@ fn test_crud_find_with_querybuilder_and_rightlike_with_mssql() { #[canyon_sql::macros::canyon_tokio_test] fn test_crud_find_with_querybuilder_and_rightlike_with_mysql() { // Find all the leagues whose name starts with "LC" + let wh = LeagueFieldValue::name("LEC".to_string()); let filtered_leagues_result = League::select_query_with(DatabaseType::MySQL) .unwrap() - .r#where(LeagueFieldValue::name(&"LC"), Like::Right); + .r#where(&wh, Like::Right); assert_eq!( filtered_leagues_result.read_sql(), @@ -226,7 +239,7 @@ fn test_crud_find_with_querybuilder_with_mssql() { // Find all the players where its ID column value is greater than 50 let filtered_find_players = Player::select_query_with(DatabaseType::SqlServer) .unwrap() - .r#where(PlayerFieldValue::id(&50), Comp::Gt) + .r#where(&PlayerFieldValue::id(50), Comp::Gt) .build() .unwrap() .launch_with::<&str, Player>(SQL_SERVER_DS) @@ -242,7 +255,7 @@ fn test_crud_find_with_querybuilder_with_mysql() { // Find all the players where its ID column value is greater than 50 let filtered_find_players = Player::select_query_with(DatabaseType::MySQL) .unwrap() - .r#where(PlayerFieldValue::id(&50), Comp::Gt) + .r#where(&PlayerFieldValue::id(50), Comp::Gt) .build() .unwrap() .launch_with::<&str, Player>(MYSQL_DS) @@ -264,16 +277,16 @@ fn test_crud_update_with_querybuilder() { (LeagueField::slug, "Updated with the QueryBuilder"), (LeagueField::name, "Random"), ]) - .r#where(LeagueFieldValue::id(&1), Comp::Gt) - .and(LeagueFieldValue::id(&8), Comp::Lt); + .r#where(&LeagueFieldValue::id(1), Comp::Gt) + .and(&LeagueFieldValue::id(8), Comp::Lt); q.build() .expect("Failed to update records with the querybuilder"); let found_updated_values = League::select_query() .unwrap() - .r#where(LeagueFieldValue::id(&1), Comp::Gt) - .and(LeagueFieldValue::id(&7), Comp::Lt) + .r#where(&LeagueFieldValue::id(1), Comp::Gt) + .and(&LeagueFieldValue::id(7), Comp::Lt) .build() .unwrap() .launch_default::() @@ -296,8 +309,8 @@ fn test_crud_update_with_querybuilder_with_mssql() { (PlayerField::summoner_name, "Random updated player name"), (PlayerField::first_name, "I am an updated first name"), ]) - .r#where(PlayerFieldValue::id(&1), Comp::Gt) - .and(PlayerFieldValue::id(&8), Comp::Lt) + .r#where(&PlayerFieldValue::id(1), Comp::Gt) + .and(&PlayerFieldValue::id(8), Comp::Lt) .build() .unwrap() .launch_with::<&str, Player>(SQL_SERVER_DS) @@ -306,8 +319,8 @@ fn test_crud_update_with_querybuilder_with_mssql() { let found_updated_values = Player::select_query_with(DatabaseType::SqlServer) .unwrap() - .r#where(PlayerFieldValue::id(&1), Comp::Gt) - .and(PlayerFieldValue::id(&7), Comp::LtEq) + .r#where(&PlayerFieldValue::id(1), Comp::Gt) + .and(&PlayerFieldValue::id(7), Comp::LtEq) .build() .unwrap() .launch_with::<&str, Player>(SQL_SERVER_DS) @@ -332,8 +345,8 @@ fn test_crud_update_with_querybuilder_with_mysql() { (PlayerField::summoner_name, "Random updated player name"), (PlayerField::first_name, "I am an updated first name"), ]) - .r#where(PlayerFieldValue::id(&1), Comp::Gt) - .and(PlayerFieldValue::id(&8), Comp::Lt) + .r#where(&PlayerFieldValue::id(1), Comp::Gt) + .and(&PlayerFieldValue::id(8), Comp::Lt) .build() .unwrap() .launch_with::<&str, Player>(MYSQL_DS) @@ -342,8 +355,8 @@ fn test_crud_update_with_querybuilder_with_mysql() { let found_updated_values = Player::select_query_with(DatabaseType::MySQL) .unwrap() - .r#where(PlayerFieldValue::id(&1), Comp::Gt) - .and(PlayerFieldValue::id(&7), Comp::LtEq) + .r#where(&PlayerFieldValue::id(1), Comp::Gt) + .and(&PlayerFieldValue::id(7), Comp::LtEq) .build() .unwrap() .launch_with::<&str, Player>(MYSQL_DS) @@ -367,8 +380,8 @@ fn test_crud_update_with_querybuilder_with_mysql() { fn test_crud_delete_with_querybuilder() { Tournament::delete_query() .unwrap() - .r#where(TournamentFieldValue::id(&14), Comp::Gt) - .and(TournamentFieldValue::id(&16), Comp::Lt) + .r#where(&TournamentFieldValue::id(14), Comp::Gt) + .and(&TournamentFieldValue::id(16), Comp::Lt) .build() .unwrap() .launch_default::() @@ -378,14 +391,28 @@ fn test_crud_delete_with_querybuilder() { assert_eq!(Tournament::find_by_pk(&15).await.unwrap(), None); } +// #[cfg(feature = "postgres")] +// #[canyon_sql::macros::canyon_tokio_test] +// fn test_crud_delete_with_querybuilder_lt_creation() { +// let q = create_querybuilder_lt(10); +// assert_eq!(q.read_sql(), "DELETE FROM tournament WHERE id = 10"); +// } +// +// #[cfg(feature = "postgres")] +// fn create_querybuilder_lt<'a, 'b: 'a>(id: i32) -> DeleteQueryBuilder<'b> { +// Tournament::delete_query() +// .unwrap() +// .r#where(&TournamentFieldValue::id(id), Comp::Gt) +// } + /// Same as the above delete, but with the specified datasource #[cfg(feature = "mssql")] #[canyon_sql::macros::canyon_tokio_test] fn test_crud_delete_with_querybuilder_with_mssql() { Player::delete_query_with(DatabaseType::SqlServer) .unwrap() - .r#where(PlayerFieldValue::id(&120), Comp::Gt) - .and(PlayerFieldValue::id(&130), Comp::Lt) + .r#where(&PlayerFieldValue::id(120), Comp::Gt) + .and(&PlayerFieldValue::id(130), Comp::Lt) .build() .unwrap() .launch_with::<&str, Player>(SQL_SERVER_DS) @@ -395,7 +422,7 @@ fn test_crud_delete_with_querybuilder_with_mssql() { assert!( Player::select_query_with(DatabaseType::SqlServer) .unwrap() - .r#where(PlayerFieldValue::id(&122), Comp::Eq) + .r#where(&PlayerFieldValue::id(122), Comp::Eq) .build() .unwrap() .launch_with::<&str, Player>(SQL_SERVER_DS) @@ -411,8 +438,8 @@ fn test_crud_delete_with_querybuilder_with_mssql() { fn test_crud_delete_with_querybuilder_with_mysql() { Player::delete_query_with(DatabaseType::MySQL) .unwrap() - .r#where(PlayerFieldValue::id(&120), Comp::Gt) - .and(PlayerFieldValue::id(&130), Comp::Lt) + .r#where(&PlayerFieldValue::id(120), Comp::Gt) + .and(&PlayerFieldValue::id(130), Comp::Lt) .build() .unwrap() .launch_with::<&str, Player>(MYSQL_DS) @@ -422,7 +449,7 @@ fn test_crud_delete_with_querybuilder_with_mysql() { assert!( Player::select_query_with(DatabaseType::MySQL) .unwrap() - .r#where(PlayerFieldValue::id(&122), Comp::Eq) + .r#where(&PlayerFieldValue::id(122), Comp::Eq) .build() .unwrap() .launch_with::<&str, Player>(MYSQL_DS) @@ -436,9 +463,10 @@ fn test_crud_delete_with_querybuilder_with_mysql() { /// WHERE clause #[canyon_sql::macros::canyon_tokio_test] fn test_where_clause() { + let wh = LeagueFieldValue::name("LEC".to_string()); let l = League::select_query() .unwrap() - .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq); + .r#where(&wh, Comp::Eq); assert_eq!(l.read_sql(), "SELECT * FROM league WHERE name = $1") } @@ -447,10 +475,11 @@ fn test_where_clause() { /// AND clause #[canyon_sql::macros::canyon_tokio_test] fn test_and_clause() { + let wh = LeagueFieldValue::name("LEC".to_string()); let l = League::select_query() .unwrap() - .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) - .and(LeagueFieldValue::id(&10), Comp::LtEq); + .r#where(&wh, Comp::Eq) + .and(&LeagueFieldValue::id(10), Comp::LtEq); assert_eq!( l.read_sql().trim(), @@ -462,9 +491,10 @@ fn test_and_clause() { /// AND clause #[canyon_sql::macros::canyon_tokio_test] fn test_and_clause_with_in_constraint() { + let wh = LeagueFieldValue::name("LEC".to_string()); let l = League::select_query() .unwrap() - .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) + .r#where(&wh, Comp::Eq) .and_values_in(LeagueField::id, &[1, 7, 10]); assert_eq!( @@ -477,10 +507,11 @@ fn test_and_clause_with_in_constraint() { /// AND clause #[canyon_sql::macros::canyon_tokio_test] fn test_or_clause() { + let wh = LeagueFieldValue::name("LEC".to_string()); let l = League::select_query() .unwrap() - .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) - .or(LeagueFieldValue::id(&10), Comp::LtEq); + .r#where(&wh, Comp::Eq) + .or(&LeagueFieldValue::id(10), Comp::LtEq); assert_eq!( l.read_sql().trim(), @@ -492,9 +523,10 @@ fn test_or_clause() { /// AND clause #[canyon_sql::macros::canyon_tokio_test] fn test_or_clause_with_in_constraint() { + let wh = LeagueFieldValue::name("LEC".to_string()); let l = League::select_query() .unwrap() - .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) + .r#where(&wh, Comp::Eq) .or_values_in(LeagueField::id, &[1, 7, 10]); assert_eq!( @@ -507,9 +539,10 @@ fn test_or_clause_with_in_constraint() { /// AND clause #[canyon_sql::macros::canyon_tokio_test] fn test_order_by_clause() { + let fv = LeagueFieldValue::name("LEC".to_string()); let l = League::select_query() .unwrap() - .r#where(LeagueFieldValue::name(&"LEC"), Comp::Eq) + .r#where(&fv, Comp::Eq) .order_by(LeagueField::id, false); assert_eq!( From 73c68d894430a2970fa938da5bb0cb71350b5762 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 6 Jun 2025 08:34:14 +0200 Subject: [PATCH 153/155] chore: simplified the implementation and definition of FieldValueIdentifier --- canyon_core/src/query/bounds.rs | 6 +++--- canyon_core/src/query/querybuilder/contracts/mod.rs | 6 +++--- canyon_core/src/query/querybuilder/impl/delete.rs | 6 +++--- canyon_core/src/query/querybuilder/impl/select.rs | 6 +++--- canyon_core/src/query/querybuilder/impl/update.rs | 6 +++--- canyon_core/src/query/querybuilder/types/mod.rs | 6 +++--- canyon_entities/src/entity.rs | 2 +- canyon_entities/src/manager_builder.rs | 12 +++++------- tests/crud/querybuilder_operations.rs | 2 +- 9 files changed, 25 insertions(+), 27 deletions(-) diff --git a/canyon_core/src/query/bounds.rs b/canyon_core/src/query/bounds.rs index 44c3b542..b3a57f80 100644 --- a/canyon_core/src/query/bounds.rs +++ b/canyon_core/src/query/bounds.rs @@ -85,7 +85,7 @@ pub trait FieldIdentifier: std::fmt::Display { /// Ex: /// `SELECT * FROM some_table WHERE id = 2` /// -/// That '2' it's extracted from some enum that implements [`FieldValueIdentifier<'a>`], +/// That '2' it's extracted from some enum that implements [`FieldValueIdentifier`], /// where usually the variant w'd be something like: /// /// ``` @@ -93,8 +93,8 @@ pub trait FieldIdentifier: std::fmt::Display { /// IntVariant(i32) /// } /// ``` -pub trait FieldValueIdentifier<'a> { - fn value(&'a self) -> (&'static str, &'a dyn QueryParameter<'_>); +pub trait FieldValueIdentifier { + fn value(&self) -> (&'static str, &dyn QueryParameter<'_>); } /// Bounds to some type T in order to make it callable over some fn parameter T diff --git a/canyon_core/src/query/querybuilder/contracts/mod.rs b/canyon_core/src/query/querybuilder/contracts/mod.rs index 9492e172..ccebd863 100644 --- a/canyon_core/src/query/querybuilder/contracts/mod.rs +++ b/canyon_core/src/query/querybuilder/contracts/mod.rs @@ -126,7 +126,7 @@ pub trait QueryBuilderOps<'a> { /// column name and the value for the filter /// * `op` - Any element that implements [`Operator`] for create the comparison /// or equality binary operator - fn r#where>(self, column: &'a Z, op: impl Operator) -> Self; + fn r#where(self, column: &'a Z, op: impl Operator) -> Self; /// Generates an `AND` SQL clause for constraint the query. /// @@ -134,7 +134,7 @@ pub trait QueryBuilderOps<'a> { /// column name and the value for the filter /// * `op` - Any element that implements [`Operator`] for create the comparison /// or equality binary operator - fn and>(self, column: &'a Z, op: impl Operator) -> Self; + fn and(self, column: &'a Z, op: impl Operator) -> Self; /// Generates an `AND` SQL clause for constraint the query that's being constructed /// @@ -167,7 +167,7 @@ pub trait QueryBuilderOps<'a> { /// column name and the value for the filter /// * `op` - Any element that implements [`Operator`] for create the comparison /// or equality binary operator - fn or>(self, column: &'a Z, op: impl Operator) -> Self; + fn or(self, column: &'a Z, op: impl Operator) -> Self; /// Generates a `ORDER BY` SQL clause for constraint the query. /// diff --git a/canyon_core/src/query/querybuilder/impl/delete.rs b/canyon_core/src/query/querybuilder/impl/delete.rs index eb030ec7..8838e03c 100644 --- a/canyon_core/src/query/querybuilder/impl/delete.rs +++ b/canyon_core/src/query/querybuilder/impl/delete.rs @@ -18,13 +18,13 @@ impl<'a> QueryBuilderOps<'a> for DeleteQueryBuilder<'a> { } #[inline] - fn r#where>(mut self, r#where: &'a Z, op: impl Operator) -> Self { + fn r#where(mut self, r#where: &'a Z, op: impl Operator) -> Self { self._inner.r#where(r#where, op); self } #[inline] - fn and>(mut self, column: &'a Z, op: impl Operator) -> Self { + fn and(mut self, column: &'a Z, op: impl Operator) -> Self { self._inner.and(column, op); self } @@ -50,7 +50,7 @@ impl<'a> QueryBuilderOps<'a> for DeleteQueryBuilder<'a> { } #[inline] - fn or>(mut self, column: &'a Z, op: impl Operator) -> Self { + fn or(mut self, column: &'a Z, op: impl Operator) -> Self { self._inner.or(column, op); self } diff --git a/canyon_core/src/query/querybuilder/impl/select.rs b/canyon_core/src/query/querybuilder/impl/select.rs index eff3897d..e93c2879 100644 --- a/canyon_core/src/query/querybuilder/impl/select.rs +++ b/canyon_core/src/query/querybuilder/impl/select.rs @@ -74,13 +74,13 @@ impl<'a> QueryBuilderOps<'a> for SelectQueryBuilder<'a> { } #[inline] - fn r#where>(mut self, r#where: &'a Z, op: impl Operator) -> Self { + fn r#where(mut self, r#where: &'a Z, op: impl Operator) -> Self { self._inner.r#where(r#where, op); self } #[inline] - fn and>(mut self, column: &'a Z, op: impl Operator) -> Self { + fn and(mut self, column: &'a Z, op: impl Operator) -> Self { self._inner.and(column, op); self } @@ -106,7 +106,7 @@ impl<'a> QueryBuilderOps<'a> for SelectQueryBuilder<'a> { } #[inline] - fn or>(mut self, column: &'a Z, op: impl Operator) -> Self { + fn or(mut self, column: &'a Z, op: impl Operator) -> Self { self._inner.or(column, op); self } diff --git a/canyon_core/src/query/querybuilder/impl/update.rs b/canyon_core/src/query/querybuilder/impl/update.rs index b1ebfec6..62372483 100644 --- a/canyon_core/src/query/querybuilder/impl/update.rs +++ b/canyon_core/src/query/querybuilder/impl/update.rs @@ -58,13 +58,13 @@ impl<'a> QueryBuilderOps<'a> for UpdateQueryBuilder<'a> { } #[inline] - fn r#where>(mut self, r#where: &'a Z, op: impl Operator) -> Self { + fn r#where(mut self, r#where: &'a Z, op: impl Operator) -> Self { self._inner.r#where(r#where, op); self } #[inline] - fn and>(mut self, column: &'a Z, op: impl Operator) -> Self { + fn and(mut self, column: &'a Z, op: impl Operator) -> Self { self._inner.and(column, op); self } @@ -90,7 +90,7 @@ impl<'a> QueryBuilderOps<'a> for UpdateQueryBuilder<'a> { } #[inline] - fn or>(mut self, column: &'a Z, op: impl Operator) -> Self { + fn or(mut self, column: &'a Z, op: impl Operator) -> Self { self._inner.or(column, op); self } diff --git a/canyon_core/src/query/querybuilder/types/mod.rs b/canyon_core/src/query/querybuilder/types/mod.rs index 675c20a7..1017a5aa 100644 --- a/canyon_core/src/query/querybuilder/types/mod.rs +++ b/canyon_core/src/query/querybuilder/types/mod.rs @@ -38,7 +38,7 @@ impl<'a> QueryBuilder<'a> { Ok(Query::new(self.sql, self.params)) } - pub fn r#where>(&mut self, r#where: &'a Z, op: impl Operator) { + pub fn r#where(&mut self, r#where: &'a Z, op: impl Operator) { let (column_name, value) = r#where.value(); let where_ = String::from(" WHERE ") @@ -49,7 +49,7 @@ impl<'a> QueryBuilder<'a> { self.params.push(value); } - pub fn and>(&mut self, r#and: &'a Z, op: impl Operator) { + pub fn and(&mut self, r#and: &'a Z, op: impl Operator) { let (column_name, value) = r#and.value(); let and_ = String::from(" AND ") @@ -110,7 +110,7 @@ impl<'a> QueryBuilder<'a> { self.sql.push(')'); } - pub fn or>(&mut self, r#or: &'a Z, op: impl Operator) { + pub fn or(&mut self, r#or: &'a Z, op: impl Operator) { let (column_name, value) = r#or.value(); let or_ = String::from(" OR ") diff --git a/canyon_entities/src/entity.rs b/canyon_entities/src/entity.rs index 3381e9ed..0b1b83a7 100644 --- a/canyon_entities/src/entity.rs +++ b/canyon_entities/src/entity.rs @@ -92,7 +92,7 @@ impl CanyonEntity { /// Generates an implementation of the match pattern to find whatever variant /// is being requested when the method `.value()` it's invoked over some - /// instance that implements the `canyon_sql_root::crud::bounds::FieldValueIdentifier<'a>` trait + /// instance that implements the `canyon_sql_root::crud::bounds::FieldValueIdentifier` trait pub fn create_match_arm_for_relate_fields_with_values( &self, enum_name: &Ident, diff --git a/canyon_entities/src/manager_builder.rs b/canyon_entities/src/manager_builder.rs index 1c616c14..f600b38d 100644 --- a/canyon_entities/src/manager_builder.rs +++ b/canyon_entities/src/manager_builder.rs @@ -197,16 +197,14 @@ pub fn generate_enum_with_fields_values(canyon_entity: &CanyonEntity) -> TokenSt /// opt(Option) /// } /// ``` - #visibility enum #enum_name<'field_value> { - #(#fields_names),*, - None(std::marker::PhantomData<&'field_value ()>) + #visibility enum #enum_name { + #(#fields_names),* } - impl<'field_value> canyon_sql::query::bounds::FieldValueIdentifier<'field_value> for #enum_name<'field_value> { - fn value(&'field_value self) -> (&'static str, &'field_value dyn canyon_sql::query::QueryParameter<'_>) { + impl canyon_sql::query::bounds::FieldValueIdentifier for #enum_name { + fn value(&self) -> (&'static str, &dyn canyon_sql::query::QueryParameter<'_>) { match self { - #(#match_arms),*, - _ => panic!() + #(#match_arms),* } } } diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index ecad1ecd..306b3901 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -397,7 +397,7 @@ fn test_crud_delete_with_querybuilder() { // let q = create_querybuilder_lt(10); // assert_eq!(q.read_sql(), "DELETE FROM tournament WHERE id = 10"); // } -// +// // #[cfg(feature = "postgres")] // fn create_querybuilder_lt<'a, 'b: 'a>(id: i32) -> DeleteQueryBuilder<'b> { // Tournament::delete_query() From 463eab8d6b36f0b401e0a22e6bb57c71903a6022 Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 6 Jun 2025 08:46:24 +0200 Subject: [PATCH 154/155] chore: simplified the implementation and definition of QueryParameter, that now does not have a lifetime bound --- README.md | 2 +- canyon_core/src/connection/clients/mssql.rs | 17 ++-- canyon_core/src/connection/clients/mysql.rs | 18 ++-- .../src/connection/clients/postgresql.rs | 12 +-- .../contracts/impl/database_connection.rs | 30 +++---- .../src/connection/contracts/impl/mod.rs | 12 +-- .../src/connection/contracts/impl/mssql.rs | 10 +-- .../src/connection/contracts/impl/mysql.rs | 10 +-- .../connection/contracts/impl/postgresql.rs | 10 +-- .../src/connection/contracts/impl/str.rs | 10 +-- canyon_core/src/connection/contracts/mod.rs | 10 +-- canyon_core/src/query/bounds.rs | 10 +-- canyon_core/src/query/parameters.rs | 83 +++++++++---------- canyon_core/src/query/query.rs | 4 +- .../src/query/querybuilder/contracts/mod.rs | 6 +- .../src/query/querybuilder/impl/delete.rs | 4 +- .../src/query/querybuilder/impl/select.rs | 4 +- .../src/query/querybuilder/impl/update.rs | 6 +- .../src/query/querybuilder/types/mod.rs | 6 +- canyon_core/src/transaction.rs | 10 +-- canyon_crud/src/crud.rs | 10 ++- canyon_entities/src/entity.rs | 2 +- canyon_entities/src/manager_builder.rs | 2 +- canyon_macros/src/canyon_mapper_macro.rs | 2 +- canyon_macros/src/foreignkeyable_macro.rs | 6 +- canyon_macros/src/query_operations/delete.rs | 5 +- .../src/query_operations/foreign_key.rs | 4 +- canyon_macros/src/query_operations/insert.rs | 14 ++-- canyon_macros/src/query_operations/read.rs | 4 +- canyon_macros/src/query_operations/update.rs | 4 +- tests/crud/delete_operations.rs | 6 +- tests/crud/hex_arch_example.rs | 10 +-- tests/crud/querybuilder_operations.rs | 12 +-- 33 files changed, 173 insertions(+), 182 deletions(-) diff --git a/README.md b/README.md index 96e3b90b..09fae0b8 100755 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ assert_eq!( ); ``` -Note the leading reference on the `find_by_pk(...)` parameter. This associated function receives an `&dyn QueryParameter<'_>` as argument, not a value. +Note the leading reference on the `find_by_pk(...)` parameter. This associated function receives an `&dyn QueryParameter` as argument, not a value. ### :wrench: Building more complex queries diff --git a/canyon_core/src/connection/clients/mssql.rs b/canyon_core/src/connection/clients/mssql.rs index 49e4e619..a80c9251 100644 --- a/canyon_core/src/connection/clients/mssql.rs +++ b/canyon_core/src/connection/clients/mssql.rs @@ -20,7 +20,7 @@ pub(crate) mod sqlserver_query_launcher { #[inline(always)] pub(crate) async fn query<'a, S, R>( stmt: S, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], conn: &SqlServerConnection, ) -> Result, Box<(dyn Error + Send + Sync)>> where @@ -41,7 +41,7 @@ pub(crate) mod sqlserver_query_launcher { #[inline(always)] pub(crate) async fn query_rows<'a>( stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], conn: &SqlServerConnection, ) -> Result> { let result = execute_query(stmt, params, conn) @@ -57,7 +57,7 @@ pub(crate) mod sqlserver_query_launcher { pub(crate) async fn query_one<'a, R>( stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], conn: &SqlServerConnection, ) -> Result, Box<(dyn Error + Send + Sync)>> where @@ -73,7 +73,7 @@ pub(crate) mod sqlserver_query_launcher { pub(crate) async fn query_one_for<'a, T: FromSqlOwnedValue>( stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], conn: &SqlServerConnection, ) -> Result> { let row = execute_query(stmt, params, conn) @@ -93,7 +93,7 @@ pub(crate) mod sqlserver_query_launcher { pub(crate) async fn execute<'a>( stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], conn: &SqlServerConnection, ) -> Result> { let mssql_query = generate_mssql_stmt(stmt, params).await; @@ -111,7 +111,7 @@ pub(crate) mod sqlserver_query_launcher { async fn execute_query<'a>( stmt: &str, - params: &[&'a (dyn QueryParameter<'a>)], + params: &[&'a (dyn QueryParameter)], conn: &SqlServerConnection, ) -> Result, Box<(dyn Error + Send + Sync)>> { let mssql_query = generate_mssql_stmt(stmt, params).await; @@ -122,10 +122,7 @@ pub(crate) mod sqlserver_query_launcher { Ok(mssql_query.query(sqlservconn.client).await?) } - async fn generate_mssql_stmt<'a>( - stmt: &str, - params: &[&'a (dyn QueryParameter<'a>)], - ) -> Query<'a> { + async fn generate_mssql_stmt<'a>(stmt: &str, params: &[&'a (dyn QueryParameter)]) -> Query<'a> { let mut stmt = String::from(stmt); if stmt.contains("RETURNING") { let c = stmt.clone(); diff --git a/canyon_core/src/connection/clients/mysql.rs b/canyon_core/src/connection/clients/mysql.rs index ab795241..fa6864ea 100644 --- a/canyon_core/src/connection/clients/mysql.rs +++ b/canyon_core/src/connection/clients/mysql.rs @@ -32,7 +32,7 @@ pub(crate) mod mysql_query_launcher { #[inline(always)] pub async fn query( stmt: S, - params: &[&'_ dyn QueryParameter<'_>], + params: &[&'_ dyn QueryParameter], conn: &MysqlConnection, ) -> Result, Box<(dyn Error + Send + Sync)>> where @@ -50,7 +50,7 @@ pub(crate) mod mysql_query_launcher { #[inline(always)] pub(crate) async fn query_rows<'a>( stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], conn: &MysqlConnection, ) -> Result> { Ok(CanyonRows::MySQL(execute_query(stmt, params, conn).await?)) @@ -59,7 +59,7 @@ pub(crate) mod mysql_query_launcher { #[inline(always)] pub(crate) async fn query_one<'a, R>( stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], conn: &MysqlConnection, ) -> Result, Box<(dyn Error + Send + Sync)>> where @@ -76,7 +76,7 @@ pub(crate) mod mysql_query_launcher { #[inline(always)] pub(crate) async fn query_one_for<'a, T: FromSqlOwnedValue>( stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], conn: &MysqlConnection, ) -> Result> { Ok(execute_query(stmt, params, conn) @@ -91,7 +91,7 @@ pub(crate) mod mysql_query_launcher { #[inline(always)] async fn execute_query( stmt: S, - params: &[&'_ dyn QueryParameter<'_>], + params: &[&'_ dyn QueryParameter], conn: &MysqlConnection, ) -> Result, Box<(dyn Error + Send + Sync)>> where @@ -121,7 +121,7 @@ pub(crate) mod mysql_query_launcher { pub(crate) async fn execute( stmt: S, - params: &[&'_ dyn QueryParameter<'_>], + params: &[&'_ dyn QueryParameter], conn: &MysqlConnection, ) -> Result> where @@ -136,7 +136,7 @@ pub(crate) mod mysql_query_launcher { #[cfg(feature = "mysql")] fn generate_mysql_stmt( stmt: &str, - params: &[&'_ dyn QueryParameter<'_>], + params: &[&'_ dyn QueryParameter], ) -> Result>, Box> { let stmt_with_escape_characters = regex::escape(stmt); let query_string = @@ -162,8 +162,8 @@ pub(crate) mod mysql_query_launcher { #[cfg(feature = "mysql")] fn reorder_params( stmt: &str, - params: &[&'_ dyn QueryParameter<'_>], - fn_parser: impl Fn(&&dyn QueryParameter<'_>) -> T, + params: &[&'_ dyn QueryParameter], + fn_parser: impl Fn(&&dyn QueryParameter) -> T, ) -> Result, Box> { use mysql_query_launcher::DETECT_PARAMS_IN_QUERY; diff --git a/canyon_core/src/connection/clients/postgresql.rs b/canyon_core/src/connection/clients/postgresql.rs index 66aa1bfa..dd353cd9 100644 --- a/canyon_core/src/connection/clients/postgresql.rs +++ b/canyon_core/src/connection/clients/postgresql.rs @@ -21,7 +21,7 @@ pub(crate) mod postgres_query_launcher { #[inline(always)] pub(crate) async fn query( stmt: S, - params: &[&'_ (dyn QueryParameter<'_>)], + params: &[&'_ (dyn QueryParameter)], conn: &PostgreSqlConnection, ) -> Result, Box<(dyn Error + Send + Sync)>> where @@ -41,7 +41,7 @@ pub(crate) mod postgres_query_launcher { #[inline(always)] pub(crate) async fn query_rows<'a>( stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], conn: &PostgreSqlConnection, ) -> Result> { let m_params: Vec<_> = params @@ -57,7 +57,7 @@ pub(crate) mod postgres_query_launcher { #[inline(always)] pub(crate) async fn query_one<'a, R>( stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], conn: &PostgreSqlConnection, ) -> Result, Box<(dyn Error + Send + Sync)>> where @@ -81,7 +81,7 @@ pub(crate) mod postgres_query_launcher { #[inline(always)] pub(crate) async fn query_one_for<'a, T: FromSqlOwnedValue>( stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], conn: &PostgreSqlConnection, ) -> Result> { let m_params: Vec<_> = params @@ -95,7 +95,7 @@ pub(crate) mod postgres_query_launcher { #[inline(always)] pub(crate) async fn execute( stmt: S, - params: &[&'_ (dyn QueryParameter<'_>)], + params: &[&'_ (dyn QueryParameter)], conn: &PostgreSqlConnection, ) -> Result> where @@ -107,7 +107,7 @@ pub(crate) mod postgres_query_launcher { .map_err(From::from) } - fn get_psql_params<'a>(params: &[&'a (dyn QueryParameter<'_>)]) -> Vec<&'a (dyn ToSql + Sync)> { + fn get_psql_params<'a>(params: &[&'a (dyn QueryParameter)]) -> Vec<&'a (dyn ToSql + Sync)> { params .as_ref() .iter() diff --git a/canyon_core/src/connection/contracts/impl/database_connection.rs b/canyon_core/src/connection/contracts/impl/database_connection.rs index 33e42fe7..4a784529 100644 --- a/canyon_core/src/connection/contracts/impl/database_connection.rs +++ b/canyon_core/src/connection/contracts/impl/database_connection.rs @@ -12,7 +12,7 @@ impl DbConnection for DatabaseConnection { async fn query_rows<'a>( &self, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> Result> { db_conn_query_rows_impl(self, stmt, params).await } @@ -20,7 +20,7 @@ impl DbConnection for DatabaseConnection { async fn query<'a, S, R>( &self, stmt: S, - params: &[&'a (dyn QueryParameter<'a>)], + params: &[&'a (dyn QueryParameter)], ) -> Result, Box<(dyn Error + Send + Sync)>> where S: AsRef + Send, @@ -33,7 +33,7 @@ impl DbConnection for DatabaseConnection { async fn query_one<'a, R>( &self, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> Result, Box<(dyn Error + Send + Sync)>> where R: RowMapper, @@ -44,7 +44,7 @@ impl DbConnection for DatabaseConnection { async fn query_one_for<'a, T: FromSqlOwnedValue>( &self, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> Result> { db_conn_query_one_for_impl::(self, stmt, params).await } @@ -52,7 +52,7 @@ impl DbConnection for DatabaseConnection { async fn execute<'a>( &self, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> Result> { db_conn_execute_impl(self, stmt, params).await } @@ -66,7 +66,7 @@ impl DbConnection for &mut DatabaseConnection { async fn query_rows<'a>( &self, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> Result> { db_conn_query_rows_impl(self, stmt, params).await } @@ -74,7 +74,7 @@ impl DbConnection for &mut DatabaseConnection { async fn query<'a, S, R>( &self, stmt: S, - params: &[&'a (dyn QueryParameter<'a>)], + params: &[&'a (dyn QueryParameter)], ) -> Result, Box<(dyn Error + Send + Sync)>> where S: AsRef + Send, @@ -87,7 +87,7 @@ impl DbConnection for &mut DatabaseConnection { async fn query_one<'a, R>( &self, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> Result, Box<(dyn Error + Send + Sync)>> where R: RowMapper, @@ -98,7 +98,7 @@ impl DbConnection for &mut DatabaseConnection { async fn query_one_for<'a, T: FromSqlOwnedValue>( &self, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> Result> { db_conn_query_one_for_impl::(self, stmt, params).await } @@ -106,7 +106,7 @@ impl DbConnection for &mut DatabaseConnection { async fn execute<'a>( &self, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> Result> { db_conn_execute_impl(self, stmt, params).await } @@ -119,7 +119,7 @@ impl DbConnection for &mut DatabaseConnection { pub(crate) async fn db_conn_query_rows_impl<'a>( c: &DatabaseConnection, stmt: &str, - params: &[&'a (dyn QueryParameter<'a> + 'a)], + params: &[&'a (dyn QueryParameter + 'a)], ) -> Result> { match c { #[cfg(feature = "postgres")] @@ -136,7 +136,7 @@ pub(crate) async fn db_conn_query_rows_impl<'a>( pub(crate) async fn db_conn_query_one_impl<'a, R>( c: &DatabaseConnection, stmt: &str, - params: &[&'a (dyn QueryParameter<'a> + 'a)], + params: &[&'a (dyn QueryParameter + 'a)], ) -> Result, Box> where R: RowMapper, @@ -156,7 +156,7 @@ where pub(crate) async fn db_conn_query_impl<'a, S, R>( c: &DatabaseConnection, stmt: S, - params: &[&'a (dyn QueryParameter<'a>)], + params: &[&'a (dyn QueryParameter)], ) -> Result, Box<(dyn Error + Send + Sync)>> where S: AsRef + Send, @@ -178,7 +178,7 @@ where pub(crate) async fn db_conn_query_one_for_impl<'a, T>( c: &DatabaseConnection, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> Result> where T: FromSqlOwnedValue, @@ -198,7 +198,7 @@ where pub(crate) async fn db_conn_execute_impl<'a>( c: &DatabaseConnection, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> Result> { match c { #[cfg(feature = "postgres")] diff --git a/canyon_core/src/connection/contracts/impl/mod.rs b/canyon_core/src/connection/contracts/impl/mod.rs index 4df947e4..adfee2de 100644 --- a/canyon_core/src/connection/contracts/impl/mod.rs +++ b/canyon_core/src/connection/contracts/impl/mod.rs @@ -25,12 +25,12 @@ impl_db_connection!(&str); impl DbConnection for Arc> where T: DbConnection + Send, - Self: Clone + Self: Clone, { async fn query_rows<'a>( &self, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> Result> { self.lock().await.query_rows(stmt, params).await } @@ -38,7 +38,7 @@ where async fn query<'a, S, R>( &self, stmt: S, - params: &[&'a (dyn QueryParameter<'a>)], + params: &[&'a (dyn QueryParameter)], ) -> Result, Box<(dyn Error + Send + Sync)>> where S: AsRef + Send, @@ -51,7 +51,7 @@ where async fn query_one<'a, R>( &self, stmt: &str, - params: &[&'a (dyn QueryParameter<'a>)], + params: &[&'a (dyn QueryParameter)], ) -> Result, Box<(dyn Error + Send + Sync)>> where R: RowMapper, @@ -62,7 +62,7 @@ where async fn query_one_for<'a, F: FromSqlOwnedValue>( &self, stmt: &str, - params: &[&'a (dyn QueryParameter<'a>)], + params: &[&'a (dyn QueryParameter)], ) -> Result> { self.lock().await.query_one_for::(stmt, params).await } @@ -70,7 +70,7 @@ where async fn execute<'a>( &self, stmt: &str, - params: &[&'a (dyn QueryParameter<'a>)], + params: &[&'a (dyn QueryParameter)], ) -> Result> { self.lock().await.execute(stmt, params).await } diff --git a/canyon_core/src/connection/contracts/impl/mssql.rs b/canyon_core/src/connection/contracts/impl/mssql.rs index 2c175951..fc0e4dd4 100644 --- a/canyon_core/src/connection/contracts/impl/mssql.rs +++ b/canyon_core/src/connection/contracts/impl/mssql.rs @@ -14,7 +14,7 @@ impl DbConnection for SqlServerConnection { fn query_rows<'a>( &self, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> impl Future>> + Send { sqlserver_query_launcher::query_rows(stmt, params, self) } @@ -22,7 +22,7 @@ impl DbConnection for SqlServerConnection { fn query<'a, S, R>( &self, stmt: S, - params: &[&'a (dyn QueryParameter<'a>)], + params: &[&'a (dyn QueryParameter)], ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where S: AsRef + Send, @@ -35,7 +35,7 @@ impl DbConnection for SqlServerConnection { fn query_one<'a, R>( &self, stmt: &str, - params: &[&'a (dyn QueryParameter<'a>)], + params: &[&'a (dyn QueryParameter)], ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where R: RowMapper, @@ -46,7 +46,7 @@ impl DbConnection for SqlServerConnection { fn query_one_for<'a, T: FromSqlOwnedValue>( &self, stmt: &str, - params: &[&'a (dyn QueryParameter<'a>)], + params: &[&'a (dyn QueryParameter)], ) -> impl Future>> + Send { sqlserver_query_launcher::query_one_for(stmt, params, self) } @@ -54,7 +54,7 @@ impl DbConnection for SqlServerConnection { fn execute<'a>( &self, stmt: &str, - params: &[&'a (dyn QueryParameter<'a>)], + params: &[&'a (dyn QueryParameter)], ) -> impl Future>> + Send { sqlserver_query_launcher::execute(stmt, params, self) } diff --git a/canyon_core/src/connection/contracts/impl/mysql.rs b/canyon_core/src/connection/contracts/impl/mysql.rs index 5d51ae4b..36e4cecc 100644 --- a/canyon_core/src/connection/contracts/impl/mysql.rs +++ b/canyon_core/src/connection/contracts/impl/mysql.rs @@ -13,7 +13,7 @@ impl DbConnection for MysqlConnection { fn query_rows<'a>( &self, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> impl Future>> + Send { mysql_query_launcher::query_rows(stmt, params, self) } @@ -21,7 +21,7 @@ impl DbConnection for MysqlConnection { fn query<'a, S, R>( &self, stmt: S, - params: &[&'a (dyn QueryParameter<'a>)], + params: &[&'a (dyn QueryParameter)], ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where S: AsRef + Send, @@ -34,7 +34,7 @@ impl DbConnection for MysqlConnection { fn query_one<'a, R>( &self, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where R: RowMapper, @@ -45,7 +45,7 @@ impl DbConnection for MysqlConnection { fn query_one_for<'a, T: FromSqlOwnedValue>( &self, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> impl Future>> + Send { mysql_query_launcher::query_one_for(stmt, params, self) } @@ -53,7 +53,7 @@ impl DbConnection for MysqlConnection { fn execute<'a>( &self, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> impl Future>> + Send { mysql_query_launcher::execute(stmt, params, self) } diff --git a/canyon_core/src/connection/contracts/impl/postgresql.rs b/canyon_core/src/connection/contracts/impl/postgresql.rs index 59d23544..f074ed43 100644 --- a/canyon_core/src/connection/contracts/impl/postgresql.rs +++ b/canyon_core/src/connection/contracts/impl/postgresql.rs @@ -14,7 +14,7 @@ impl DbConnection for PostgreSqlConnection { fn query_rows<'a>( &self, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> impl Future>> + Send { postgres_query_launcher::query_rows(stmt, params, self) } @@ -22,7 +22,7 @@ impl DbConnection for PostgreSqlConnection { fn query<'a, S, R>( &self, stmt: S, - params: &[&'a (dyn QueryParameter<'a>)], + params: &[&'a (dyn QueryParameter)], ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where S: AsRef + Send, @@ -35,7 +35,7 @@ impl DbConnection for PostgreSqlConnection { fn query_one<'a, R>( &self, stmt: &str, - params: &[&'a (dyn QueryParameter<'a>)], + params: &[&'a (dyn QueryParameter)], ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where R: RowMapper, @@ -46,7 +46,7 @@ impl DbConnection for PostgreSqlConnection { fn query_one_for<'a, T: FromSqlOwnedValue>( &self, stmt: &str, - params: &[&'a (dyn QueryParameter<'a>)], + params: &[&'a (dyn QueryParameter)], ) -> impl Future>> + Send { postgres_query_launcher::query_one_for(stmt, params, self) } @@ -54,7 +54,7 @@ impl DbConnection for PostgreSqlConnection { fn execute<'a>( &self, stmt: &str, - params: &[&'a (dyn QueryParameter<'a>)], + params: &[&'a (dyn QueryParameter)], ) -> impl Future>> + Send { postgres_query_launcher::execute(stmt, params, self) } diff --git a/canyon_core/src/connection/contracts/impl/str.rs b/canyon_core/src/connection/contracts/impl/str.rs index 7fa7e522..b01817ef 100644 --- a/canyon_core/src/connection/contracts/impl/str.rs +++ b/canyon_core/src/connection/contracts/impl/str.rs @@ -6,7 +6,7 @@ macro_rules! impl_db_connection { async fn query_rows<'a>( &self, stmt: &str, - params: &[&'a dyn crate::query::parameters::QueryParameter<'a>], + params: &[&'a dyn crate::query::parameters::QueryParameter], ) -> Result> { let conn = crate::connection::Canyon::instance()?.get_connection(self)?; conn.lock().await.query_rows(stmt, params).await @@ -15,7 +15,7 @@ macro_rules! impl_db_connection { async fn query<'a, S, R>( &self, stmt: S, - params: &[&'a (dyn crate::query::parameters::QueryParameter<'a>)], + params: &[&'a (dyn crate::query::parameters::QueryParameter)], ) -> Result, Box<(dyn std::error::Error + Send + Sync)>> where S: AsRef + Send, @@ -29,7 +29,7 @@ macro_rules! impl_db_connection { async fn query_one<'a, R>( &self, stmt: &str, - params: &[&'a dyn crate::query::parameters::QueryParameter<'a>], + params: &[&'a dyn crate::query::parameters::QueryParameter], ) -> Result, Box<(dyn std::error::Error + Send + Sync)>> where R: crate::mapper::RowMapper, @@ -41,7 +41,7 @@ macro_rules! impl_db_connection { async fn query_one_for<'a, T: crate::rows::FromSqlOwnedValue>( &self, stmt: &str, - params: &[&'a dyn crate::query::parameters::QueryParameter<'a>], + params: &[&'a dyn crate::query::parameters::QueryParameter], ) -> Result> { let conn = crate::connection::Canyon::instance()?.get_connection(self)?; conn.lock().await.query_one_for(stmt, params).await @@ -50,7 +50,7 @@ macro_rules! impl_db_connection { async fn execute<'a>( &self, stmt: &str, - params: &[&'a dyn crate::query::parameters::QueryParameter<'a>], + params: &[&'a dyn crate::query::parameters::QueryParameter], ) -> Result> { let conn = crate::connection::Canyon::instance()?.get_connection(self)?; conn.lock().await.execute(stmt, params).await diff --git a/canyon_core/src/connection/contracts/mod.rs b/canyon_core/src/connection/contracts/mod.rs index 981c46da..dff0d62a 100644 --- a/canyon_core/src/connection/contracts/mod.rs +++ b/canyon_core/src/connection/contracts/mod.rs @@ -42,7 +42,7 @@ pub trait DbConnection { fn query_rows<'a>( &self, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> impl Future>> + Send; /// Executes a query and maps the result to a collection of rows of type `R`. @@ -58,7 +58,7 @@ pub trait DbConnection { fn query<'a, S, R>( &self, stmt: S, - params: &[&'a (dyn QueryParameter<'a>)], + params: &[&'a (dyn QueryParameter)], ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where S: AsRef + Send, @@ -78,7 +78,7 @@ pub trait DbConnection { fn query_one<'a, R>( &self, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where R: RowMapper; @@ -96,7 +96,7 @@ pub trait DbConnection { fn query_one_for<'a, T: FromSqlOwnedValue>( &self, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> impl Future>> + Send; /// Executes a SQL statement and returns the number of affected rows. @@ -110,7 +110,7 @@ pub trait DbConnection { fn execute<'a>( &self, stmt: &str, - params: &[&'a dyn QueryParameter<'a>], + params: &[&'a dyn QueryParameter], ) -> impl Future>> + Send; /// Retrieves the type of the database associated with the connection. diff --git a/canyon_core/src/query/bounds.rs b/canyon_core/src/query/bounds.rs index b3a57f80..867ad303 100644 --- a/canyon_core/src/query/bounds.rs +++ b/canyon_core/src/query/bounds.rs @@ -20,7 +20,7 @@ pub trait Inspectionable<'a> { /// # Warning /// This may change in the future, so that's why this operation shouldn't be used, nor it's /// recommended to use it publicly as an end-user. - fn fields_actual_values(&self) -> Vec<&dyn QueryParameter<'_>>; + fn fields_actual_values(&self) -> Vec<&dyn QueryParameter>; /// Returns a linear collection with the names of every field for the implementor as a String fn fields_names(&self) -> &[&'static str]; @@ -30,7 +30,7 @@ pub trait Inspectionable<'a> { fn primary_key(&self) -> Option<&'static str>; fn primary_key_st() -> Option<&'static str>; - fn primary_key_actual_value(&self) -> &'a (dyn QueryParameter<'_> + 'a); + fn primary_key_actual_value(&self) -> &'_ (dyn QueryParameter + '_); fn set_primary_key_actual_value( &mut self, value: Self::PrimaryKeyType, @@ -76,7 +76,7 @@ pub trait FieldIdentifier: std::fmt::Display { /// Represents some kind of introspection to make the implementors /// able to retrieve a value inside some variant of an associated enum type. /// and convert it to a tuple struct formed by the column name as an String, -/// and the dynamic value of the [`QueryParameter<'_>`] trait object contained +/// and the dynamic value of the [`QueryParameter`] trait object contained /// inside the variant requested, /// enabling a conversion of that value into something /// that can be part of an SQL query. @@ -94,7 +94,7 @@ pub trait FieldIdentifier: std::fmt::Display { /// } /// ``` pub trait FieldValueIdentifier { - fn value(&self) -> (&'static str, &dyn QueryParameter<'_>); + fn value(&self) -> (&'static str, &dyn QueryParameter); } /// Bounds to some type T in order to make it callable over some fn parameter T @@ -106,5 +106,5 @@ pub trait FieldValueIdentifier { /// this side of the relation it's representing pub trait ForeignKeyable { /// Returns the actual value of the field related to the column passed in - fn get_fk_column(&self, column: &str) -> Option<&dyn QueryParameter<'_>>; + fn get_fk_column(&self, column: &str) -> Option<&dyn QueryParameter>; } diff --git a/canyon_core/src/query/parameters.rs b/canyon_core/src/query/parameters.rs index 62264a29..7094da3c 100644 --- a/canyon_core/src/query/parameters.rs +++ b/canyon_core/src/query/parameters.rs @@ -14,7 +14,7 @@ pub trait QueryParameterValue<'a> { fn downcast_ref(&'a self) -> Option<&'a T>; fn to_owned_any(&'a self) -> Box; } -impl<'a> QueryParameterValue<'a> for dyn QueryParameter<'a> { +impl<'a> QueryParameterValue<'a> for dyn QueryParameter { fn downcast_ref(&'a self) -> Option<&'a T> { self.as_any().downcast_ref() } @@ -23,7 +23,7 @@ impl<'a> QueryParameterValue<'a> for dyn QueryParameter<'a> { Box::new(self.downcast_ref::().cloned().unwrap()) } } -impl<'a> QueryParameterValue<'a> for &'a dyn QueryParameter<'a> { +impl<'a> QueryParameterValue<'a> for &'a dyn QueryParameter { fn downcast_ref(&'a self) -> Option<&'a T> { self.as_any().downcast_ref() } @@ -37,8 +37,8 @@ impl<'a> QueryParameterValue<'a> for &'a dyn QueryParameter<'a> { // #[derive(Debug, Clone, Copy)] // pub struct NoPrimaryKey; // -// // Implement the QueryParameter<'a> trait for the zero-sized type -// impl<'a> QueryParameter<'a> for NoPrimaryKey { +// // Implement the QueryParameter trait for the zero-sized type +// impl QueryParameter for NoPrimaryKey { // fn as_any(&'a self) -> &'a dyn Any { // todo!() // } @@ -59,8 +59,8 @@ impl<'a> QueryParameterValue<'a> for &'a dyn QueryParameter<'a> { /// Defines a trait for represent type bounds against the allowed /// data types supported by Canyon to be used as query parameters. -pub trait QueryParameter<'a>: Debug + Send + Sync { - fn as_any(&'a self) -> &'a dyn Any; +pub trait QueryParameter: Debug + Send + Sync { + fn as_any(&self) -> &dyn Any; #[cfg(feature = "postgres")] fn as_postgres_param(&self) -> &(dyn ToSql + Sync); @@ -75,11 +75,11 @@ pub trait QueryParameter<'a>: Debug + Send + Sync { /// /// This implementation is necessary because of the generic amplitude /// of the arguments of the [`crate::transaction::Transaction::query`], that should work with -/// a collection of [`QueryParameter<'a>`], in order to allow a workflow +/// a collection of [`QueryParameter`], in order to allow a workflow /// that is not dependent of the specific type of the argument that holds /// the query parameters of the database connectors #[cfg(feature = "mssql")] -impl<'b> IntoSql<'b> for &'b dyn QueryParameter<'b> { +impl<'b> IntoSql<'b> for &'b dyn QueryParameter { fn into_sql(self) -> ColumnData<'b> { self.as_sqlserver_param() } @@ -87,8 +87,8 @@ impl<'b> IntoSql<'b> for &'b dyn QueryParameter<'b> { //TODO Pending to review and see if it is necessary to apply something similar to the previous implementation. -impl<'a> QueryParameter<'a> for bool { - fn as_any(&'a self) -> &'a dyn Any { +impl QueryParameter for bool { + fn as_any(&self) -> &dyn Any { self } @@ -106,7 +106,7 @@ impl<'a> QueryParameter<'a> for bool { } } -impl QueryParameter<'_> for i16 { +impl QueryParameter for i16 { fn as_any(&'_ self) -> &'_ dyn Any { self } @@ -125,8 +125,8 @@ impl QueryParameter<'_> for i16 { } } -impl<'a> QueryParameter<'a> for Option<&'static i16> { - fn as_any(&'a self) -> &'a dyn Any { +impl QueryParameter for Option<&'static i16> { + fn as_any(&self) -> &dyn Any { self } @@ -144,7 +144,7 @@ impl<'a> QueryParameter<'a> for Option<&'static i16> { } } -impl QueryParameter<'_> for i32 { +impl QueryParameter for i32 { fn as_any(&self) -> &dyn Any { self } @@ -163,8 +163,8 @@ impl QueryParameter<'_> for i32 { } } -impl<'a> QueryParameter<'a> for Option { - fn as_any(&'a self) -> &'a dyn Any { +impl QueryParameter for Option { + fn as_any(&self) -> &dyn Any { self } @@ -182,7 +182,7 @@ impl<'a> QueryParameter<'a> for Option { } } -impl QueryParameter<'_> for u32 { +impl QueryParameter for u32 { fn as_any(&self) -> &dyn Any { self } @@ -201,9 +201,8 @@ impl QueryParameter<'_> for u32 { } } - -impl<'a> QueryParameter<'a> for Option { - fn as_any(&'a self) -> &'a dyn Any { +impl QueryParameter for Option { + fn as_any(&self) -> &dyn Any { self } @@ -221,7 +220,7 @@ impl<'a> QueryParameter<'a> for Option { } } -impl QueryParameter<'_> for f32 { +impl QueryParameter for f32 { fn as_any(&self) -> &dyn Any { self } @@ -240,7 +239,7 @@ impl QueryParameter<'_> for f32 { } } -impl<'a> QueryParameter<'a> for Option { +impl QueryParameter for Option { fn as_any(&self) -> &dyn Any { self } @@ -259,7 +258,7 @@ impl<'a> QueryParameter<'a> for Option { } } -impl<'a> QueryParameter<'a> for f64 { +impl QueryParameter for f64 { fn as_any(&self) -> &dyn Any { self } @@ -278,7 +277,7 @@ impl<'a> QueryParameter<'a> for f64 { } } -impl<'a> QueryParameter<'a> for Option { +impl QueryParameter for Option { fn as_any(&self) -> &dyn Any { self } @@ -297,7 +296,7 @@ impl<'a> QueryParameter<'a> for Option { } } -impl<'a> QueryParameter<'a> for i64 { +impl QueryParameter for i64 { fn as_any(&self) -> &dyn Any { self } @@ -316,7 +315,7 @@ impl<'a> QueryParameter<'a> for i64 { } } -impl<'a> QueryParameter<'a> for Option { +impl QueryParameter for Option { fn as_any(&self) -> &dyn Any { self } @@ -335,7 +334,7 @@ impl<'a> QueryParameter<'a> for Option { } } -impl<'a> QueryParameter<'a> for String { +impl QueryParameter for String { fn as_any(&self) -> &dyn Any { self } @@ -354,7 +353,7 @@ impl<'a> QueryParameter<'a> for String { } } -impl<'a> QueryParameter<'a> for Option { +impl QueryParameter for Option { fn as_any(&self) -> &dyn Any { self } @@ -376,8 +375,8 @@ impl<'a> QueryParameter<'a> for Option { } } -impl<'a> QueryParameter<'a> for Option<&'static String> { - fn as_any(&'a self) -> &'a dyn Any { +impl QueryParameter for Option<&'static String> { + fn as_any(&self) -> &dyn Any { self } @@ -398,7 +397,7 @@ impl<'a> QueryParameter<'a> for Option<&'static String> { } } -impl QueryParameter<'_> for &'static str { +impl QueryParameter for &'static str { fn as_any(&self) -> &dyn Any { self } @@ -417,7 +416,7 @@ impl QueryParameter<'_> for &'static str { } } -impl QueryParameter<'_> for Option<&'static str> { +impl QueryParameter for Option<&'static str> { fn as_any(&'_ self) -> &'_ dyn Any { self } @@ -439,7 +438,7 @@ impl QueryParameter<'_> for Option<&'static str> { } } -impl QueryParameter<'_> for NaiveDate { +impl QueryParameter for NaiveDate { fn as_any(&'_ self) -> &'_ dyn Any { self } @@ -458,7 +457,7 @@ impl QueryParameter<'_> for NaiveDate { } } -impl QueryParameter<'_> for Option { +impl QueryParameter for Option { fn as_any(&'_ self) -> &'_ dyn Any { self } @@ -477,7 +476,7 @@ impl QueryParameter<'_> for Option { } } -impl QueryParameter<'_> for NaiveTime { +impl QueryParameter for NaiveTime { fn as_any(&'_ self) -> &'_ dyn Any { self } @@ -496,7 +495,7 @@ impl QueryParameter<'_> for NaiveTime { } } -impl QueryParameter<'_> for Option { +impl QueryParameter for Option { fn as_any(&'_ self) -> &'_ dyn Any { self } @@ -515,7 +514,7 @@ impl QueryParameter<'_> for Option { } } -impl QueryParameter<'_> for NaiveDateTime { +impl QueryParameter for NaiveDateTime { fn as_any(&'_ self) -> &'_ dyn Any { self } @@ -534,7 +533,7 @@ impl QueryParameter<'_> for NaiveDateTime { } } -impl QueryParameter<'_> for Option { +impl QueryParameter for Option { fn as_any(&'_ self) -> &'_ dyn Any { self } @@ -554,7 +553,7 @@ impl QueryParameter<'_> for Option { } //TODO pending -impl QueryParameter<'_> for DateTime { +impl QueryParameter for DateTime { fn as_any(&'_ self) -> &'_ dyn Any { self } @@ -573,7 +572,7 @@ impl QueryParameter<'_> for DateTime { } } -impl QueryParameter<'_> for Option> { +impl QueryParameter for Option> { fn as_any(&'_ self) -> &'_ dyn Any { self } @@ -592,7 +591,7 @@ impl QueryParameter<'_> for Option> { } } -impl QueryParameter<'_> for DateTime { +impl QueryParameter for DateTime { fn as_any(&'_ self) -> &'_ dyn Any { self } @@ -611,7 +610,7 @@ impl QueryParameter<'_> for DateTime { } } -impl QueryParameter<'_> for Option> { +impl QueryParameter for Option> { fn as_any(&'_ self) -> &'_ dyn Any { self } diff --git a/canyon_core/src/query/query.rs b/canyon_core/src/query/query.rs index 54c8ae47..3fc1b47f 100644 --- a/canyon_core/src/query/query.rs +++ b/canyon_core/src/query/query.rs @@ -17,7 +17,7 @@ use std::ops::DerefMut; #[derive(Debug)] pub struct Query<'a> { pub sql: String, - pub params: Vec<&'a dyn QueryParameter<'a>>, + pub params: Vec<&'a dyn QueryParameter>, } impl AsRef for Query<'_> { @@ -30,7 +30,7 @@ impl<'a> Query<'a> { /// Constructs a new [`Self`] but receiving the number of expected query parameters, allowing /// to pre-allocate the underlying linear collection that holds the arguments to the exact capacity, /// potentially saving re-allocations when the query is created - pub fn new(sql: String, params: Vec<&'a dyn QueryParameter<'a>>) -> Query<'a> { + pub fn new(sql: String, params: Vec<&'a dyn QueryParameter>) -> Query<'a> { Self { sql, params } } diff --git a/canyon_core/src/query/querybuilder/contracts/mod.rs b/canyon_core/src/query/querybuilder/contracts/mod.rs index ccebd863..c58b2bc1 100644 --- a/canyon_core/src/query/querybuilder/contracts/mod.rs +++ b/canyon_core/src/query/querybuilder/contracts/mod.rs @@ -12,7 +12,7 @@ pub trait UpdateQueryBuilderOps<'a>: QueryBuilderOps<'a> { fn set(self, columns: &'a [(Z, Q)]) -> Self where Z: FieldIdentifier, - Q: QueryParameter<'a>; + Q: QueryParameter; } pub trait SelectQueryBuilderOps<'a>: QueryBuilderOps<'a> { @@ -146,7 +146,7 @@ pub trait QueryBuilderOps<'a> { fn and_values_in(self, column: Z, values: &'a [Q]) -> Self where Z: FieldIdentifier, - Q: QueryParameter<'a>; + Q: QueryParameter; /// Generates an `OR` SQL clause for constraint the query that will create /// the filter in conjunction with an `IN` operator that will ac @@ -159,7 +159,7 @@ pub trait QueryBuilderOps<'a> { fn or_values_in(self, r#or: Z, values: &'a [Q]) -> Self where Z: FieldIdentifier, - Q: QueryParameter<'a>; + Q: QueryParameter; /// Generates an `OR` SQL clause for constraint the query. /// diff --git a/canyon_core/src/query/querybuilder/impl/delete.rs b/canyon_core/src/query/querybuilder/impl/delete.rs index 8838e03c..b139e87a 100644 --- a/canyon_core/src/query/querybuilder/impl/delete.rs +++ b/canyon_core/src/query/querybuilder/impl/delete.rs @@ -33,7 +33,7 @@ impl<'a> QueryBuilderOps<'a> for DeleteQueryBuilder<'a> { fn and_values_in(mut self, r#and: Z, values: &'a [Q]) -> Self where Z: FieldIdentifier, - Q: QueryParameter<'a>, + Q: QueryParameter, { self._inner.and_values_in(and, values); self @@ -43,7 +43,7 @@ impl<'a> QueryBuilderOps<'a> for DeleteQueryBuilder<'a> { fn or_values_in(mut self, r#or: Z, values: &'a [Q]) -> Self where Z: FieldIdentifier, - Q: QueryParameter<'a>, + Q: QueryParameter, { self._inner.or_values_in(or, values); self diff --git a/canyon_core/src/query/querybuilder/impl/select.rs b/canyon_core/src/query/querybuilder/impl/select.rs index e93c2879..ced1a23b 100644 --- a/canyon_core/src/query/querybuilder/impl/select.rs +++ b/canyon_core/src/query/querybuilder/impl/select.rs @@ -89,7 +89,7 @@ impl<'a> QueryBuilderOps<'a> for SelectQueryBuilder<'a> { fn and_values_in(mut self, r#and: Z, values: &'a [Q]) -> Self where Z: FieldIdentifier, - Q: QueryParameter<'a>, + Q: QueryParameter, { self._inner.and_values_in(and, values); self @@ -99,7 +99,7 @@ impl<'a> QueryBuilderOps<'a> for SelectQueryBuilder<'a> { fn or_values_in(mut self, r#and: Z, values: &'a [Q]) -> Self where Z: FieldIdentifier, - Q: QueryParameter<'a>, + Q: QueryParameter, { self._inner.or_values_in(and, values); self diff --git a/canyon_core/src/query/querybuilder/impl/update.rs b/canyon_core/src/query/querybuilder/impl/update.rs index 62372483..9b192f93 100644 --- a/canyon_core/src/query/querybuilder/impl/update.rs +++ b/canyon_core/src/query/querybuilder/impl/update.rs @@ -9,7 +9,7 @@ impl<'a> UpdateQueryBuilderOps<'a> for UpdateQueryBuilder<'a> { fn set(mut self, columns: &'a [(Z, Q)]) -> Self where Z: FieldIdentifier, - Q: QueryParameter<'a>, + Q: QueryParameter, { if columns.is_empty() { // TODO: this is an err as well @@ -73,7 +73,7 @@ impl<'a> QueryBuilderOps<'a> for UpdateQueryBuilder<'a> { fn and_values_in(mut self, r#and: Z, values: &'a [Q]) -> Self where Z: FieldIdentifier, - Q: QueryParameter<'a>, + Q: QueryParameter, { self._inner.and_values_in(and, values); self @@ -83,7 +83,7 @@ impl<'a> QueryBuilderOps<'a> for UpdateQueryBuilder<'a> { fn or_values_in(mut self, r#or: Z, values: &'a [Q]) -> Self where Z: FieldIdentifier, - Q: QueryParameter<'a>, + Q: QueryParameter, { self._inner.or_values_in(or, values); self diff --git a/canyon_core/src/query/querybuilder/types/mod.rs b/canyon_core/src/query/querybuilder/types/mod.rs index 1017a5aa..82ddde51 100644 --- a/canyon_core/src/query/querybuilder/types/mod.rs +++ b/canyon_core/src/query/querybuilder/types/mod.rs @@ -13,7 +13,7 @@ use std::error::Error; /// Type for construct more complex queries than the classical CRUD ones. pub struct QueryBuilder<'a> { pub(crate) sql: String, - pub(crate) params: Vec<&'a dyn QueryParameter<'a>>, + pub(crate) params: Vec<&'a dyn QueryParameter>, pub(crate) database_type: DatabaseType, } @@ -63,7 +63,7 @@ impl<'a> QueryBuilder<'a> { pub fn and_values_in(&mut self, r#and: Z, values: &'a [Q]) where Z: FieldIdentifier, - Q: QueryParameter<'a>, + Q: QueryParameter, { if values.is_empty() { return; @@ -88,7 +88,7 @@ impl<'a> QueryBuilder<'a> { pub fn or_values_in(&mut self, r#or: Z, values: &'a [Q]) where Z: FieldIdentifier, - Q: QueryParameter<'a>, + Q: QueryParameter, { if values.is_empty() { return; diff --git a/canyon_core/src/transaction.rs b/canyon_core/src/transaction.rs index 6804551a..2cafbf14 100644 --- a/canyon_core/src/transaction.rs +++ b/canyon_core/src/transaction.rs @@ -43,7 +43,7 @@ use std::future::Future; pub trait Transaction { fn query<'a, S, R>( stmt: S, - params: &[&'a (dyn QueryParameter<'a>)], + params: &[&'a (dyn QueryParameter)], input: impl DbConnection + Send, ) -> impl Future, Box<(dyn Error + Send + Sync)>>> where @@ -61,7 +61,7 @@ pub trait Transaction { ) -> impl Future, Box<(dyn Error + Send + Sync)>>> + Send where S: AsRef + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send, + Z: AsRef<[&'a dyn QueryParameter]> + Send, R: RowMapper, { async move { input.query_one::(stmt.as_ref(), params.as_ref()).await } @@ -74,7 +74,7 @@ pub trait Transaction { ) -> impl Future>> + Send where S: AsRef + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter]> + Send + 'a, { async move { input.query_one_for(stmt.as_ref(), params.as_ref()).await } } @@ -89,7 +89,7 @@ pub trait Transaction { ) -> impl Future>> + Send where S: AsRef + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter]> + Send + 'a, { async move { input.query_rows(stmt.as_ref(), params.as_ref()).await } } @@ -101,7 +101,7 @@ pub trait Transaction { ) -> impl Future>> + Send where S: AsRef + Send + 'a, - Z: AsRef<[&'a dyn QueryParameter<'a>]> + Send + 'a, + Z: AsRef<[&'a dyn QueryParameter]> + Send + 'a, { async move { input.execute(stmt.as_ref(), params.as_ref()).await } } diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 0fe609af..0a8aa9f3 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -52,11 +52,11 @@ where I: DbConnection + Send + 'a; fn find_by_pk<'a, 'b>( - value: &'a dyn QueryParameter<'a>, + value: &'a dyn QueryParameter, ) -> impl Future, Box<(dyn Error + Send + Sync + 'b)>>> + Send; fn find_by_pk_with<'a, 'b, I>( - value: &'a dyn QueryParameter<'a>, + value: &'a dyn QueryParameter, input: I, ) -> impl Future, Box<(dyn Error + Send + Sync + 'b)>>> + Send where @@ -251,10 +251,12 @@ where I: DbConnection + Send + 'a; fn delete_query<'a, 'b>() -> Result, Box<(dyn Error + Send + Sync + 'b)>> - where 'a: 'b; + where + 'a: 'b; fn delete_query_with<'a, 'b>( database_type: DatabaseType, ) -> Result, Box<(dyn Error + Send + Sync + 'b)>> - where 'a: 'b; + where + 'a: 'b; } diff --git a/canyon_entities/src/entity.rs b/canyon_entities/src/entity.rs index 0b1b83a7..938a9b9c 100644 --- a/canyon_entities/src/entity.rs +++ b/canyon_entities/src/entity.rs @@ -104,7 +104,7 @@ impl CanyonEntity { let field_name_as_string = f.name.to_string(); quote! { - #enum_name::#field_name(v) => (#field_name_as_string, v as &dyn canyon_sql::query::QueryParameter<'_>) + #enum_name::#field_name(v) => (#field_name_as_string, v as &dyn canyon_sql::query::QueryParameter) } }) .collect::>() diff --git a/canyon_entities/src/manager_builder.rs b/canyon_entities/src/manager_builder.rs index f600b38d..e0d6a90e 100644 --- a/canyon_entities/src/manager_builder.rs +++ b/canyon_entities/src/manager_builder.rs @@ -202,7 +202,7 @@ pub fn generate_enum_with_fields_values(canyon_entity: &CanyonEntity) -> TokenSt } impl canyon_sql::query::bounds::FieldValueIdentifier for #enum_name { - fn value(&self) -> (&'static str, &dyn canyon_sql::query::QueryParameter<'_>) { + fn value(&self) -> (&'static str, &dyn canyon_sql::query::QueryParameter) { match self { #(#match_arms),* } diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index 3236d82f..ec62d892 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -311,7 +311,7 @@ mod __details { type PrimaryKeyType = #pk_assoc_ty; - fn fields_actual_values(&self) -> Vec<&dyn canyon_sql::query::QueryParameter<'_>> { + fn fields_actual_values(&self) -> Vec<&dyn canyon_sql::query::QueryParameter> { vec![#(#fields_values),*] } diff --git a/canyon_macros/src/foreignkeyable_macro.rs b/canyon_macros/src/foreignkeyable_macro.rs index 0da201e4..6d7aae7d 100644 --- a/canyon_macros/src/foreignkeyable_macro.rs +++ b/canyon_macros/src/foreignkeyable_macro.rs @@ -18,7 +18,7 @@ pub fn foreignkeyable_impl_tokens(ast: DeriveInput) -> TokenStream { let field_idents = fields.iter().map(|(_vis, ident)| { let i = ident.to_string(); quote! { - #i => Some(&self.#ident as &dyn canyon_sql::query::QueryParameter<'_>) + #i => Some(&self.#ident as &dyn canyon_sql::query::QueryParameter) } }); let field_idents_cloned = field_idents.clone(); @@ -27,7 +27,7 @@ pub fn foreignkeyable_impl_tokens(ast: DeriveInput) -> TokenStream { /// Implementation of the trait `ForeignKeyable` for the type /// calling this derive proc macro impl canyon_sql::query::bounds::ForeignKeyable for #ty { - fn get_fk_column(&self, column: &str) -> Option<&dyn canyon_sql::query::QueryParameter<'_>> { + fn get_fk_column(&self, column: &str) -> Option<&dyn canyon_sql::query::QueryParameter> { match column { #(#field_idents),*, _ => None @@ -37,7 +37,7 @@ pub fn foreignkeyable_impl_tokens(ast: DeriveInput) -> TokenStream { /// Implementation of the trait `ForeignKeyable` for a reference of this type /// calling this derive proc macro impl canyon_sql::query::bounds::ForeignKeyable<&Self> for &#ty { - fn get_fk_column<'a>(&self, column: &'a str) -> Option<&dyn canyon_sql::query::QueryParameter<'_>> { + fn get_fk_column<'a>(&self, column: &'a str) -> Option<&dyn canyon_sql::query::QueryParameter> { match column { #(#field_idents_cloned),*, _ => None diff --git a/canyon_macros/src/query_operations/delete.rs b/canyon_macros/src/query_operations/delete.rs index 7542ffb8..6a1cba11 100644 --- a/canyon_macros/src/query_operations/delete.rs +++ b/canyon_macros/src/query_operations/delete.rs @@ -42,8 +42,7 @@ pub fn generate_delete_method_tokens( if let Some(primary_key) = pk { let pk_field = Ident::new(&primary_key, Span::call_site()); - let pk_field_value = - quote! { &self.#pk_field as &dyn canyon_sql::query::QueryParameter<'_> }; + let pk_field_value = quote! { &self.#pk_field as &dyn canyon_sql::query::QueryParameter }; let delete_stmt = format!( "DELETE FROM {} WHERE {:?} = $1", table_schema_data, primary_key @@ -189,7 +188,7 @@ mod __details { fn generate_delete_entity_pk_body_logic(table_schema_data: &str) -> TokenStream { quote! { - // let pk_actual_value = &entity.primary_key_actual_value() as &dyn canyon_sql::query::QueryParameter<'canyon>; + // let pk_actual_value = &entity.primary_key_actual_value() as &dyn canyon_sql::query::QueryParameter; let pk_actual_value = entity.primary_key_actual_value(); let delete_stmt = format!( "DELETE FROM {} WHERE {:?} = $1", diff --git a/canyon_macros/src/query_operations/foreign_key.rs b/canyon_macros/src/query_operations/foreign_key.rs index e6b6c5c1..962aca90 100644 --- a/canyon_macros/src/query_operations/foreign_key.rs +++ b/canyon_macros/src/query_operations/foreign_key.rs @@ -103,7 +103,7 @@ fn generate_find_by_foreign_key_tokens( .get_default_connection()?; default_db_conn.lock().await.query_one::<#fk_ty>( #stmt, - &[&self.#field_ident as &dyn canyon_sql::query::QueryParameter<'_>] + &[&self.#field_ident as &dyn canyon_sql::query::QueryParameter] ).await } }, @@ -116,7 +116,7 @@ fn generate_find_by_foreign_key_tokens( #quoted_with_method_signature { input.query_one::<#fk_ty>( #stmt, - &[&self.#field_ident as &dyn canyon_sql::query::QueryParameter<'_>] + &[&self.#field_ident as &dyn canyon_sql::query::QueryParameter] ).await } }, diff --git a/canyon_macros/src/query_operations/insert.rs b/canyon_macros/src/query_operations/insert.rs index 9cf45784..bfd5f708 100644 --- a/canyon_macros/src/query_operations/insert.rs +++ b/canyon_macros/src/query_operations/insert.rs @@ -183,7 +183,7 @@ mod __details { }); quote! { - let values: &[&dyn canyon_sql::query::QueryParameter<'_>] = &[#(#insert_values),*]; + let values: &[&dyn canyon_sql::query::QueryParameter] = &[#(#insert_values),*]; } } @@ -489,11 +489,11 @@ fn _generate_multiple_insert_tokens( ) { let input = ""; - let mut final_values: Vec>> = Vec::new(); + let mut final_values: Vec> = Vec::new(); for instance in instances.iter() { - let intermediate: &[&dyn canyon_sql::query::QueryParameter<'_>] = &[#(#macro_fields),*]; + let intermediate: &[&dyn canyon_sql::query::QueryParameter] = &[#(#macro_fields),*]; - let mut longer_lived: Vec<&dyn canyon_sql::query::QueryParameter<'_>> = Vec::new(); + let mut longer_lived: Vec<&dyn canyon_sql::query::QueryParameter> = Vec::new(); for value in intermediate.into_iter() { longer_lived.push(*value) } @@ -545,11 +545,11 @@ fn _generate_multiple_insert_tokens( where I: canyon_sql::connection::DbConnection + Send + 'a { - let mut final_values: Vec>> = Vec::new(); + let mut final_values: Vec> = Vec::new(); for instance in instances.iter() { - let intermediate: &[&dyn canyon_sql::query::QueryParameter<'_>] = &[#(#macro_fields_cloned),*]; + let intermediate: &[&dyn canyon_sql::query::QueryParameter] = &[#(#macro_fields_cloned),*]; - let mut longer_lived: Vec<&dyn canyon_sql::query::QueryParameter<'_>> = Vec::new(); + let mut longer_lived: Vec<&dyn canyon_sql::query::QueryParameter> = Vec::new(); for value in intermediate.into_iter() { longer_lived.push(*value) } diff --git a/canyon_macros/src/query_operations/read.rs b/canyon_macros/src/query_operations/read.rs index 963b72f2..c3ed8403 100644 --- a/canyon_macros/src/query_operations/read.rs +++ b/canyon_macros/src/query_operations/read.rs @@ -212,7 +212,7 @@ mod __details { }; quote! { - async fn find_by_pk<'canyon_lt, 'err_lt>(value: &'canyon_lt dyn canyon_sql::query::QueryParameter<'canyon_lt>) + async fn find_by_pk<'canyon_lt, 'err_lt>(value: &'canyon_lt dyn canyon_sql::query::QueryParameter) -> Result, Box<(dyn std::error::Error + Send + Sync + 'err_lt)>> { #body @@ -235,7 +235,7 @@ mod __details { }; quote! { - async fn find_by_pk_with<'canyon_lt, 'err_lt, I>(value: &'canyon_lt dyn canyon_sql::query::QueryParameter<'canyon_lt>, input: I) + async fn find_by_pk_with<'canyon_lt, 'err_lt, I>(value: &'canyon_lt dyn canyon_sql::query::QueryParameter, input: I) -> Result, Box<(dyn std::error::Error + Send + Sync + 'err_lt)>> where I: canyon_sql::connection::DbConnection + Send + 'canyon_lt diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index 800b31fb..9c1369f6 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -56,11 +56,11 @@ fn generate_update_method_tokens(macro_data: &MacroTokens, table_schema_data: &s update_ops_tokens.extend(quote! { #update_signature { - let update_values: &[&dyn canyon_sql::query::QueryParameter<'_>] = #update_values; + let update_values: &[&dyn canyon_sql::query::QueryParameter] = #update_values; <#ty #ty_generics as canyon_sql::core::Transaction>::execute(#stmt, update_values, "").await } #update_with_signature { - let update_values: &[&dyn canyon_sql::query::QueryParameter<'_>] = #update_values; + let update_values: &[&dyn canyon_sql::query::QueryParameter] = #update_values; input.execute(#stmt, update_values).await } }); diff --git a/tests/crud/delete_operations.rs b/tests/crud/delete_operations.rs index e9bb61ef..c4edd96b 100644 --- a/tests/crud/delete_operations.rs +++ b/tests/crud/delete_operations.rs @@ -55,7 +55,7 @@ fn test_crud_delete_method_operation() { // To check the success, we can query by the primary key value and check if, after unwrap() // the result of the operation, the find by primary key contains Some(v) or None - // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> + // Remember that `find_by_primary_key(&dyn QueryParameter) -> Result>, Err> assert_eq!( League::find_by_pk(&new_league.id) .await @@ -102,7 +102,7 @@ fn test_crud_delete_with_mssql_method_operation() { // To check the success, we can query by the primary key value and check if, after unwrap() // the result of the operation, the find by primary key contains Some(v) or None - // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> + // Remember that `find_by_primary_key(&dyn QueryParameter) -> Result>, Err> assert_eq!( League::find_by_pk_with(&new_league.id, SQL_SERVER_DS) .await @@ -149,7 +149,7 @@ fn test_crud_delete_with_mysql_method_operation() { // To check the success, we can query by the primary key value and check if, after unwrap() // the result of the operation, the find by primary key contains Some(v) or None - // Remember that `find_by_primary_key(&dyn QueryParameter<'a>) -> Result>, Err> + // Remember that `find_by_primary_key(&dyn QueryParameter) -> Result>, Err> assert_eq!( League::find_by_pk_with(&new_league.id, MYSQL_DS) .await diff --git a/tests/crud/hex_arch_example.rs b/tests/crud/hex_arch_example.rs index 246fa3b9..5dfaa50c 100644 --- a/tests/crud/hex_arch_example.rs +++ b/tests/crud/hex_arch_example.rs @@ -69,7 +69,7 @@ fn test_hex_arch_find_insert_ops() { find_new_league.as_ref().unwrap().name, String::from("Test LeagueHex on layered") ); - + let mut updt = find_new_league.unwrap(); updt.ext_id = 5; let r = LeagueHexRepositoryAdapter::::update_entity(&updt).await; @@ -99,7 +99,7 @@ pub trait LeagueHexService { league: &'a mut LeagueHex, ) -> Result<(), Box>; - async fn get<'a, Pk: QueryParameter<'a>>( + async fn get<'a, Pk: QueryParameter>( &self, id: &'a Pk, ) -> Result, Box>; @@ -120,7 +120,7 @@ impl LeagueHexService for LeagueHexServiceAdapter { self.league_repository.create(league).await } - async fn get<'a, Pk: QueryParameter<'a>>( + async fn get<'a, Pk: QueryParameter>( &self, id: &'a Pk, ) -> Result, Box> { @@ -135,7 +135,7 @@ pub trait LeagueHexRepository { league: &'a mut LeagueHex, ) -> Result<(), Box>; - async fn get<'a, Pk: QueryParameter<'a>>( + async fn get<'a, Pk: QueryParameter>( &self, id: &'a Pk, ) -> Result, Box>; @@ -163,7 +163,7 @@ impl LeagueHexRepository for LeagueHexRepositoryA Self::insert_entity(league).await } - async fn get<'a, Pk: QueryParameter<'a>>( + async fn get<'a, Pk: QueryParameter>( &self, id: &'a Pk, ) -> Result, Box> { diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index 306b3901..f83404f2 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -137,9 +137,7 @@ fn test_crud_find_with_querybuilder_and_fulllike_with_mysql() { fn test_crud_find_with_querybuilder_and_leftlike() { // Find all the leagues whose name ends with "CK" let fv = LeagueFieldValue::name("CK".to_string()); - let filtered_leagues_result = League::select_query() - .unwrap() - .r#where(&fv, Like::Left); + let filtered_leagues_result = League::select_query().unwrap().r#where(&fv, Like::Left); assert_eq!( filtered_leagues_result.read_sql(), @@ -188,9 +186,7 @@ fn test_crud_find_with_querybuilder_and_leftlike_with_mysql() { fn test_crud_find_with_querybuilder_and_rightlike() { // Find all the leagues whose name starts with "LC" let fv = LeagueFieldValue::name("LEC".to_string()); - let filtered_leagues_result = League::select_query() - .unwrap() - .r#where(&fv, Like::Right); + let filtered_leagues_result = League::select_query().unwrap().r#where(&fv, Like::Right); assert_eq!( filtered_leagues_result.read_sql(), @@ -464,9 +460,7 @@ fn test_crud_delete_with_querybuilder_with_mysql() { #[canyon_sql::macros::canyon_tokio_test] fn test_where_clause() { let wh = LeagueFieldValue::name("LEC".to_string()); - let l = League::select_query() - .unwrap() - .r#where(&wh, Comp::Eq); + let l = League::select_query().unwrap().r#where(&wh, Comp::Eq); assert_eq!(l.read_sql(), "SELECT * FROM league WHERE name = $1") } From 7dd1dfae4a43bdd62ed51a02dbabd738582c0f7c Mon Sep 17 00:00:00 2001 From: Alex Vergara Date: Fri, 6 Jun 2025 14:10:28 +0200 Subject: [PATCH 155/155] fix: solved the update entity bug --- canyon_macros/src/canyon_mapper_macro.rs | 2 +- canyon_macros/src/query_operations/update.rs | 16 ++++--- tests/crud/hex_arch_example.rs | 44 +++++++++++++++----- tests/crud/querybuilder_operations.rs | 2 - 4 files changed, 46 insertions(+), 18 deletions(-) diff --git a/canyon_macros/src/canyon_mapper_macro.rs b/canyon_macros/src/canyon_mapper_macro.rs index ec62d892..34d3c4a5 100644 --- a/canyon_macros/src/canyon_mapper_macro.rs +++ b/canyon_macros/src/canyon_mapper_macro.rs @@ -288,7 +288,7 @@ mod __details { let set_pk_val_method = if let Some(pk_ident) = pk_ident_ts { quote! { self.#pk_ident = value.into(); - Ok(()) + Ok(()) } } else { quote! { diff --git a/canyon_macros/src/query_operations/update.rs b/canyon_macros/src/query_operations/update.rs index 9c1369f6..9ae85e6f 100644 --- a/canyon_macros/src/query_operations/update.rs +++ b/canyon_macros/src/query_operations/update.rs @@ -192,20 +192,26 @@ mod __details { fn generate_update_entity_pk_body_logic(table_schema_data: &str) -> TokenStream { quote! { - let pk_actual_value = &entity.primary_key_actual_value(); + let pk_actual_value = entity.primary_key_actual_value(); let update_columns = entity.fields_names(); - let update_values = entity.fields_actual_values(); + let update_values_pk_parsed = entity.fields_actual_values(); let mut vec_columns_values: Vec = Vec::new(); for (i, column_name) in update_columns.to_vec().iter().enumerate() { let column_equal_value = format!("{} = ${}", column_name, i + 2); vec_columns_values.push(column_equal_value) } - let str_columns_values = vec_columns_values.join(", "); + let col_vals_placeholders = vec_columns_values.join(", "); + + // Efficiently build argument list: pk first, then values + let mut update_values: Vec<&dyn canyon_sql::query::QueryParameter> = + Vec::with_capacity(1 + update_values_pk_parsed.len()); + update_values.push(pk_actual_value); + update_values.extend(update_values_pk_parsed); let stmt = format!( - "UPDATE {} SET {} WHERE {} = ${:?}", - #table_schema_data, str_columns_values, primary_key, pk_actual_value + "UPDATE {} SET {} WHERE {:?} = $1", + #table_schema_data, col_vals_placeholders, primary_key ); } } diff --git a/tests/crud/hex_arch_example.rs b/tests/crud/hex_arch_example.rs index 5dfaa50c..aac5c408 100644 --- a/tests/crud/hex_arch_example.rs +++ b/tests/crud/hex_arch_example.rs @@ -33,15 +33,42 @@ fn test_hex_arch_ops() { .unwrap() as usize, find_all_result.len() ); - // assert_eq!(LeagueHexRepositoryAdapter::::count_with(binding.deref_mut()).await.unwrap() as usize, find_all_result.len()); - // The line above works, because we're using binding, but in a better ideal world, our repository would hold an Arc> with the connection, - // so the user acquire the lock on every query, just cloning the Arc, which if you remember, just increases in one unit the number of active - // references pointing to the resource behind the atomic smart pointer } #[cfg(feature = "postgres")] #[canyon_sql::macros::canyon_tokio_test] -fn test_hex_arch_find_insert_ops() { +fn test_hex_arch_insert_entity_ops() { + let default_db_conn = Canyon::instance() + .unwrap() + .get_default_connection() + .unwrap(); + let league_service = LeagueHexServiceAdapter { + league_repository: LeagueHexRepositoryAdapter { + db_conn: default_db_conn, + }, + }; + + let mut other_league: LeagueHex = LeagueHex { + id: Default::default(), + ext_id: Default::default(), + slug: "leaguehex-slug".to_string(), + name: "Test LeagueHex on layered".to_string(), + region: "LeagueHex Region".to_string(), + image_url: "http://example.com/image.png".to_string(), + }; + league_service.create(&mut other_league).await.unwrap(); + + let find_new_league = league_service.get(&other_league.id).await.unwrap(); + assert!(find_new_league.is_some()); + assert_eq!( + find_new_league.as_ref().unwrap().name, + String::from("Test LeagueHex on layered") + ); +} + +#[cfg(feature = "postgres")] +#[canyon_sql::macros::canyon_tokio_test] +fn test_hex_arch_update_entity_ops() { let default_db_conn = Canyon::instance() .unwrap() .get_default_connection() @@ -61,7 +88,6 @@ fn test_hex_arch_find_insert_ops() { image_url: "http://example.com/image.png".to_string(), }; league_service.create(&mut other_league).await.unwrap(); - println!("New league inserted with: {:#?}", other_league.id); let find_new_league = league_service.get(&other_league.id).await.unwrap(); assert!(find_new_league.is_some()); @@ -76,7 +102,7 @@ fn test_hex_arch_find_insert_ops() { assert!(r.is_ok()); let updated = league_service.get(&other_league.id).await.unwrap(); - assert_eq!(updated.unwrap().ext_id, 5) + assert_eq!(updated.unwrap().ext_id, 5); } #[derive(CanyonMapper, Debug)] @@ -167,8 +193,6 @@ impl LeagueHexRepository for LeagueHexRepositoryA &self, id: &'a Pk, ) -> Result, Box> { - let r = Self::find_by_pk(id).await; - println!("FIND BY PK ON GET err: {:?}", r); - r + Self::find_by_pk(id).await } } diff --git a/tests/crud/querybuilder_operations.rs b/tests/crud/querybuilder_operations.rs index f83404f2..1982e762 100644 --- a/tests/crud/querybuilder_operations.rs +++ b/tests/crud/querybuilder_operations.rs @@ -4,8 +4,6 @@ use crate::constants::MYSQL_DS; use crate::constants::SQL_SERVER_DS; use canyon_sql::connection::DatabaseType; -use canyon_sql::query::querybuilder::DeleteQueryBuilder; - /// Tests for the QueryBuilder available operations within Canyon. /// /// QueryBuilder are the way of obtain more flexibility that with