db_mover/databases/mysql/
value.rs

1use chrono::{NaiveDateTime, TimeZone, Utc};
2
3use crate::databases::table::{Column, ColumnType, Value};
4
5#[derive(Clone, Debug, PartialEq)]
6pub struct MysqlTypeOptions {
7    pub binary_16_as_uuid: bool,
8    pub tinyint_as_bool: bool,
9}
10
11impl Default for MysqlTypeOptions {
12    fn default() -> Self {
13        return MysqlTypeOptions {
14            binary_16_as_uuid: true,
15            tinyint_as_bool: true,
16        };
17    }
18}
19
20impl ColumnType {
21    pub fn try_from_mysql_type(
22        type_name: &str,
23        options: &MysqlTypeOptions,
24    ) -> anyhow::Result<ColumnType> {
25        let formated = type_name.trim().to_lowercase();
26        if options.binary_16_as_uuid && formated == "binary(16)" {
27            return Ok(ColumnType::Uuid);
28        }
29        if options.tinyint_as_bool && formated == "tinyint(1)" {
30            return Ok(ColumnType::Bool);
31        }
32        if formated.starts_with("char") || formated.starts_with("varchar") {
33            return Ok(ColumnType::String);
34        }
35        if formated.starts_with("binary") || formated.starts_with("varbinary") {
36            return Ok(ColumnType::Bytes);
37        }
38        if formated.starts_with("smallint") {
39            return Ok(ColumnType::I16);
40        }
41        if formated.starts_with("int") {
42            return Ok(ColumnType::I32);
43        }
44        if formated.starts_with("bigint") {
45            return Ok(ColumnType::I64);
46        }
47        if formated.starts_with("numeric") || formated.starts_with("decimal") {
48            return Ok(ColumnType::Decimal);
49        }
50        return match formated.as_str() {
51            "float" => Ok(ColumnType::F32),
52            "double" | "real" | "double precision" => Ok(ColumnType::F64),
53            "bool" | "boolean" => Ok(ColumnType::Bool),
54            "tinytext" | "text" | "mediumtext" | "longtext" => Ok(ColumnType::String),
55            "tinyblob" | "blob" | "mediumblob" | "longblob" => Ok(ColumnType::Bytes),
56            "timestamp" => Ok(ColumnType::Timestamptz),
57            "datetime" => Ok(ColumnType::Timestamp),
58            "date" => Ok(ColumnType::Date),
59            "time" => Ok(ColumnType::Time),
60            "json" => Ok(ColumnType::Json),
61            _ => Err(anyhow::anyhow!("Unknown column type {type_name}")),
62        };
63    }
64}
65
66impl TryFrom<(&Column, mysql::Value)> for Value {
67    type Error = anyhow::Error;
68
69    fn try_from(value: (&Column, mysql::Value)) -> Result<Self, Self::Error> {
70        let (column, val) = value;
71        if val == mysql::Value::NULL {
72            return Ok(Value::Null);
73        }
74        let parsed = match column.column_type {
75            ColumnType::I64 => Value::I64(mysql::from_value_opt(val)?),
76            ColumnType::I32 => Value::I32(mysql::from_value_opt(val)?),
77            ColumnType::I16 => Value::I16(mysql::from_value_opt(val)?),
78            ColumnType::F64 => Value::F64(mysql::from_value_opt(val)?),
79            ColumnType::F32 => Value::F32(mysql::from_value_opt(val)?),
80            ColumnType::Decimal => Value::Decimal(mysql::from_value_opt(val)?),
81            ColumnType::Bool => Value::Bool(mysql::from_value_opt(val)?),
82            ColumnType::String => Value::String(mysql::from_value_opt(val)?),
83            ColumnType::Bytes => {
84                Value::Bytes(bytes::Bytes::from(mysql::from_value_opt::<Vec<u8>>(val)?))
85            }
86            ColumnType::Timestamp => Value::Timestamp(mysql::from_value_opt(val)?),
87            ColumnType::Timestamptz => {
88                let dt: NaiveDateTime = mysql::from_value_opt(val)?;
89                Value::Timestamptz(Utc.from_utc_datetime(&dt)) // UTC timezone set on connection
90            }
91            ColumnType::Date => Value::Date(mysql::from_value_opt(val)?),
92            ColumnType::Time => Value::Time(mysql::from_value_opt(val)?),
93            ColumnType::Json => Value::Json(mysql::from_value_opt(val)?),
94            ColumnType::Uuid => Value::Uuid(mysql::from_value_opt(val)?),
95        };
96        return Ok(parsed);
97    }
98}
99
100impl From<&Value> for mysql::Value {
101    fn from(value: &Value) -> Self {
102        match value {
103            Value::Null => mysql::Value::NULL,
104            Value::I64(val) => val.into(),
105            Value::I32(val) => val.into(),
106            Value::I16(val) => val.into(),
107            Value::F64(val) => val.into(),
108            Value::F32(val) => val.into(),
109            Value::Decimal(val) => val.into(),
110            Value::Bool(val) => val.into(),
111            Value::String(val) => val.into(),
112            Value::Bytes(val) => val.as_ref().into(),
113            Value::Timestamptz(val) => val.naive_utc().into(),
114            Value::Timestamp(val) => val.into(),
115            Value::Date(val) => val.into(),
116            Value::Time(val) => val.into(),
117            Value::Json(val) => val.into(),
118            Value::Uuid(val) => val.into(),
119        }
120    }
121}