use crate::wit_bindgen;
use std::sync::Arc;
#[doc(hidden)]
pub mod wit {
#![allow(missing_docs)]
use crate::wit_bindgen;
wit_bindgen::generate!({
runtime_path: "crate::wit_bindgen::rt",
world: "spin-sdk-mysql-v3",
path: "wit",
generate_all,
});
pub use spin::mysql::mysql;
}
pub struct Connection(wit::mysql::Connection);
impl Connection {
pub async fn open(address: impl Into<String>) -> Result<Self, Error> {
let inner = wit::mysql::Connection::open(address.into()).await?;
Ok(Self(inner))
}
pub async fn query(
&self,
statement: impl Into<String>,
params: impl Into<Vec<ParameterValue>>,
) -> Result<QueryResult, Error> {
let (columns, rows, result) = self.0.query(statement.into(), params.into()).await?;
Ok(QueryResult {
columns: Arc::new(columns),
rows,
result,
})
}
pub async fn execute(
&self,
statement: impl Into<String>,
params: impl Into<Vec<ParameterValue>>,
) -> Result<(), Error> {
self.0
.execute(statement.into(), params.into())
.await
.map_err(Error::MysqlError)
}
}
#[doc(inline)]
pub use wit::mysql::Error as MysqlError;
#[doc(inline)]
pub use wit::mysql::{Column, DbDataType, DbValue, ParameterValue};
pub struct QueryResult {
columns: Arc<Vec<Column>>,
rows: wit_bindgen::StreamReader<Vec<DbValue>>,
result: wit_bindgen::FutureReader<Result<(), MysqlError>>,
}
impl QueryResult {
pub fn columns(&self) -> &[Column] {
&self.columns
}
pub async fn next(&mut self) -> Option<Row> {
self.rows.next().await.map(|r| Row {
columns: self.columns.clone(),
result: r,
})
}
pub async fn result(self) -> Result<(), Error> {
self.result.await.map_err(Error::MysqlError)
}
pub async fn collect(mut self) -> Result<Vec<Row>, Error> {
let mut rows = vec![];
while let Some(row) = self.next().await {
rows.push(row);
}
self.result.await.map_err(Error::MysqlError)?;
Ok(rows)
}
pub fn rows(&mut self) -> &mut wit_bindgen::StreamReader<Vec<DbValue>> {
&mut self.rows
}
#[allow(
clippy::type_complexity,
reason = "sorry clippy that's just what the inner bits are"
)]
pub fn into_inner(
self,
) -> (
Vec<Column>,
wit_bindgen::StreamReader<Vec<DbValue>>,
wit_bindgen::FutureReader<Result<(), MysqlError>>,
) {
((*self.columns).clone(), self.rows, self.result)
}
}
pub struct Row {
columns: Arc<Vec<wit::mysql::Column>>,
result: Vec<DbValue>,
}
impl Row {
pub fn get<T: Decode>(&self, column: &str) -> Option<T> {
let i = self.columns.iter().position(|c| c.name == column)?;
let db_value = self.result.get(i)?;
Decode::decode(db_value).ok()
}
}
impl std::ops::Index<usize> for Row {
type Output = DbValue;
fn index(&self, index: usize) -> &Self::Output {
&self.result[index]
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("error value decoding: {0}")]
Decode(String),
#[error(transparent)]
MysqlError(#[from] MysqlError),
}
pub trait Decode: Sized {
fn decode(value: &DbValue) -> Result<Self, Error>;
}
impl<T> Decode for Option<T>
where
T: Decode,
{
fn decode(value: &DbValue) -> Result<Self, Error> {
match value {
DbValue::DbNull => Ok(None),
v => Ok(Some(T::decode(v)?)),
}
}
}
impl Decode for bool {
fn decode(value: &DbValue) -> Result<Self, Error> {
match value {
DbValue::Int8(0) => Ok(false),
DbValue::Int8(1) => Ok(true),
_ => Err(Error::Decode(format_decode_err(
"TINYINT(1), BOOLEAN",
value,
))),
}
}
}
impl Decode for i8 {
fn decode(value: &DbValue) -> Result<Self, Error> {
match value {
DbValue::Int8(n) => Ok(*n),
_ => Err(Error::Decode(format_decode_err("TINYINT", value))),
}
}
}
impl Decode for i16 {
fn decode(value: &DbValue) -> Result<Self, Error> {
match value {
DbValue::Int16(n) => Ok(*n),
_ => Err(Error::Decode(format_decode_err("SMALLINT", value))),
}
}
}
impl Decode for i32 {
fn decode(value: &DbValue) -> Result<Self, Error> {
match value {
DbValue::Int32(n) => Ok(*n),
_ => Err(Error::Decode(format_decode_err("INT", value))),
}
}
}
impl Decode for i64 {
fn decode(value: &DbValue) -> Result<Self, Error> {
match value {
DbValue::Int64(n) => Ok(*n),
_ => Err(Error::Decode(format_decode_err("BIGINT", value))),
}
}
}
impl Decode for u8 {
fn decode(value: &DbValue) -> Result<Self, Error> {
match value {
DbValue::Uint8(n) => Ok(*n),
_ => Err(Error::Decode(format_decode_err("UNSIGNED TINYINT", value))),
}
}
}
impl Decode for u16 {
fn decode(value: &DbValue) -> Result<Self, Error> {
match value {
DbValue::Uint16(n) => Ok(*n),
_ => Err(Error::Decode(format_decode_err("UNSIGNED SMALLINT", value))),
}
}
}
impl Decode for u32 {
fn decode(value: &DbValue) -> Result<Self, Error> {
match value {
DbValue::Uint32(n) => Ok(*n),
_ => Err(Error::Decode(format_decode_err(
"UNISIGNED MEDIUMINT, UNSIGNED INT",
value,
))),
}
}
}
impl Decode for u64 {
fn decode(value: &DbValue) -> Result<Self, Error> {
match value {
DbValue::Uint64(n) => Ok(*n),
_ => Err(Error::Decode(format_decode_err("UNSIGNED BIGINT", value))),
}
}
}
impl Decode for f32 {
fn decode(value: &DbValue) -> Result<Self, Error> {
match value {
DbValue::Floating32(n) => Ok(*n),
_ => Err(Error::Decode(format_decode_err("FLOAT", value))),
}
}
}
impl Decode for f64 {
fn decode(value: &DbValue) -> Result<Self, Error> {
match value {
DbValue::Floating64(n) => Ok(*n),
_ => Err(Error::Decode(format_decode_err("DOUBLE", value))),
}
}
}
impl Decode for Vec<u8> {
fn decode(value: &DbValue) -> Result<Self, Error> {
match value {
DbValue::Binary(n) => Ok(n.to_owned()),
_ => Err(Error::Decode(format_decode_err("BINARY, VARBINARY", value))),
}
}
}
impl Decode for String {
fn decode(value: &DbValue) -> Result<Self, Error> {
match value {
DbValue::Str(s) => Ok(s.to_owned()),
_ => Err(Error::Decode(format_decode_err(
"CHAR, VARCHAR, TEXT",
value,
))),
}
}
}
macro_rules! impl_parameter_value_conversions {
($($ty:ty => $id:ident),*) => {
$(
impl From<$ty> for ParameterValue {
fn from(v: $ty) -> ParameterValue {
ParameterValue::$id(v)
}
}
)*
};
}
impl_parameter_value_conversions! {
i8 => Int8,
i16 => Int16,
i32 => Int32,
i64 => Int64,
f32 => Floating32,
f64 => Floating64,
bool => Boolean,
String => Str,
Vec<u8> => Binary
}
fn format_decode_err(types: &str, value: &DbValue) -> String {
format!("Expected {} from the DB but got {:?}", types, value)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn boolean() {
assert!(bool::decode(&DbValue::Int8(1)).unwrap());
assert!(bool::decode(&DbValue::Int8(3)).is_err());
assert!(bool::decode(&DbValue::Int32(0)).is_err());
assert!(Option::<bool>::decode(&DbValue::DbNull).unwrap().is_none());
}
#[test]
fn int8() {
assert_eq!(i8::decode(&DbValue::Int8(0)).unwrap(), 0);
assert!(i8::decode(&DbValue::Int32(0)).is_err());
assert!(Option::<i8>::decode(&DbValue::DbNull).unwrap().is_none());
}
#[test]
fn int16() {
assert_eq!(i16::decode(&DbValue::Int16(0)).unwrap(), 0);
assert!(i16::decode(&DbValue::Int32(0)).is_err());
assert!(Option::<i16>::decode(&DbValue::DbNull).unwrap().is_none());
}
#[test]
fn int32() {
assert_eq!(i32::decode(&DbValue::Int32(0)).unwrap(), 0);
assert!(i32::decode(&DbValue::Boolean(false)).is_err());
assert!(Option::<i32>::decode(&DbValue::DbNull).unwrap().is_none());
}
#[test]
fn int64() {
assert_eq!(i64::decode(&DbValue::Int64(0)).unwrap(), 0);
assert!(i64::decode(&DbValue::Boolean(false)).is_err());
assert!(Option::<i64>::decode(&DbValue::DbNull).unwrap().is_none());
}
#[test]
fn uint8() {
assert_eq!(u8::decode(&DbValue::Uint8(0)).unwrap(), 0);
assert!(u8::decode(&DbValue::Uint32(0)).is_err());
assert!(Option::<u16>::decode(&DbValue::DbNull).unwrap().is_none());
}
#[test]
fn uint16() {
assert_eq!(u16::decode(&DbValue::Uint16(0)).unwrap(), 0);
assert!(u16::decode(&DbValue::Uint32(0)).is_err());
assert!(Option::<u16>::decode(&DbValue::DbNull).unwrap().is_none());
}
#[test]
fn uint32() {
assert_eq!(u32::decode(&DbValue::Uint32(0)).unwrap(), 0);
assert!(u32::decode(&DbValue::Boolean(false)).is_err());
assert!(Option::<u32>::decode(&DbValue::DbNull).unwrap().is_none());
}
#[test]
fn uint64() {
assert_eq!(u64::decode(&DbValue::Uint64(0)).unwrap(), 0);
assert!(u64::decode(&DbValue::Boolean(false)).is_err());
assert!(Option::<u64>::decode(&DbValue::DbNull).unwrap().is_none());
}
#[test]
fn floating32() {
assert!(f32::decode(&DbValue::Floating32(0.0)).is_ok());
assert!(f32::decode(&DbValue::Boolean(false)).is_err());
assert!(Option::<f32>::decode(&DbValue::DbNull).unwrap().is_none());
}
#[test]
fn floating64() {
assert!(f64::decode(&DbValue::Floating64(0.0)).is_ok());
assert!(f64::decode(&DbValue::Boolean(false)).is_err());
assert!(Option::<f64>::decode(&DbValue::DbNull).unwrap().is_none());
}
#[test]
fn str() {
assert_eq!(
String::decode(&DbValue::Str(String::from("foo"))).unwrap(),
String::from("foo")
);
assert!(String::decode(&DbValue::Int32(0)).is_err());
assert!(
Option::<String>::decode(&DbValue::DbNull)
.unwrap()
.is_none()
);
}
}