Skip to content

Commit 13b2a98

Browse files
0nSystemOnSystem
andauthored
Mysql integration in Canyon (#45)
* mysql-support start with docker configuration to up container and load data * mysql-support - first implementation workspace canyon_connection * mysql - change module bounds pending DateTime<OffSet> look problem and implement solution * mysql-support canyon macros, pending trait implement FromValue in &str and chrono:Date<OffSet> * mysql-support count table implemented * mysql-support - pending implement select test * mysql-support craete reorder array params to use with mysql params * mysql-support resolve quotes in queries and innecesary loop in reorder map * mysql-support select, update , and query_builder * mysql-support correction test insert and delete mysql implementation and investigate get last insert in mysql_async * develop remove imports not use * mysql-support added integration to returning primary key * mysql-support correction cfg features * mysql-support remove comment to mysql implementation in canyon_database_connector.rs * mysql-support canyon_macro.rs added in feature migrations * mysql-support replace error with feature in canyon_macro and separate pattern in crud.rs * changes: remove CanyonRowsMysql and implement mysql_async, repair initialization mssql * changes: corrections other test * changes: insert and multiinsert match_rows * changes: lib cargo_macros RowMapper * changes: select macro remove else if to generate tokenstream with action count * changes: resolve clippy error format! in expect to create Regex * changes: resolve clippy error format! in expect to create Regex * changes: implement Operator by datasource type * changes: added default datasources * changes: correction error feature specify in datasources.rs enum Auth * changes: correction cargo fmt --------- Co-authored-by: OnSystem <thihiddenonsystem@gmail.com>
1 parent c990b33 commit 13b2a98

36 files changed

Lines changed: 1489 additions & 260 deletions

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ Cargo.lock
55
canyon_tester/
66
macro_utils.rs
77
.vscode/
8-
postgres-data/
8+
postgres-data/
9+
mysql-data/

Cargo.toml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ members = [
1616
"canyon_entities",
1717
"canyon_migrations",
1818
"canyon_macros",
19-
2019
"tests"
2120
]
2221

@@ -31,6 +30,9 @@ canyon_macros = { workspace = true, path = "canyon_macros" }
3130
# To be marked as opt deps
3231
tokio-postgres = { workspace = true, optional = true }
3332
tiberius = { workspace = true, optional = true }
33+
mysql_async = { workspace = true, optional = true }
34+
mysql_common = { workspace = true, optional = true }
35+
3436

3537
[workspace.dependencies]
3638
canyon_crud = { version = "0.4.2", path = "canyon_crud" }
@@ -43,6 +45,8 @@ tokio = { version = "1.27.0", features = ["full"] }
4345
tokio-util = { version = "0.7.4", features = ["compat"] }
4446
tokio-postgres = { version = "0.7.2", features = ["with-chrono-0_4"] }
4547
tiberius = { version = "0.12.1", features = ["tds73", "chrono", "integrated-auth-gssapi"] }
48+
mysql_async = { version = "0.32.2" }
49+
mysql_common = { version = "0.30.6", features = [ "chrono" ]}
4650

4751
chrono = { version = "0.4", features = ["serde"] } # Just from TP better?
4852
serde = { version = "1.0.138", features = ["derive"] }
@@ -54,12 +58,14 @@ lazy_static = "1.4.0"
5458
toml = "0.7.3"
5559
async-trait = "0.1.68"
5660
walkdir = "2.3.3"
57-
regex = "1.5"
61+
regex = "1.9.3"
5862
partialdebug = "0.2.0"
5963

6064
quote = "1.0.9"
6165
proc-macro2 = "1.0.27"
6266

67+
68+
6369
[workspace.package]
6470
version = "0.4.2"
6571
edition = "2021"
@@ -73,4 +79,5 @@ description = "A Rust ORM and QueryBuilder"
7379
[features]
7480
postgres = ["tokio-postgres", "canyon_connection/postgres", "canyon_crud/postgres", "canyon_migrations/postgres", "canyon_macros/postgres"]
7581
mssql = ["tiberius", "canyon_connection/mssql", "canyon_crud/mssql", "canyon_migrations/mssql", "canyon_macros/mssql"]
82+
mysql = ["mysql_async", "mysql_common", "canyon_connection/mysql", "canyon_crud/mysql", "canyon_migrations/mysql", "canyon_macros/mysql"]
7683
migrations = ["canyon_migrations", "canyon_macros/migrations"]

bash_aliases.sh

100644100755
File mode changed.

canyon_connection/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ tokio-util = { workspace = true }
1515

1616
tokio-postgres = { workspace = true, optional = true }
1717
tiberius = { workspace = true, optional = true }
18+
mysql_async = { workspace = true, optional = true }
19+
mysql_common = { workspace = true, optional = true }
20+
1821

1922
futures = { workspace = true }
2023
indexmap = { workspace = true }
@@ -28,3 +31,6 @@ walkdir = { workspace = true }
2831
[features]
2932
postgres = ["tokio-postgres"]
3033
mssql = ["tiberius", "async-std"]
34+
mysql = ["mysql_async","mysql_common"]
35+
36+

canyon_connection/src/canyon_database_connector.rs

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ use serde::Deserialize;
22

33
#[cfg(feature = "mssql")]
44
use async_std::net::TcpStream;
5+
#[cfg(feature = "mysql")]
6+
use mysql_async::Pool;
57
#[cfg(feature = "mssql")]
68
use tiberius::{AuthMethod, Config};
79
#[cfg(feature = "postgres")]
810
use tokio_postgres::{Client, NoTls};
911

10-
use crate::datasources::DatasourceConfig;
12+
use crate::datasources::{Auth, DatasourceConfig};
1113

1214
/// Represents the current supported databases by Canyon
1315
#[derive(Deserialize, Debug, Eq, PartialEq, Clone, Copy)]
@@ -18,6 +20,19 @@ pub enum DatabaseType {
1820
#[serde(alias = "sqlserver", alias = "mssql")]
1921
#[cfg(feature = "mssql")]
2022
SqlServer,
23+
#[serde(alias = "mysql")]
24+
#[cfg(feature = "mysql")]
25+
MySQL,
26+
}
27+
28+
impl From<&Auth> for DatabaseType {
29+
fn from(value: &Auth) -> Self {
30+
match value {
31+
crate::datasources::Auth::Postgres(_) => DatabaseType::PostgreSql,
32+
crate::datasources::Auth::SqlServer(_) => DatabaseType::SqlServer,
33+
crate::datasources::Auth::MySQL(_) => DatabaseType::MySQL,
34+
}
35+
}
2136
}
2237

2338
/// A connection with a `PostgreSQL` database
@@ -33,6 +48,12 @@ pub struct SqlServerConnection {
3348
pub client: &'static mut tiberius::Client<TcpStream>,
3449
}
3550

51+
/// A connection with a `Mysql` database
52+
#[cfg(feature = "mysql")]
53+
pub struct MysqlConnection {
54+
pub client: Pool,
55+
}
56+
3657
/// The Canyon database connection handler. When the client's program
3758
/// starts, Canyon gets the information about the desired datasources,
3859
/// process them and generates a pool of 1 to 1 database connection for
@@ -42,6 +63,8 @@ pub enum DatabaseConnection {
4263
Postgres(PostgreSqlConnection),
4364
#[cfg(feature = "mssql")]
4465
SqlServer(SqlServerConnection),
66+
#[cfg(feature = "mysql")]
67+
MySQL(MysqlConnection),
4568
}
4669

4770
unsafe impl Send for DatabaseConnection {}
@@ -64,6 +87,10 @@ impl DatabaseConnection {
6487
crate::datasources::Auth::SqlServer(_) => {
6588
panic!("Found SqlServer auth configuration for a PostgreSQL datasource")
6689
}
90+
#[cfg(feature = "mysql")]
91+
crate::datasources::Auth::MySQL(_) => {
92+
panic!("Found MySql auth configuration for a PostgreSQL datasource")
93+
}
6794
};
6895
let (new_client, new_connection) = tokio_postgres::connect(
6996
&format!(
@@ -109,6 +136,10 @@ impl DatabaseConnection {
109136
}
110137
crate::datasources::SqlServerAuth::Integrated => AuthMethod::Integrated,
111138
},
139+
#[cfg(feature = "mysql")]
140+
crate::datasources::Auth::MySQL(_) => {
141+
panic!("Found PostgreSQL auth configuration for a SqlServer database")
142+
}
112143
});
113144

