1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use std::ffi::c_char;
use std::slice;

use crate::bindings;

/// A SQL type and value
#[non_exhaustive]
#[derive(Clone, Copy, Debug)]
pub enum Value<'a> {
    Decimal(&'a [u8]),
    I8(i8),
    I16(i16),
    Long(i64),
    LongLong(i64),
    F32(f32),
    F64(f64),
    Null,
    Time,      // todo
    TimeStamp, // todo
    Date,      // todo
    DateTime,  // todo
    Year,      // todo
    String(&'a [u8]),
    Blob(&'a [u8]),
    Json(&'a [u8]),
}

impl<'a> Value<'a> {
    /// Don't ask me why but our responses from the server API seem to all be strings
    /// So: take a pointer to the string then parse it as whatever value we expect
    pub(crate) unsafe fn from_str_ptr(
        ty: bindings::enum_field_types::Type,
        ptr: *const c_char,
        len: usize,
    ) -> Self {
        // SAFETY: caller guarantees validity
        let bytes: &[u8] = unsafe { slice::from_raw_parts(ptr.cast(), len) };
        let tostr = |bytes| std::str::from_utf8(bytes).unwrap();

        match ty {
            bindings::enum_field_types::MYSQL_TYPE_DECIMAL => Self::Decimal(bytes),
            // TODO: seems like we get unaligned pointers?
            // bindings::enum_field_types::MYSQL_TYPE_TINY => Self::Tiny(ptr::read_unaligned(ptr.cast())),
            // bindings::enum_field_types::MYSQL_TYPE_SHORT => Self::Short(ptr::read_unaligned(ptr.cast())),
            // bindings::enum_field_types::MYSQL_TYPE_LONG => Self::Long(ptr::read_unaligned(ptr.cast())),
            // bindings::enum_field_types::MYSQL_TYPE_FLOAT => Self::Float(ptr::read_unaligned(ptr.cast())),
            // bindings::enum_field_types::MYSQL_TYPE_DOUBLE => Self::Double(ptr::read_unaligned(ptr.cast())),
            bindings::enum_field_types::MYSQL_TYPE_TINY => Self::I8(tostr(bytes).parse().unwrap()),
            bindings::enum_field_types::MYSQL_TYPE_SHORT => {
                Self::I16(tostr(bytes).parse().unwrap())
            }
            // This is yucky, `long` is `i32` on Windows but `i64` on nix. So, we load it as a `long` but
            // always store it as `i64`.
            bindings::enum_field_types::MYSQL_TYPE_LONG => {
                Self::Long(tostr(bytes).parse().unwrap())
            }
            bindings::enum_field_types::MYSQL_TYPE_LONGLONG => {
                Self::LongLong(tostr(bytes).parse().unwrap())
            }
            bindings::enum_field_types::MYSQL_TYPE_FLOAT => {
                Self::F32(tostr(bytes).parse().unwrap())
            }
            bindings::enum_field_types::MYSQL_TYPE_DOUBLE => {
                Self::F64(tostr(bytes).parse().unwrap())
            }
            bindings::enum_field_types::MYSQL_TYPE_NULL => Self::Null,
            bindings::enum_field_types::MYSQL_TYPE_TIMESTAMP => todo!(),
            bindings::enum_field_types::MYSQL_TYPE_INT24 => todo!(),
            bindings::enum_field_types::MYSQL_TYPE_DATE => todo!(),
            bindings::enum_field_types::MYSQL_TYPE_TIME => todo!(),
            bindings::enum_field_types::MYSQL_TYPE_DATETIME => todo!(),
            bindings::enum_field_types::MYSQL_TYPE_YEAR => todo!(),
            bindings::enum_field_types::MYSQL_TYPE_NEWDATE => todo!(),
            bindings::enum_field_types::MYSQL_TYPE_VARCHAR => todo!(),
            bindings::enum_field_types::MYSQL_TYPE_BIT => todo!(),
            bindings::enum_field_types::MYSQL_TYPE_TIMESTAMP2 => todo!(),
            bindings::enum_field_types::MYSQL_TYPE_DATETIME2 => todo!(),
            bindings::enum_field_types::MYSQL_TYPE_TIME2 => todo!(),
            bindings::enum_field_types::MYSQL_TYPE_BLOB_COMPRESSED => todo!(),
            bindings::enum_field_types::MYSQL_TYPE_VARCHAR_COMPRESSED => todo!(),
            bindings::enum_field_types::MYSQL_TYPE_NEWDECIMAL => todo!(),
            bindings::enum_field_types::MYSQL_TYPE_ENUM => todo!(),
            bindings::enum_field_types::MYSQL_TYPE_SET => todo!(),
            bindings::enum_field_types::MYSQL_TYPE_TINY_BLOB
            | bindings::enum_field_types::MYSQL_TYPE_MEDIUM_BLOB
            | bindings::enum_field_types::MYSQL_TYPE_LONG_BLOB
            | bindings::enum_field_types::MYSQL_TYPE_BLOB => Self::Blob(bytes),
            bindings::enum_field_types::MYSQL_TYPE_VAR_STRING
            | bindings::enum_field_types::MYSQL_TYPE_STRING => Self::String(bytes),
            bindings::enum_field_types::MYSQL_TYPE_GEOMETRY => todo!(),
            _ => todo!(),
        }
    }

    pub fn as_int(&self) -> Option<i64> {
        match self {
            Value::I8(v) => Some((*v).into()),
            Value::I16(v) => Some((*v).into()),
            Value::Long(v) | Value::LongLong(v) => Some((*v).into()),
            _ => None,
        }
    }

    /// Note: a lot of things that would probably be strings actually show up as blobs. Likely
    /// encoding related.
    pub fn as_str(&self) -> Option<&'a str> {
        match self {
            Value::String(v) => std::str::from_utf8(v).ok(),
            _ => None,
        }
    }

    pub fn as_bytes(&self) -> Option<&'a [u8]> {
        match self {
            Value::String(v) | Value::Blob(v) | Value::Json(v) => Some(*v),
            _ => None,
        }
    }
}