Skip to content

Commit 0cf234b

Browse files
committed
feat(sdk): Add MySQL types mapping
- Extract outbound-mysql into separate crate - Add integration test - Add connections reusing during the request - Improve MySQL types conversion Signed-off-by: Konstantin Shabanov <[email protected]>
1 parent 4d72886 commit 0cf234b

File tree

20 files changed

+857
-50
lines changed

20 files changed

+857
-50
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ vergen = { version = "7", default-features = false, features = [ "build", "git"
7676
default = []
7777
e2e-tests = []
7878
outbound-redis-tests = []
79+
outbound-mysql-tests = []
7980

8081
[workspace]
8182
members = [

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ test-e2e:
3737
test-outbound-redis:
3838
RUST_LOG=$(LOG_LEVEL) cargo test --test integration --features outbound-redis-tests --no-fail-fast -- --nocapture
3939

40+
.PHONY: test-outbound-mysql
41+
test-outbound-mysql:
42+
RUST_LOG=$(LOG_LEVEL) cargo test --test integration --features outbound-mysql-tests --no-fail-fast -- --nocapture
43+
4044
.PHONY: test-sdk-go
4145
test-sdk-go:
4246
$(MAKE) -C sdk/go test
@@ -46,4 +50,4 @@ test-sdk-go:
4650
tls: ${CERT_NAME}.crt.pem
4751

4852
$(CERT_NAME).crt.pem:
49-
openssl req -newkey rsa:2048 -nodes -keyout $(CERT_NAME).key.pem -x509 -days 365 -out $(CERT_NAME).crt.pem
53+
openssl req -newkey rsa:2048 -nodes -keyout $(CERT_NAME).key.pem -x509 -days 365 -out $(CERT_NAME).crt.pem

crates/outbound-mysql/src/lib.rs

Lines changed: 66 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ use mysql_async::consts::ColumnType;
22
use mysql_async::{from_value_opt, prelude::*};
33
pub use outbound_mysql::add_to_linker;
44
use spin_core::HostComponent;
5+
use std::collections::HashMap;
56
use std::sync::Arc;
67
use wit_bindgen_wasmtime::async_trait;
78

89
wit_bindgen_wasmtime::export!({paths: ["../../wit/ephemeral/outbound-mysql.wit"], async: *});
910
use outbound_mysql::*;
1011

1112
/// A simple implementation to support outbound mysql connection
12-
#[derive(Default, Clone)]
13-
pub struct OutboundMysql;
13+
#[derive(Default)]
14+
pub struct OutboundMysql {
15+
pub connections: HashMap<String, mysql_async::Conn>,
16+
}
1417

1518
impl HostComponent for OutboundMysql {
1619
type Data = Self;
@@ -35,20 +38,17 @@ impl outbound_mysql::OutboundMysql for OutboundMysql {
3538
statement: &str,
3639
params: Vec<ParameterValue<'_>>,
3740
) -> Result<(), MysqlError> {
38-
let connection_pool = mysql_async::Pool::new(address);
39-
let mut connection = connection_pool
40-
.get_conn()
41-
.await
42-
.map_err(|e| MysqlError::ConnectionFailed(format!("{:?}", e)))?;
43-
4441
let db_params = params
4542
.iter()
4643
.map(to_sql_parameter)
4744
.collect::<anyhow::Result<Vec<_>>>()
4845
.map_err(|e| MysqlError::QueryFailed(format!("{:?}", e)))?;
4946

5047
let parameters = mysql_async::Params::Positional(db_params);
51-
connection
48+
49+
self.get_conn(address)
50+
.await
51+
.map_err(|e| MysqlError::ConnectionFailed(format!("{:?}", e)))?
5252
.exec_batch(statement, &[parameters])
5353
.await
5454
.map_err(|e| MysqlError::QueryFailed(format!("{:?}", e)))?;
@@ -62,20 +62,18 @@ impl outbound_mysql::OutboundMysql for OutboundMysql {
6262
statement: &str,
6363
params: Vec<ParameterValue<'_>>,
6464
) -> Result<RowSet, MysqlError> {
65-
let connection_pool = mysql_async::Pool::new(address);
66-
let mut connection = connection_pool
67-
.get_conn()
68-
.await
69-
.map_err(|e| MysqlError::ConnectionFailed(format!("{:?}", e)))?;
70-
7165
let db_params = params
7266
.iter()
7367
.map(to_sql_parameter)
7468
.collect::<anyhow::Result<Vec<_>>>()
7569
.map_err(|e| MysqlError::QueryFailed(format!("{:?}", e)))?;
7670

7771
let parameters = mysql_async::Params::Positional(db_params);
78-
let mut query_result = connection
72+
73+
let mut query_result = self
74+
.get_conn(address)
75+
.await
76+
.map_err(|e| MysqlError::ConnectionFailed(format!("{:?}", e)))?
7977
.exec_iter(statement, parameters)
8078
.await
8179
.map_err(|e| MysqlError::QueryFailed(format!("{:?}", e)))?;
@@ -132,24 +130,45 @@ fn convert_column(column: &mysql_async::Column) -> Column {
132130
}
133131

134132
fn convert_data_type(column: &mysql_async::Column) -> DbDataType {
135-
match (column.column_type(), is_signed(column)) {
136-
(ColumnType::MYSQL_TYPE_BIT, _) => DbDataType::Boolean,
133+
let column_type = column.column_type();
134+
135+
if column_type.is_numeric_type() {
136+
convert_numeric_type(column)
137+
} else if column_type.is_character_type() {
138+
convert_character_type(column)
139+
} else {
140+
DbDataType::Other
141+
}
142+
}
143+
144+
fn convert_character_type(column: &mysql_async::Column) -> DbDataType {
145+
match (column.column_type(), is_binary(column)) {
146+
(ColumnType::MYSQL_TYPE_BLOB, false) => DbDataType::Str, // TEXT type
137147
(ColumnType::MYSQL_TYPE_BLOB, _) => DbDataType::Binary,
148+
(ColumnType::MYSQL_TYPE_LONG_BLOB, _) => DbDataType::Binary,
149+
(ColumnType::MYSQL_TYPE_MEDIUM_BLOB, _) => DbDataType::Binary,
150+
(ColumnType::MYSQL_TYPE_STRING, true) => DbDataType::Binary, // BINARY type
151+
(ColumnType::MYSQL_TYPE_STRING, _) => DbDataType::Str,
152+
(ColumnType::MYSQL_TYPE_VAR_STRING, true) => DbDataType::Binary, // VARBINARY type
153+
(ColumnType::MYSQL_TYPE_VAR_STRING, _) => DbDataType::Str,
154+
(_, _) => DbDataType::Other,
155+
}
156+
}
157+
158+
fn convert_numeric_type(column: &mysql_async::Column) -> DbDataType {
159+
match (column.column_type(), is_signed(column)) {
138160
(ColumnType::MYSQL_TYPE_DOUBLE, _) => DbDataType::Floating64,
139161
(ColumnType::MYSQL_TYPE_FLOAT, _) => DbDataType::Floating32,
162+
(ColumnType::MYSQL_TYPE_INT24, true) => DbDataType::Int32,
163+
(ColumnType::MYSQL_TYPE_INT24, false) => DbDataType::Uint32,
140164
(ColumnType::MYSQL_TYPE_LONG, true) => DbDataType::Int32,
141165
(ColumnType::MYSQL_TYPE_LONG, false) => DbDataType::Uint32,
142166
(ColumnType::MYSQL_TYPE_LONGLONG, true) => DbDataType::Int64,
143167
(ColumnType::MYSQL_TYPE_LONGLONG, false) => DbDataType::Uint64,
144-
(ColumnType::MYSQL_TYPE_LONG_BLOB, _) => DbDataType::Binary,
145-
(ColumnType::MYSQL_TYPE_MEDIUM_BLOB, _) => DbDataType::Binary,
146168
(ColumnType::MYSQL_TYPE_SHORT, true) => DbDataType::Int16,
147169
(ColumnType::MYSQL_TYPE_SHORT, false) => DbDataType::Uint16,
148-
(ColumnType::MYSQL_TYPE_STRING, _) => DbDataType::Str,
149170
(ColumnType::MYSQL_TYPE_TINY, true) => DbDataType::Int8,
150171
(ColumnType::MYSQL_TYPE_TINY, false) => DbDataType::Uint8,
151-
(ColumnType::MYSQL_TYPE_VARCHAR, _) => DbDataType::Str,
152-
(ColumnType::MYSQL_TYPE_VAR_STRING, _) => DbDataType::Str,
153172
(_, _) => DbDataType::Other,
154173
}
155174
}
@@ -160,6 +179,12 @@ fn is_signed(column: &mysql_async::Column) -> bool {
160179
.contains(mysql_async::consts::ColumnFlags::UNSIGNED_FLAG)
161180
}
162181

182+
fn is_binary(column: &mysql_async::Column) -> bool {
183+
column
184+
.flags()
185+
.contains(mysql_async::consts::ColumnFlags::BINARY_FLAG)
186+
}
187+
163188
fn convert_row(mut row: mysql_async::Row, columns: &[Column]) -> Result<Vec<DbValue>, MysqlError> {
164189
let mut result = Vec::with_capacity(row.len());
165190
for index in 0..row.len() {
@@ -206,6 +231,24 @@ fn convert_value(value: mysql_async::Value, column: &Column) -> Result<DbValue,
206231
}
207232
}
208233

234+
impl OutboundMysql {
235+
async fn get_conn(&mut self, address: &str) -> anyhow::Result<&mut mysql_async::Conn> {
236+
let client = match self.connections.entry(address.to_owned()) {
237+
std::collections::hash_map::Entry::Occupied(o) => o.into_mut(),
238+
std::collections::hash_map::Entry::Vacant(v) => v.insert(build_conn(address).await?),
239+
};
240+
Ok(client)
241+
}
242+
}
243+
244+
async fn build_conn(address: &str) -> Result<mysql_async::Conn, mysql_async::Error> {
245+
tracing::log::debug!("Build new connection: {}", address);
246+
247+
let connection_pool = mysql_async::Pool::new(address);
248+
249+
connection_pool.get_conn().await
250+
}
251+
209252
fn convert_value_to<T: FromValue>(value: mysql_async::Value) -> Result<T, MysqlError> {
210253
from_value_opt::<T>(value).map_err(|e| MysqlError::ValueConversionFailed(format!("{}", e)))
211254
}

examples/config-rust/Cargo.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/http-rust-outbound-http/Cargo.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/http-rust/Cargo.lock

Lines changed: 32 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/redis-rust/Cargo.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)