114145
// on production, it is not a good idea to do this. We should upgrade
@@ -136,14 +167,49 @@ impl DatabaseConnection {
136167
)),
137168
}))
138169
}
170+
#[cfg(feature = "mysql")]
171+
DatabaseType::MySQL => {
172+
let (user, password) = match &datasource.auth {
173+
#[cfg(feature = "mssql")]
174+
crate::datasources::Auth::SqlServer(_) => {
175+
panic!("Found SqlServer auth configuration for a PostgreSQL datasource")
176+
}
177+
#[cfg(feature = "postgres")]
178+
crate::datasources::Auth::Postgres(_) => {
179+
panic!("Found MySql auth configuration for a PostgreSQL datasource")
180+
}
181+
#[cfg(feature = "mysql")]
182+
crate::datasources::Auth::MySQL(mysql_auth) => match mysql_auth {
183+
crate::datasources::MySQLAuth::Basic { username, password } => {
184+
(username, password)
185+
}
186+
},
187+
};
188+
189+
//TODO add options to optionals params in url
190+
191+
let url = format!(
192+
"mysql://{}:{}@{}:{}/{}",
193+
user,
194+
password,
195+
datasource.properties.host,
196+
datasource.properties.port.unwrap_or_default(),
197+
datasource.properties.db_name
198+
);
199+
let mysql_connection = Pool::from_url(url)?;
200+
201+
Ok(DatabaseConnection::MySQL(MysqlConnection {
202+
client: { mysql_connection },
203+
}))
204+
}
139205
}
140206
}
141207

142208
#[cfg(feature = "postgres")]
143209
pub fn postgres_connection(&self) -> &PostgreSqlConnection {
144210
match self {
145211
DatabaseConnection::Postgres(conn) => conn,
146-
#[cfg(all(feature = "postgres", feature = "mssql"))]
212+
#[cfg(all(feature = "postgres", feature = "mssql", feature = "mysql"))]
147213
_ => panic!(),
148214
}
149215
}
@@ -152,7 +218,16 @@ impl DatabaseConnection {
152218
pub fn sqlserver_connection(&mut self) -> &mut SqlServerConnection {
153219
match self {
154220
DatabaseConnection::SqlServer(conn) => conn,
155-
#[cfg(all(feature = "postgres", feature = "mssql"))]
221+
#[cfg(all(feature = "postgres", feature = "mssql", feature = "mysql"))]
222+
_ => panic!(),
223+
}
224+
}
225+
226+
#[cfg(feature = "mysql")]
227+
pub fn mysql_connection(&self) -> &MysqlConnection {
228+
match self {
229+
DatabaseConnection::MySQL(conn) => conn,
230+
#[cfg(all(feature = "postgres", feature = "mssql", feature = "mysql"))]
156231
_ => panic!(),
157232
}
158233
}
@@ -166,13 +241,14 @@ mod database_connection_handler {
166241
/// Tests the behaviour of the `DatabaseType::from_datasource(...)`
167242
#[test]
168243
fn check_from_datasource() {
169-
#[cfg(all(feature = "postgres", feature = "mssql"))]
244+
#[cfg(all(feature = "postgres", feature = "mssql", feature = "mysql"))]
170245
{
171246
const CONFIG_FILE_MOCK_ALT_ALL: &str = r#"
172247
[canyon_sql]
173248
datasources = [
174249
{name = 'PostgresDS', auth = { postgresql = { basic = { username = "postgres", password = "postgres" } } }, properties.host = 'localhost', properties.db_name = 'triforce', properties.migrations='enabled' },
175-
{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' }
250+
{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' },
251+
{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' }
176252
]
177253
"#;
178254
let config: CanyonSqlConfig = toml::from_str(CONFIG_FILE_MOCK_ALT_ALL)
@@ -185,6 +261,10 @@ mod database_connection_handler {
185261
config.canyon_sql.datasources[1].get_db_type(),
186262
DatabaseType::SqlServer
187263
);
264+
assert_eq!(
265+
config.canyon_sql.datasources[2].get_db_type(),
266+
DatabaseType::MySQL
267+
);
188268
}
189269

190270
#[cfg(feature = "postgres")]
@@ -218,5 +298,22 @@ mod database_connection_handler {
218298
DatabaseType::SqlServer
219299
);
220300
}
301+
302+
#[cfg(feature = "mysql")]
303+
{
304+
const CONFIG_FILE_MOCK_ALT_MYSQL: &str = r#"
305+
[canyon_sql]
306+
datasources = [
307+
{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' }
308+
]
309+
"#;
310+
311+
let config: CanyonSqlConfig = toml::from_str(CONFIG_FILE_MOCK_ALT_MYSQL)
312+
.expect("A failure happened retrieving the [canyon_sql] section");
313+
assert_eq!(
314+
config.canyon_sql.datasources[0].get_db_type(),
315+
DatabaseType::MySQL
316+
);
317+
}
221318
}
222319
}

canyon_connection/src/datasources.rs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ fn load_ds_config_from_array() {
1111
[canyon_sql]
1212
datasources = [
1313
{name = 'PostgresDS', auth = { postgresql = { basic = { username = "postgres", password = "postgres" } } }, properties.host = 'localhost', properties.db_name = 'triforce', properties.migrations='enabled' },
14-
]
14+
]
1515
"#;
1616
let config: CanyonSqlConfig = toml::from_str(CONFIG_FILE_MOCK_ALT_PG)
1717
.expect("A failure happened retrieving the [canyon_sql] section");
@@ -64,6 +64,33 @@ fn load_ds_config_from_array() {
6464

6565
assert_eq!(ds_2.auth, Auth::SqlServer(SqlServerAuth::Integrated));
6666
}
67+
#[cfg(feature = "mysql")]
68+
{
69+
const CONFIG_FILE_MOCK_ALT_MYSQL: &str = r#"
70+
[canyon_sql]
71+
datasources = [
72+
{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' }
73+
]
74+
"#;
75+
let config: CanyonSqlConfig = toml::from_str(CONFIG_FILE_MOCK_ALT_MYSQL)
76+
.expect("A failure happened retrieving the [canyon_sql] section");
77+
78+
let ds_1 = &config.canyon_sql.datasources[0];
79+
80+
assert_eq!(ds_1.name, "MysqlDS");
81+
assert_eq!(ds_1.get_db_type(), DatabaseType::MySQL);
82+
assert_eq!(
83+
ds_1.auth,
84+
Auth::MySQL(MySQLAuth::Basic {
85+
username: "root".to_string(),
86+
password: "root".to_string()
87+
})
88+
);
89+
assert_eq!(ds_1.properties.host, "192.168.0.250.1");
90+
assert_eq!(ds_1.properties.port, Some(3340));
91+
assert_eq!(ds_1.properties.db_name, "triforce2");
92+
assert_eq!(ds_1.properties.migrations, Some(Migrations::Disabled));
93+
}
6794
}
6895
///
6996
#[derive(Deserialize, Debug, Clone)]
@@ -90,18 +117,23 @@ impl DatasourceConfig {
90117
Auth::Postgres(_) => DatabaseType::PostgreSql,
91118
#[cfg(feature = "mssql")]
92119
Auth::SqlServer(_) => DatabaseType::SqlServer,
120+
#[cfg(feature = "mysql")]
121+
Auth::MySQL(_) => DatabaseType::MySQL,
93122
}
94123
}
95124
}
96125

97126
#[derive(Deserialize, Debug, Clone, PartialEq)]
98127
pub enum Auth {
99-
#[serde(alias = "PostgreSQL", alias = "postgresql", alias = "postgres")]
128+
#[serde(alias = "PostgresSQL", alias = "postgresql", alias = "postgres")]
100129
#[cfg(feature = "postgres")]
101130
Postgres(PostgresAuth),
102131
#[serde(alias = "SqlServer", alias = "sqlserver", alias = "mssql")]
103132
#[cfg(feature = "mssql")]
104133
SqlServer(SqlServerAuth),
134+
#[serde(alias = "MYSQL", alias = "mysql", alias = "MySQL")]
135+
#[cfg(feature = "mysql")]
136+
MySQL(MySQLAuth),
105137
}
106138

107139
#[derive(Deserialize, Debug, Clone, PartialEq)]
@@ -120,6 +152,13 @@ pub enum SqlServerAuth {
120152
Integrated,
121153
}
122154

155+
#[derive(Deserialize, Debug, Clone, PartialEq)]
156+
#[cfg(feature = "mysql")]
157+
pub enum MySQLAuth {
158+
#[serde(alias = "Basic", alias = "basic")]
159+
Basic { username: String, password: String },
160+
}
161+
123162
#[derive(Deserialize, Debug, Clone)]
124163
pub struct DatasourceProperties {
125164
pub host: String,

canyon_connection/src/lib.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
pub extern crate async_std;
33
pub extern crate futures;
44
pub extern crate lazy_static;
5+
#[cfg(feature = "mysql")]
6+
pub extern crate mysql_async;
57
#[cfg(feature = "mssql")]
68
pub extern crate tiberius;
79
pub extern crate tokio;
@@ -104,3 +106,19 @@ pub fn get_database_connection<'a>(
104106
)
105107
}
106108
}
109+
110+
pub fn get_database_config<'a>(
111+
datasource_name: &str,
112+
datasources_config: &'a [DatasourceConfig],
113+
) -> &'a DatasourceConfig {
114+
if datasource_name.is_empty() {
115+
datasources_config
116+
.get(0)
117+
.unwrap_or_else(|| panic!("Not exist datasource"))
118+
} else {
119+
datasources_config
120+
.iter()
121+
.find(|dc| dc.name == datasource_name)
122+
.unwrap_or_else(|| panic!("Not found datasource expected {datasource_name}"))
123+
}
124+
}

0 commit comments

Comments
 (0)