#![allow(unsafe_code)] // module uses ffi use mysqlclient_sys as ffi; use std::mem; use std::mem::MaybeUninit; use std::ops::Index; use std::os::raw as libc; use std::ptr::NonNull; use super::stmt::MysqlFieldMetadata; use super::stmt::StatementUse; use crate::mysql::connection::stmt::StatementMetadata; use crate::mysql::types::date_and_time::MysqlTime; use crate::mysql::{MysqlType, MysqlValue}; use crate::result::QueryResult; pub(super) struct PreparedStatementBinds(Binds); pub(super) struct OutputBinds(Binds); impl Clone for OutputBinds { fn clone(&self) -> Self { Self(Binds { data: self.0.data.clone(), }) } } struct Binds { data: Vec, } impl PreparedStatementBinds { pub(super) fn from_input_data(input: Iter) -> Self where Iter: IntoIterator>)>, { let data = input .into_iter() .map(BindData::for_input) .collect::>(); Self(Binds { data }) } pub(super) fn with_mysql_binds(&mut self, f: F) -> T where F: FnOnce(*mut ffi::MYSQL_BIND) -> T, { self.0.with_mysql_binds(f) } } impl OutputBinds { pub(super) fn from_output_types( types: &[Option], metadata: &StatementMetadata, ) -> Self { let data = metadata .fields() .iter() .zip(types.iter().copied().chain(std::iter::repeat(None))) .map(|(field, tpe)| BindData::for_output(tpe, field)) .collect(); Self(Binds { data }) } pub(super) fn populate_dynamic_buffers(&mut self, stmt: &StatementUse<'_>) -> QueryResult<()> { for (i, data) in self.0.data.iter_mut().enumerate() { data.did_numeric_overflow_occur()?; // This is safe because we are re-binding the invalidated buffers // at the end of this function unsafe { if let Some((mut bind, offset)) = data.bind_for_truncated_data() { stmt.fetch_column(&mut bind, i, offset)? } else { data.update_buffer_length() } } } unsafe { self.with_mysql_binds(|bind_ptr| stmt.bind_result(bind_ptr)) } } pub(super) fn update_buffer_lengths(&mut self) { for data in &mut self.0.data { data.update_buffer_length(); } } pub(super) fn with_mysql_binds(&mut self, f: F) -> T where F: FnOnce(*mut ffi::MYSQL_BIND) -> T, { self.0.with_mysql_binds(f) } } impl Binds { fn with_mysql_binds(&mut self, f: F) -> T where F: FnOnce(*mut ffi::MYSQL_BIND) -> T, { let mut binds = self .data .iter_mut() .map(|x| unsafe { x.mysql_bind() }) .collect::>(); f(binds.as_mut_ptr()) } } impl Index for OutputBinds { type Output = BindData; fn index(&self, index: usize) -> &Self::Output { &self.0.data[index] } } bitflags::bitflags! { #[derive(Clone, Copy, Debug)] pub(crate) struct Flags: u32 { const NOT_NULL_FLAG = 1; const PRI_KEY_FLAG = 2; const UNIQUE_KEY_FLAG = 4; const MULTIPLE_KEY_FLAG = 8; const BLOB_FLAG = 16; const UNSIGNED_FLAG = 32; const ZEROFILL_FLAG = 64; const BINARY_FLAG = 128; const ENUM_FLAG = 256; const AUTO_INCREMENT_FLAG = 512; const TIMESTAMP_FLAG = 1024; const SET_FLAG = 2048; const NO_DEFAULT_VALUE_FLAG = 4096; const ON_UPDATE_NOW_FLAG = 8192; const NUM_FLAG = 32768; const PART_KEY_FLAG = 16384; const GROUP_FLAG = 32768; const UNIQUE_FLAG = 65536; const BINCMP_FLAG = 130_172; const GET_FIXED_FIELDS_FLAG = (1<<18); const FIELD_IN_PART_FUNC_FLAG = (1 << 19); } } impl From for Flags { fn from(flags: u32) -> Self { Flags::from_bits(flags).expect( "We encountered an unknown type flag while parsing \ Mysql's type information. If you see this error message \ please open an issue at diesels github page.", ) } } #[derive(Debug)] pub(super) struct BindData { tpe: ffi::enum_field_types, bytes: Option>, length: libc::c_ulong, capacity: usize, flags: Flags, is_null: ffi::my_bool, is_truncated: Option, } // We need to write a manual clone impl // as we need to clone the underlying buffer // instead of just copying the pointer impl Clone for BindData { fn clone(&self) -> Self { let (ptr, len, capacity) = if let Some(ptr) = self.bytes { let slice = unsafe { // We know that this points to a slice and the pointer is not null at this // location // The length pointer is valid as long as none missuses `bind_for_truncated_data` // as this is the only location that updates the length field before the corresponding data are // written. At the time of writing this comment, the `BindData::bind_for_truncated_data` // function is only called by `Binds::populate_dynamic_buffers` which ensures the corresponding // invariant. std::slice::from_raw_parts( ptr.as_ptr(), self.length.try_into().expect("usize is at least 32bit"), ) }; let mut vec = slice.to_owned(); let ptr = NonNull::new(vec.as_mut_ptr()); let len = vec.len() as libc::c_ulong; let capacity = vec.capacity(); mem::forget(vec); (ptr, len, capacity) } else { (None, 0, 0) }; Self { tpe: self.tpe, bytes: ptr, length: len, capacity, flags: self.flags, is_null: self.is_null, is_truncated: self.is_truncated, } } } impl Drop for BindData { fn drop(&mut self) { if let Some(bytes) = self.bytes { std::mem::drop(unsafe { // We know that this buffer was allocated by a vector, so constructing a vector from it is fine // We know the correct capacity here // We use 0 as length to prevent situations where the length is already updated but // no date are already written as we could touch uninitialized memory otherwise // Using 0 as length is fine as we don't need to call drop for `u8` // (as there is no drop impl for primitive types) Vec::from_raw_parts(bytes.as_ptr(), 0, self.capacity) }); self.bytes = None; } } } impl BindData { fn for_input((tpe, data): (MysqlType, Option>)) -> Self { let (tpe, flags) = tpe.into(); let is_null = ffi::my_bool::from(data.is_none()); let mut bytes = data.unwrap_or_default(); let ptr = NonNull::new(bytes.as_mut_ptr()); let len = bytes.len() as libc::c_ulong; let capacity = bytes.capacity(); mem::forget(bytes); Self { tpe, bytes: ptr, length: len, capacity, flags, is_null, is_truncated: None, } } fn for_output(tpe: Option, metadata: &MysqlFieldMetadata<'_>) -> Self { let (tpe, flags) = if let Some(tpe) = tpe { match (tpe, metadata.field_type()) { // Those are types where we handle the conversion in diesel itself // and do not relay on libmysqlclient (MysqlType::Tiny, ffi::enum_field_types::MYSQL_TYPE_DECIMAL) | (MysqlType::Tiny, ffi::enum_field_types::MYSQL_TYPE_TINY) | (MysqlType::Tiny, ffi::enum_field_types::MYSQL_TYPE_SHORT) | (MysqlType::Tiny, ffi::enum_field_types::MYSQL_TYPE_LONG) | (MysqlType::Tiny, ffi::enum_field_types::MYSQL_TYPE_FLOAT) | (MysqlType::Tiny, ffi::enum_field_types::MYSQL_TYPE_DOUBLE) | (MysqlType::Tiny, ffi::enum_field_types::MYSQL_TYPE_INT24) | (MysqlType::Tiny, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL) | (MysqlType::Tiny, ffi::enum_field_types::MYSQL_TYPE_LONGLONG) | (MysqlType::UnsignedTiny, ffi::enum_field_types::MYSQL_TYPE_DECIMAL) | (MysqlType::UnsignedTiny, ffi::enum_field_types::MYSQL_TYPE_TINY) | (MysqlType::UnsignedTiny, ffi::enum_field_types::MYSQL_TYPE_SHORT) | (MysqlType::UnsignedTiny, ffi::enum_field_types::MYSQL_TYPE_LONG) | (MysqlType::UnsignedTiny, ffi::enum_field_types::MYSQL_TYPE_FLOAT) | (MysqlType::UnsignedTiny, ffi::enum_field_types::MYSQL_TYPE_DOUBLE) | (MysqlType::UnsignedTiny, ffi::enum_field_types::MYSQL_TYPE_INT24) | (MysqlType::UnsignedTiny, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL) | (MysqlType::UnsignedTiny, ffi::enum_field_types::MYSQL_TYPE_LONGLONG) | (MysqlType::Short, ffi::enum_field_types::MYSQL_TYPE_DECIMAL) | (MysqlType::Short, ffi::enum_field_types::MYSQL_TYPE_TINY) | (MysqlType::Short, ffi::enum_field_types::MYSQL_TYPE_SHORT) | (MysqlType::Short, ffi::enum_field_types::MYSQL_TYPE_LONG) | (MysqlType::Short, ffi::enum_field_types::MYSQL_TYPE_FLOAT) | (MysqlType::Short, ffi::enum_field_types::MYSQL_TYPE_DOUBLE) | (MysqlType::Short, ffi::enum_field_types::MYSQL_TYPE_INT24) | (MysqlType::Short, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL) | (MysqlType::Short, ffi::enum_field_types::MYSQL_TYPE_LONGLONG) | (MysqlType::UnsignedShort, ffi::enum_field_types::MYSQL_TYPE_DECIMAL) | (MysqlType::UnsignedShort, ffi::enum_field_types::MYSQL_TYPE_TINY) | (MysqlType::UnsignedShort, ffi::enum_field_types::MYSQL_TYPE_SHORT) | (MysqlType::UnsignedShort, ffi::enum_field_types::MYSQL_TYPE_LONG) | (MysqlType::UnsignedShort, ffi::enum_field_types::MYSQL_TYPE_FLOAT) | (MysqlType::UnsignedShort, ffi::enum_field_types::MYSQL_TYPE_DOUBLE) | (MysqlType::UnsignedShort, ffi::enum_field_types::MYSQL_TYPE_INT24) | (MysqlType::UnsignedShort, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL) | (MysqlType::UnsignedShort, ffi::enum_field_types::MYSQL_TYPE_LONGLONG) | (MysqlType::Long, ffi::enum_field_types::MYSQL_TYPE_DECIMAL) | (MysqlType::Long, ffi::enum_field_types::MYSQL_TYPE_TINY) | (MysqlType::Long, ffi::enum_field_types::MYSQL_TYPE_SHORT) | (MysqlType::Long, ffi::enum_field_types::MYSQL_TYPE_LONG) | (MysqlType::Long, ffi::enum_field_types::MYSQL_TYPE_FLOAT) | (MysqlType::Long, ffi::enum_field_types::MYSQL_TYPE_DOUBLE) | (MysqlType::Long, ffi::enum_field_types::MYSQL_TYPE_INT24) | (MysqlType::Long, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL) | (MysqlType::Long, ffi::enum_field_types::MYSQL_TYPE_LONGLONG) | (MysqlType::UnsignedLong, ffi::enum_field_types::MYSQL_TYPE_DECIMAL) | (MysqlType::UnsignedLong, ffi::enum_field_types::MYSQL_TYPE_TINY) | (MysqlType::UnsignedLong, ffi::enum_field_types::MYSQL_TYPE_SHORT) | (MysqlType::UnsignedLong, ffi::enum_field_types::MYSQL_TYPE_LONG) | (MysqlType::UnsignedLong, ffi::enum_field_types::MYSQL_TYPE_FLOAT) | (MysqlType::UnsignedLong, ffi::enum_field_types::MYSQL_TYPE_DOUBLE) | (MysqlType::UnsignedLong, ffi::enum_field_types::MYSQL_TYPE_INT24) | (MysqlType::UnsignedLong, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL) | (MysqlType::UnsignedLong, ffi::enum_field_types::MYSQL_TYPE_LONGLONG) | (MysqlType::LongLong, ffi::enum_field_types::MYSQL_TYPE_DECIMAL) | (MysqlType::LongLong, ffi::enum_field_types::MYSQL_TYPE_TINY) | (MysqlType::LongLong, ffi::enum_field_types::MYSQL_TYPE_SHORT) | (MysqlType::LongLong, ffi::enum_field_types::MYSQL_TYPE_LONG) | (MysqlType::LongLong, ffi::enum_field_types::MYSQL_TYPE_FLOAT) | (MysqlType::LongLong, ffi::enum_field_types::MYSQL_TYPE_DOUBLE) | (MysqlType::LongLong, ffi::enum_field_types::MYSQL_TYPE_INT24) | (MysqlType::LongLong, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL) | (MysqlType::LongLong, ffi::enum_field_types::MYSQL_TYPE_LONGLONG) | (MysqlType::UnsignedLongLong, ffi::enum_field_types::MYSQL_TYPE_DECIMAL) | (MysqlType::UnsignedLongLong, ffi::enum_field_types::MYSQL_TYPE_TINY) | (MysqlType::UnsignedLongLong, ffi::enum_field_types::MYSQL_TYPE_SHORT) | (MysqlType::UnsignedLongLong, ffi::enum_field_types::MYSQL_TYPE_LONG) | (MysqlType::UnsignedLongLong, ffi::enum_field_types::MYSQL_TYPE_FLOAT) | (MysqlType::UnsignedLongLong, ffi::enum_field_types::MYSQL_TYPE_DOUBLE) | (MysqlType::UnsignedLongLong, ffi::enum_field_types::MYSQL_TYPE_INT24) | (MysqlType::UnsignedLongLong, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL) | (MysqlType::UnsignedLongLong, ffi::enum_field_types::MYSQL_TYPE_LONGLONG) | (MysqlType::Float, ffi::enum_field_types::MYSQL_TYPE_DECIMAL) | (MysqlType::Float, ffi::enum_field_types::MYSQL_TYPE_TINY) | (MysqlType::Float, ffi::enum_field_types::MYSQL_TYPE_SHORT) | (MysqlType::Float, ffi::enum_field_types::MYSQL_TYPE_LONG) | (MysqlType::Float, ffi::enum_field_types::MYSQL_TYPE_FLOAT) | (MysqlType::Float, ffi::enum_field_types::MYSQL_TYPE_DOUBLE) | (MysqlType::Float, ffi::enum_field_types::MYSQL_TYPE_INT24) | (MysqlType::Float, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL) | (MysqlType::Float, ffi::enum_field_types::MYSQL_TYPE_LONGLONG) | (MysqlType::Numeric, ffi::enum_field_types::MYSQL_TYPE_DECIMAL) | (MysqlType::Numeric, ffi::enum_field_types::MYSQL_TYPE_TINY) | (MysqlType::Numeric, ffi::enum_field_types::MYSQL_TYPE_SHORT) | (MysqlType::Numeric, ffi::enum_field_types::MYSQL_TYPE_LONG) | (MysqlType::Numeric, ffi::enum_field_types::MYSQL_TYPE_FLOAT) | (MysqlType::Numeric, ffi::enum_field_types::MYSQL_TYPE_DOUBLE) | (MysqlType::Numeric, ffi::enum_field_types::MYSQL_TYPE_INT24) | (MysqlType::Numeric, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL) | (MysqlType::Numeric, ffi::enum_field_types::MYSQL_TYPE_LONGLONG) | (MysqlType::String, ffi::enum_field_types::MYSQL_TYPE_JSON) | (MysqlType::String, ffi::enum_field_types::MYSQL_TYPE_ENUM) | (MysqlType::String, ffi::enum_field_types::MYSQL_TYPE_SET) | (MysqlType::String, ffi::enum_field_types::MYSQL_TYPE_TINY_BLOB) | (MysqlType::String, ffi::enum_field_types::MYSQL_TYPE_MEDIUM_BLOB) | (MysqlType::String, ffi::enum_field_types::MYSQL_TYPE_LONG_BLOB) | (MysqlType::String, ffi::enum_field_types::MYSQL_TYPE_BLOB) | (MysqlType::String, ffi::enum_field_types::MYSQL_TYPE_VAR_STRING) | (MysqlType::String, ffi::enum_field_types::MYSQL_TYPE_STRING) | (MysqlType::Blob, ffi::enum_field_types::MYSQL_TYPE_TINY_BLOB) | (MysqlType::Blob, ffi::enum_field_types::MYSQL_TYPE_MEDIUM_BLOB) | (MysqlType::Blob, ffi::enum_field_types::MYSQL_TYPE_LONG_BLOB) | (MysqlType::Blob, ffi::enum_field_types::MYSQL_TYPE_BLOB) | (MysqlType::Set, ffi::enum_field_types::MYSQL_TYPE_ENUM) | (MysqlType::Set, ffi::enum_field_types::MYSQL_TYPE_SET) | (MysqlType::Set, ffi::enum_field_types::MYSQL_TYPE_TINY_BLOB) | (MysqlType::Set, ffi::enum_field_types::MYSQL_TYPE_MEDIUM_BLOB) | (MysqlType::Set, ffi::enum_field_types::MYSQL_TYPE_LONG_BLOB) | (MysqlType::Set, ffi::enum_field_types::MYSQL_TYPE_BLOB) | (MysqlType::Set, ffi::enum_field_types::MYSQL_TYPE_VAR_STRING) | (MysqlType::Set, ffi::enum_field_types::MYSQL_TYPE_STRING) | (MysqlType::Enum, ffi::enum_field_types::MYSQL_TYPE_ENUM) | (MysqlType::Enum, ffi::enum_field_types::MYSQL_TYPE_SET) | (MysqlType::Enum, ffi::enum_field_types::MYSQL_TYPE_TINY_BLOB) | (MysqlType::Enum, ffi::enum_field_types::MYSQL_TYPE_MEDIUM_BLOB) | (MysqlType::Enum, ffi::enum_field_types::MYSQL_TYPE_LONG_BLOB) | (MysqlType::Enum, ffi::enum_field_types::MYSQL_TYPE_BLOB) | (MysqlType::Enum, ffi::enum_field_types::MYSQL_TYPE_VAR_STRING) | (MysqlType::Enum, ffi::enum_field_types::MYSQL_TYPE_STRING) => { (metadata.field_type(), metadata.flags()) } (tpe, _) => tpe.into(), } } else { (metadata.field_type(), metadata.flags()) }; Self::from_tpe_and_flags((tpe, flags)) } fn from_tpe_and_flags((tpe, flags): (ffi::enum_field_types, Flags)) -> Self { // newer mysqlclient versions do not accept a zero sized buffer let len = known_buffer_size_for_ffi_type(tpe).unwrap_or(1); let mut bytes = vec![0; len]; let length = bytes.len() as libc::c_ulong; let capacity = bytes.capacity(); let ptr = NonNull::new(bytes.as_mut_ptr()); mem::forget(bytes); Self { tpe, bytes: ptr, length, capacity, flags, is_null: super::raw::ffi_false(), is_truncated: Some(super::raw::ffi_false()), } } fn is_truncated(&self) -> bool { self.is_truncated.unwrap_or(super::raw::ffi_false()) != super::raw::ffi_false() } fn is_fixed_size_buffer(&self) -> bool { known_buffer_size_for_ffi_type(self.tpe).is_some() } pub(super) fn value(&'_ self) -> Option> { if self.is_null() { None } else { let data = self.bytes?; let tpe = (self.tpe, self.flags).into(); let slice = unsafe { // We know that this points to a slice and the pointer is not null at this // location // The length pointer is valid as long as none missuses `bind_for_truncated_data` // as this is the only location that updates the length field before the corresponding data are // written. At the time of writing this comment, the `BindData::bind_for_truncated_data` // function is only called by `Binds::populate_dynamic_buffers` which ensures the corresponding // invariant. std::slice::from_raw_parts( data.as_ptr(), self.length.try_into().expect("Usize is at least 32 bit"), ) }; Some(MysqlValue::new_internal(slice, tpe)) } } pub(super) fn is_null(&self) -> bool { self.is_null != ffi::my_bool::default() } fn update_buffer_length(&mut self) { use std::cmp::min; let actual_bytes_in_buffer = min( self.capacity, self.length.try_into().expect("Usize is at least 32 bit"), ); self.length = actual_bytes_in_buffer as libc::c_ulong; } // This function is marked as unsafe as it returns an owned value // containing a pointer with a lifetime coupled to self. // Callers need to ensure that the returned value cannot outlive `self` unsafe fn mysql_bind(&mut self) -> ffi::MYSQL_BIND { use std::ptr::addr_of_mut; let mut bind: MaybeUninit = mem::MaybeUninit::zeroed(); let ptr = bind.as_mut_ptr(); addr_of_mut!((*ptr).buffer_type).write(self.tpe); addr_of_mut!((*ptr).buffer).write( self.bytes .map(|p| p.as_ptr()) .unwrap_or(std::ptr::null_mut()) as *mut libc::c_void, ); addr_of_mut!((*ptr).buffer_length).write(self.capacity as libc::c_ulong); addr_of_mut!((*ptr).length).write(&mut self.length); addr_of_mut!((*ptr).is_null).write(&mut self.is_null); addr_of_mut!((*ptr).is_unsigned) .write(self.flags.contains(Flags::UNSIGNED_FLAG) as ffi::my_bool); if let Some(ref mut is_truncated) = self.is_truncated { addr_of_mut!((*ptr).error).write(is_truncated); } // That's what the mysqlclient examples are doing bind.assume_init() } /// Resizes the byte buffer to fit the value of `self.length`, and returns /// a tuple of a bind pointing at the truncated data, and the offset to use /// in order to read the truncated data into it. /// /// This invalidates the bind previously returned by `mysql_bind`. Calling /// this function is unsafe unless the binds are immediately rebound. unsafe fn bind_for_truncated_data(&mut self) -> Option<(ffi::MYSQL_BIND, usize)> { if self.is_truncated() { if let Some(bytes) = self.bytes { let mut bytes = Vec::from_raw_parts(bytes.as_ptr(), self.capacity, self.capacity); self.bytes = None; let offset = self.capacity; let truncated_amount = usize::try_from(self.length).expect("Usize is at least 32 bit") - offset; debug_assert!( truncated_amount > 0, "output buffers were invalidated \ without calling `mysql_stmt_bind_result`" ); // reserve space for any missing byte // we know the exact size here bytes.reserve(truncated_amount); self.capacity = bytes.capacity(); self.bytes = NonNull::new(bytes.as_mut_ptr()); mem::forget(bytes); let mut bind = self.mysql_bind(); if let Some(ptr) = self.bytes { // Using offset is safe here as we have a u8 array (where std::mem::size_of:: == 1) // and we have a buffer that has at least bind.buffer = ptr.as_ptr().add(offset) as *mut libc::c_void; bind.buffer_length = truncated_amount as libc::c_ulong; } else { bind.buffer_length = 0; } Some((bind, offset)) } else { // offset is zero here as we don't have a buffer yet // we know the requested length here so we can just request // the correct size let mut vec = vec![0_u8; self.length.try_into().expect("usize is at least 32 bit")]; self.capacity = vec.capacity(); self.bytes = NonNull::new(vec.as_mut_ptr()); mem::forget(vec); let bind = self.mysql_bind(); // As we did not have a buffer before // we couldn't have loaded any data yet, therefore // request everything Some((bind, 0)) } } else { None } } fn did_numeric_overflow_occur(&self) -> QueryResult<()> { use crate::result::Error::DeserializationError; if self.is_truncated() && self.is_fixed_size_buffer() { Err(DeserializationError( "Numeric overflow/underflow occurred".into(), )) } else { Ok(()) } } } impl From for (ffi::enum_field_types, Flags) { fn from(tpe: MysqlType) -> Self { use self::ffi::enum_field_types; let mut flags = Flags::empty(); let tpe = match tpe { MysqlType::Tiny => enum_field_types::MYSQL_TYPE_TINY, MysqlType::Short => enum_field_types::MYSQL_TYPE_SHORT, MysqlType::Long => enum_field_types::MYSQL_TYPE_LONG, MysqlType::LongLong => enum_field_types::MYSQL_TYPE_LONGLONG, MysqlType::Float => enum_field_types::MYSQL_TYPE_FLOAT, MysqlType::Double => enum_field_types::MYSQL_TYPE_DOUBLE, MysqlType::Time => enum_field_types::MYSQL_TYPE_TIME, MysqlType::Date => enum_field_types::MYSQL_TYPE_DATE, MysqlType::DateTime => enum_field_types::MYSQL_TYPE_DATETIME, MysqlType::Timestamp => enum_field_types::MYSQL_TYPE_TIMESTAMP, MysqlType::String => enum_field_types::MYSQL_TYPE_STRING, MysqlType::Blob => enum_field_types::MYSQL_TYPE_BLOB, MysqlType::Numeric => enum_field_types::MYSQL_TYPE_NEWDECIMAL, MysqlType::Bit => enum_field_types::MYSQL_TYPE_BIT, MysqlType::UnsignedTiny => { flags = Flags::UNSIGNED_FLAG; enum_field_types::MYSQL_TYPE_TINY } MysqlType::UnsignedShort => { flags = Flags::UNSIGNED_FLAG; enum_field_types::MYSQL_TYPE_SHORT } MysqlType::UnsignedLong => { flags = Flags::UNSIGNED_FLAG; enum_field_types::MYSQL_TYPE_LONG } MysqlType::UnsignedLongLong => { flags = Flags::UNSIGNED_FLAG; enum_field_types::MYSQL_TYPE_LONGLONG } MysqlType::Set => { flags = Flags::SET_FLAG; enum_field_types::MYSQL_TYPE_STRING } MysqlType::Enum => { flags = Flags::ENUM_FLAG; enum_field_types::MYSQL_TYPE_STRING } }; (tpe, flags) } } impl From<(ffi::enum_field_types, Flags)> for MysqlType { fn from((tpe, flags): (ffi::enum_field_types, Flags)) -> Self { use self::ffi::enum_field_types; let is_unsigned = flags.contains(Flags::UNSIGNED_FLAG); // https://docs.oracle.com/cd/E17952_01/mysql-8.0-en/c-api-data-structures.html // https://dev.mysql.com/doc/dev/mysql-server/8.0.12/binary__log__types_8h.html // https://dev.mysql.com/doc/internals/en/binary-protocol-value.html // https://mariadb.com/kb/en/packet_bindata/ match tpe { enum_field_types::MYSQL_TYPE_TINY if is_unsigned => MysqlType::UnsignedTiny, enum_field_types::MYSQL_TYPE_YEAR | enum_field_types::MYSQL_TYPE_SHORT if is_unsigned => { MysqlType::UnsignedShort } enum_field_types::MYSQL_TYPE_INT24 | enum_field_types::MYSQL_TYPE_LONG if is_unsigned => { MysqlType::UnsignedLong } enum_field_types::MYSQL_TYPE_LONGLONG if is_unsigned => MysqlType::UnsignedLongLong, enum_field_types::MYSQL_TYPE_TINY => MysqlType::Tiny, enum_field_types::MYSQL_TYPE_SHORT => MysqlType::Short, enum_field_types::MYSQL_TYPE_INT24 | enum_field_types::MYSQL_TYPE_LONG => { MysqlType::Long } enum_field_types::MYSQL_TYPE_LONGLONG => MysqlType::LongLong, enum_field_types::MYSQL_TYPE_FLOAT => MysqlType::Float, enum_field_types::MYSQL_TYPE_DOUBLE => MysqlType::Double, enum_field_types::MYSQL_TYPE_DECIMAL | enum_field_types::MYSQL_TYPE_NEWDECIMAL => { MysqlType::Numeric } enum_field_types::MYSQL_TYPE_BIT => MysqlType::Bit, enum_field_types::MYSQL_TYPE_TIME => MysqlType::Time, enum_field_types::MYSQL_TYPE_DATE => MysqlType::Date, enum_field_types::MYSQL_TYPE_DATETIME => MysqlType::DateTime, enum_field_types::MYSQL_TYPE_TIMESTAMP => MysqlType::Timestamp, // Treat json as string because even mysql 8.0 // throws errors sometimes if we use json for json enum_field_types::MYSQL_TYPE_JSON => MysqlType::String, // The documentation states that // MYSQL_TYPE_STRING is used for enums and sets // but experimentation has shown that // just any string like type works, so // better be safe here enum_field_types::MYSQL_TYPE_BLOB | enum_field_types::MYSQL_TYPE_TINY_BLOB | enum_field_types::MYSQL_TYPE_MEDIUM_BLOB | enum_field_types::MYSQL_TYPE_LONG_BLOB | enum_field_types::MYSQL_TYPE_VAR_STRING | enum_field_types::MYSQL_TYPE_STRING if flags.contains(Flags::ENUM_FLAG) => { MysqlType::Enum } enum_field_types::MYSQL_TYPE_BLOB | enum_field_types::MYSQL_TYPE_TINY_BLOB | enum_field_types::MYSQL_TYPE_MEDIUM_BLOB | enum_field_types::MYSQL_TYPE_LONG_BLOB | enum_field_types::MYSQL_TYPE_VAR_STRING | enum_field_types::MYSQL_TYPE_STRING if flags.contains(Flags::SET_FLAG) => { MysqlType::Set } // "blobs" may contain binary data // also "strings" can contain binary data // but all only if the binary flag is set // (see the check_all_the_types test case) enum_field_types::MYSQL_TYPE_BLOB | enum_field_types::MYSQL_TYPE_TINY_BLOB | enum_field_types::MYSQL_TYPE_MEDIUM_BLOB | enum_field_types::MYSQL_TYPE_LONG_BLOB | enum_field_types::MYSQL_TYPE_VAR_STRING | enum_field_types::MYSQL_TYPE_STRING if flags.contains(Flags::BINARY_FLAG) => { MysqlType::Blob } // If the binary flag is not set consider everything as string enum_field_types::MYSQL_TYPE_BLOB | enum_field_types::MYSQL_TYPE_TINY_BLOB | enum_field_types::MYSQL_TYPE_MEDIUM_BLOB | enum_field_types::MYSQL_TYPE_LONG_BLOB | enum_field_types::MYSQL_TYPE_VAR_STRING | enum_field_types::MYSQL_TYPE_STRING => MysqlType::String, // unsigned seems to be set for year in any case enum_field_types::MYSQL_TYPE_YEAR => unreachable!( "The year type should have set the unsigned flag. If you ever \ see this error message, something has gone very wrong. Please \ open an issue at the diesel github repo in this case" ), // Null value enum_field_types::MYSQL_TYPE_NULL => unreachable!( "We ensure at the call side that we do not hit this type here. \ If you ever see this error, something has gone very wrong. \ Please open an issue at the diesel github repo in this case" ), // Those exist in libmysqlclient // but are just not supported // enum_field_types::MYSQL_TYPE_VARCHAR | enum_field_types::MYSQL_TYPE_ENUM | enum_field_types::MYSQL_TYPE_SET | enum_field_types::MYSQL_TYPE_GEOMETRY => { unimplemented!( "Hit a type that should be unsupported in libmysqlclient. If \ you ever see this error, they probably have added support for \ one of those types. Please open an issue at the diesel github \ repo in this case." ) } enum_field_types::MYSQL_TYPE_NEWDATE | enum_field_types::MYSQL_TYPE_TIME2 | enum_field_types::MYSQL_TYPE_DATETIME2 | enum_field_types::MYSQL_TYPE_TIMESTAMP2 => unreachable!( "The mysql documentation states that these types are \ only used on the server side, so if you see this error \ something has gone wrong. Please open an issue at \ the diesel github repo." ), // depending on the bindings version // there might be no unlisted field type #[allow(unreachable_patterns)] t => unreachable!( "Unsupported type encountered: {t:?}. \ If you ever see this error, something has gone wrong. \ Please open an issue at the diesel github \ repo in this case." ), } } } fn known_buffer_size_for_ffi_type(tpe: ffi::enum_field_types) -> Option { use self::ffi::enum_field_types as t; use std::mem::size_of; match tpe { t::MYSQL_TYPE_TINY => Some(1), t::MYSQL_TYPE_YEAR | t::MYSQL_TYPE_SHORT => Some(2), t::MYSQL_TYPE_INT24 | t::MYSQL_TYPE_LONG | t::MYSQL_TYPE_FLOAT => Some(4), t::MYSQL_TYPE_LONGLONG | t::MYSQL_TYPE_DOUBLE => Some(8), t::MYSQL_TYPE_TIME | t::MYSQL_TYPE_DATE | t::MYSQL_TYPE_DATETIME | t::MYSQL_TYPE_TIMESTAMP => Some(size_of::()), _ => None, } } #[cfg(test)] mod tests { use super::*; use crate::connection::statement_cache::MaybeCached; use crate::deserialize::FromSql; use crate::mysql::connection::stmt::Statement; use crate::prelude::*; use crate::sql_types::*; #[cfg(feature = "numeric")] use std::str::FromStr; fn to_value( bind: &BindData, ) -> Result> where T: FromSql + std::fmt::Debug, { let meta = (bind.tpe, bind.flags).into(); dbg!(meta); let value = bind.value().expect("Is not null"); let value = MysqlValue::new_internal(value.as_bytes(), meta); dbg!(T::from_sql(value)) } #[cfg(feature = "extras")] #[test] fn check_all_the_types() { let conn = &mut crate::test_helpers::connection(); crate::sql_query("DROP TABLE IF EXISTS all_mysql_types CASCADE") .execute(conn) .unwrap(); crate::sql_query( "CREATE TABLE all_mysql_types ( tiny_int TINYINT NOT NULL, small_int SMALLINT NOT NULL, medium_int MEDIUMINT NOT NULL, int_col INTEGER NOT NULL, big_int BIGINT NOT NULL, unsigned_int INTEGER UNSIGNED NOT NULL, zero_fill_int INTEGER ZEROFILL NOT NULL, numeric_col NUMERIC(20,5) NOT NULL, decimal_col DECIMAL(20,5) NOT NULL, float_col FLOAT NOT NULL, double_col DOUBLE NOT NULL, bit_col BIT(8) NOT NULL, date_col DATE NOT NULL, date_time DATETIME NOT NULL, timestamp_col TIMESTAMP NOT NULL, time_col TIME NOT NULL, year_col YEAR NOT NULL, char_col CHAR(30) NOT NULL, varchar_col VARCHAR(30) NOT NULL, binary_col BINARY(30) NOT NULL, varbinary_col VARBINARY(30) NOT NULL, blob_col BLOB NOT NULL, text_col TEXT NOT NULL, enum_col ENUM('red', 'green', 'blue') NOT NULL, set_col SET('one', 'two') NOT NULL, geom GEOMETRY NOT NULL, point_col POINT NOT NULL, linestring_col LINESTRING NOT NULL, polygon_col POLYGON NOT NULL, multipoint_col MULTIPOINT NOT NULL, multilinestring_col MULTILINESTRING NOT NULL, multipolygon_col MULTIPOLYGON NOT NULL, geometry_collection GEOMETRYCOLLECTION NOT NULL, json_col JSON NOT NULL )", ) .execute(conn) .unwrap(); crate::sql_query( "INSERT INTO all_mysql_types VALUES ( 0, -- tiny_int 1, -- small_int 2, -- medium_int 3, -- int_col -5, -- big_int 42, -- unsigned_int 1, -- zero_fill_int -999.999, -- numeric_col, 3.14, -- decimal_col, 1.23, -- float_col 4.5678, -- double_col b'10101010', -- bit_col '1000-01-01', -- date_col '9999-12-31 12:34:45.012345', -- date_time '2020-01-01 10:10:10', -- timestamp_col '23:01:01', -- time_col 2020, -- year_col 'abc', -- char_col 'foo', -- varchar_col 'a ', -- binary_col 'a ', -- varbinary_col 'binary', -- blob_col 'some text whatever', -- text_col 'red', -- enum_col 'one', -- set_col ST_GeomFromText('POINT(1 1)'), -- geom ST_PointFromText('POINT(1 1)'), -- point_col ST_LineStringFromText('LINESTRING(0 0,1 1,2 2)'), -- linestring_col ST_PolygonFromText('POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7, 5 5))'), -- polygon_col ST_MultiPointFromText('MULTIPOINT(0 0,10 10,10 20,20 20)'), -- multipoint_col ST_MultiLineStringFromText('MULTILINESTRING((10 48,10 21,10 0),(16 0,16 23,16 48))'), -- multilinestring_col ST_MultiPolygonFromText('MULTIPOLYGON(((28 26,28 0,84 0,84 42,28 26),(52 18,66 23,73 9,48 6,52 18)),((59 18,67 18,67 13,59 13,59 18)))'), -- multipolygon_col ST_GeomCollFromText('GEOMETRYCOLLECTION(POINT(1 1),LINESTRING(0 0,1 1,2 2,3 3,4 4))'), -- geometry_collection '{\"key1\": \"value1\", \"key2\": \"value2\"}' -- json_col )", ).execute(conn) .unwrap(); let stmt = crate::mysql::connection::prepared_query( &crate::sql_query( "SELECT tiny_int, small_int, medium_int, int_col, big_int, unsigned_int, zero_fill_int, numeric_col, decimal_col, float_col, double_col, bit_col, date_col, date_time, timestamp_col, time_col, year_col, char_col, varchar_col, binary_col, varbinary_col, blob_col, text_col, enum_col, set_col, ST_AsText(geom), ST_AsText(point_col), ST_AsText(linestring_col), ST_AsText(polygon_col), ST_AsText(multipoint_col), ST_AsText(multilinestring_col), ST_AsText(multipolygon_col), ST_AsText(geometry_collection), json_col FROM all_mysql_types", ), &mut conn.statement_cache, &mut conn.raw_connection, &mut conn.instrumentation, ).unwrap(); let metadata = stmt.metadata().unwrap(); let mut output_binds = OutputBinds::from_output_types(&vec![None; metadata.fields().len()], &metadata); let stmt = stmt.execute_statement(&mut output_binds).unwrap(); stmt.populate_row_buffers(&mut output_binds).unwrap(); let results: Vec<(BindData, &_)> = output_binds .0 .data .into_iter() .zip(metadata.fields()) .collect::>(); let tiny_int_col = &results[0].0; assert_eq!(tiny_int_col.tpe, ffi::enum_field_types::MYSQL_TYPE_TINY); assert!(tiny_int_col.flags.contains(Flags::NUM_FLAG)); assert!(!tiny_int_col.flags.contains(Flags::UNSIGNED_FLAG)); assert!(matches!(to_value::(tiny_int_col), Ok(0))); let small_int_col = &results[1].0; assert_eq!(small_int_col.tpe, ffi::enum_field_types::MYSQL_TYPE_SHORT); assert!(small_int_col.flags.contains(Flags::NUM_FLAG)); assert!(!small_int_col.flags.contains(Flags::UNSIGNED_FLAG)); assert!(matches!(to_value::(small_int_col), Ok(1))); let medium_int_col = &results[2].0; assert_eq!(medium_int_col.tpe, ffi::enum_field_types::MYSQL_TYPE_INT24); assert!(medium_int_col.flags.contains(Flags::NUM_FLAG)); assert!(!medium_int_col.flags.contains(Flags::UNSIGNED_FLAG)); assert!(matches!(to_value::(medium_int_col), Ok(2))); let int_col = &results[3].0; assert_eq!(int_col.tpe, ffi::enum_field_types::MYSQL_TYPE_LONG); assert!(int_col.flags.contains(Flags::NUM_FLAG)); assert!(!int_col.flags.contains(Flags::UNSIGNED_FLAG)); assert!(matches!(to_value::(int_col), Ok(3))); let big_int_col = &results[4].0; assert_eq!(big_int_col.tpe, ffi::enum_field_types::MYSQL_TYPE_LONGLONG); assert!(big_int_col.flags.contains(Flags::NUM_FLAG)); assert!(!big_int_col.flags.contains(Flags::UNSIGNED_FLAG)); assert!(matches!(to_value::(big_int_col), Ok(-5))); let unsigned_int_col = &results[5].0; assert_eq!(unsigned_int_col.tpe, ffi::enum_field_types::MYSQL_TYPE_LONG); assert!(unsigned_int_col.flags.contains(Flags::NUM_FLAG)); assert!(unsigned_int_col.flags.contains(Flags::UNSIGNED_FLAG)); assert!(matches!( to_value::, u32>(unsigned_int_col), Ok(42) )); let zero_fill_int_col = &results[6].0; assert_eq!( zero_fill_int_col.tpe, ffi::enum_field_types::MYSQL_TYPE_LONG ); assert!(zero_fill_int_col.flags.contains(Flags::NUM_FLAG)); assert!(zero_fill_int_col.flags.contains(Flags::ZEROFILL_FLAG)); assert!(matches!(to_value::(zero_fill_int_col), Ok(1))); let numeric_col = &results[7].0; assert_eq!( numeric_col.tpe, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL ); assert!(numeric_col.flags.contains(Flags::NUM_FLAG)); assert!(!numeric_col.flags.contains(Flags::UNSIGNED_FLAG)); assert_eq!( to_value::(numeric_col).unwrap(), bigdecimal::BigDecimal::from_str("-999.99900").unwrap() ); let decimal_col = &results[8].0; assert_eq!( decimal_col.tpe, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL ); assert!(decimal_col.flags.contains(Flags::NUM_FLAG)); assert!(!decimal_col.flags.contains(Flags::UNSIGNED_FLAG)); assert_eq!( to_value::(decimal_col).unwrap(), bigdecimal::BigDecimal::from_str("3.14000").unwrap() ); let float_col = &results[9].0; assert_eq!(float_col.tpe, ffi::enum_field_types::MYSQL_TYPE_FLOAT); assert!(float_col.flags.contains(Flags::NUM_FLAG)); assert!(!float_col.flags.contains(Flags::UNSIGNED_FLAG)); assert_eq!(to_value::(float_col).unwrap(), 1.23); let double_col = &results[10].0; assert_eq!(double_col.tpe, ffi::enum_field_types::MYSQL_TYPE_DOUBLE); assert!(double_col.flags.contains(Flags::NUM_FLAG)); assert!(!double_col.flags.contains(Flags::UNSIGNED_FLAG)); assert_eq!(to_value::(double_col).unwrap(), 4.5678); let bit_col = &results[11].0; assert_eq!(bit_col.tpe, ffi::enum_field_types::MYSQL_TYPE_BIT); assert!(!bit_col.flags.contains(Flags::NUM_FLAG)); assert!(bit_col.flags.contains(Flags::UNSIGNED_FLAG)); assert!(!bit_col.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::>(bit_col).unwrap(), vec![170]); let date_col = &results[12].0; assert_eq!(date_col.tpe, ffi::enum_field_types::MYSQL_TYPE_DATE); assert!(!date_col.flags.contains(Flags::NUM_FLAG)); assert_eq!( to_value::(date_col).unwrap(), chrono::NaiveDate::from_ymd_opt(1000, 1, 1).unwrap(), ); let date_time_col = &results[13].0; assert_eq!( date_time_col.tpe, ffi::enum_field_types::MYSQL_TYPE_DATETIME ); assert!(!date_time_col.flags.contains(Flags::NUM_FLAG)); assert_eq!( to_value::(date_time_col).unwrap(), chrono::NaiveDateTime::parse_from_str("9999-12-31 12:34:45", "%Y-%m-%d %H:%M:%S") .unwrap() ); let timestamp_col = &results[14].0; assert_eq!( timestamp_col.tpe, ffi::enum_field_types::MYSQL_TYPE_TIMESTAMP ); assert!(!timestamp_col.flags.contains(Flags::NUM_FLAG)); assert_eq!( to_value::(timestamp_col).unwrap(), chrono::NaiveDateTime::parse_from_str("2020-01-01 10:10:10", "%Y-%m-%d %H:%M:%S") .unwrap() ); let time_col = &results[15].0; assert_eq!(time_col.tpe, ffi::enum_field_types::MYSQL_TYPE_TIME); assert!(!time_col.flags.contains(Flags::NUM_FLAG)); assert_eq!( to_value::(time_col).unwrap(), chrono::NaiveTime::from_hms_opt(23, 01, 01).unwrap() ); let year_col = &results[16].0; assert_eq!(year_col.tpe, ffi::enum_field_types::MYSQL_TYPE_YEAR); assert!(year_col.flags.contains(Flags::NUM_FLAG)); assert!(year_col.flags.contains(Flags::UNSIGNED_FLAG)); assert!(matches!(to_value::(year_col), Ok(2020))); let char_col = &results[17].0; assert_eq!(char_col.tpe, ffi::enum_field_types::MYSQL_TYPE_STRING); assert!(!char_col.flags.contains(Flags::NUM_FLAG)); assert!(!char_col.flags.contains(Flags::BLOB_FLAG)); assert!(!char_col.flags.contains(Flags::SET_FLAG)); assert!(!char_col.flags.contains(Flags::ENUM_FLAG)); assert!(!char_col.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::(char_col).unwrap(), "abc"); let varchar_col = &results[18].0; assert_eq!( varchar_col.tpe, ffi::enum_field_types::MYSQL_TYPE_VAR_STRING ); assert!(!varchar_col.flags.contains(Flags::NUM_FLAG)); assert!(!varchar_col.flags.contains(Flags::BLOB_FLAG)); assert!(!varchar_col.flags.contains(Flags::SET_FLAG)); assert!(!varchar_col.flags.contains(Flags::ENUM_FLAG)); assert!(!varchar_col.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::(varchar_col).unwrap(), "foo"); let binary_col = &results[19].0; assert_eq!(binary_col.tpe, ffi::enum_field_types::MYSQL_TYPE_STRING); assert!(!binary_col.flags.contains(Flags::NUM_FLAG)); assert!(!binary_col.flags.contains(Flags::BLOB_FLAG)); assert!(!binary_col.flags.contains(Flags::SET_FLAG)); assert!(!binary_col.flags.contains(Flags::ENUM_FLAG)); assert!(binary_col.flags.contains(Flags::BINARY_FLAG)); assert_eq!( to_value::>(binary_col).unwrap(), b"a \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ); let varbinary_col = &results[20].0; assert_eq!( varbinary_col.tpe, ffi::enum_field_types::MYSQL_TYPE_VAR_STRING ); assert!(!varbinary_col.flags.contains(Flags::NUM_FLAG)); assert!(!varbinary_col.flags.contains(Flags::BLOB_FLAG)); assert!(!varbinary_col.flags.contains(Flags::SET_FLAG)); assert!(!varbinary_col.flags.contains(Flags::ENUM_FLAG)); assert!(varbinary_col.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::>(varbinary_col).unwrap(), b"a "); let blob_col = &results[21].0; assert_eq!(blob_col.tpe, ffi::enum_field_types::MYSQL_TYPE_BLOB); assert!(!blob_col.flags.contains(Flags::NUM_FLAG)); assert!(blob_col.flags.contains(Flags::BLOB_FLAG)); assert!(!blob_col.flags.contains(Flags::SET_FLAG)); assert!(!blob_col.flags.contains(Flags::ENUM_FLAG)); assert!(blob_col.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::>(blob_col).unwrap(), b"binary"); let text_col = &results[22].0; assert_eq!(text_col.tpe, ffi::enum_field_types::MYSQL_TYPE_BLOB); assert!(!text_col.flags.contains(Flags::NUM_FLAG)); assert!(text_col.flags.contains(Flags::BLOB_FLAG)); assert!(!text_col.flags.contains(Flags::SET_FLAG)); assert!(!text_col.flags.contains(Flags::ENUM_FLAG)); assert!(!text_col.flags.contains(Flags::BINARY_FLAG)); assert_eq!( to_value::(text_col).unwrap(), "some text whatever" ); let enum_col = &results[23].0; assert_eq!(enum_col.tpe, ffi::enum_field_types::MYSQL_TYPE_STRING); assert!(!enum_col.flags.contains(Flags::NUM_FLAG)); assert!(!enum_col.flags.contains(Flags::BLOB_FLAG)); assert!(!enum_col.flags.contains(Flags::SET_FLAG)); assert!(enum_col.flags.contains(Flags::ENUM_FLAG)); assert!(!enum_col.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::(enum_col).unwrap(), "red"); let set_col = &results[24].0; assert_eq!(set_col.tpe, ffi::enum_field_types::MYSQL_TYPE_STRING); assert!(!set_col.flags.contains(Flags::NUM_FLAG)); assert!(!set_col.flags.contains(Flags::BLOB_FLAG)); assert!(set_col.flags.contains(Flags::SET_FLAG)); assert!(!set_col.flags.contains(Flags::ENUM_FLAG)); assert!(!set_col.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::(set_col).unwrap(), "one"); let geom = &results[25].0; assert_eq!(geom.tpe, ffi::enum_field_types::MYSQL_TYPE_LONG_BLOB); assert!(!geom.flags.contains(Flags::NUM_FLAG)); assert!(!geom.flags.contains(Flags::BLOB_FLAG)); assert!(!geom.flags.contains(Flags::SET_FLAG)); assert!(!geom.flags.contains(Flags::ENUM_FLAG)); assert!(!geom.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::(geom).unwrap(), "POINT(1 1)"); let point_col = &results[26].0; assert_eq!(point_col.tpe, ffi::enum_field_types::MYSQL_TYPE_LONG_BLOB); assert!(!point_col.flags.contains(Flags::NUM_FLAG)); assert!(!point_col.flags.contains(Flags::BLOB_FLAG)); assert!(!point_col.flags.contains(Flags::SET_FLAG)); assert!(!point_col.flags.contains(Flags::ENUM_FLAG)); assert!(!point_col.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::(point_col).unwrap(), "POINT(1 1)"); let linestring_col = &results[27].0; assert_eq!( linestring_col.tpe, ffi::enum_field_types::MYSQL_TYPE_LONG_BLOB ); assert!(!linestring_col.flags.contains(Flags::NUM_FLAG)); assert!(!linestring_col.flags.contains(Flags::BLOB_FLAG)); assert!(!linestring_col.flags.contains(Flags::SET_FLAG)); assert!(!linestring_col.flags.contains(Flags::ENUM_FLAG)); assert!(!linestring_col.flags.contains(Flags::BINARY_FLAG)); assert_eq!( to_value::(linestring_col).unwrap(), "LINESTRING(0 0,1 1,2 2)" ); let polygon_col = &results[28].0; assert_eq!(polygon_col.tpe, ffi::enum_field_types::MYSQL_TYPE_LONG_BLOB); assert!(!polygon_col.flags.contains(Flags::NUM_FLAG)); assert!(!polygon_col.flags.contains(Flags::BLOB_FLAG)); assert!(!polygon_col.flags.contains(Flags::SET_FLAG)); assert!(!polygon_col.flags.contains(Flags::ENUM_FLAG)); assert!(!polygon_col.flags.contains(Flags::BINARY_FLAG)); assert_eq!( to_value::(polygon_col).unwrap(), "POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7,5 5))" ); let multipoint_col = &results[29].0; assert_eq!( multipoint_col.tpe, ffi::enum_field_types::MYSQL_TYPE_LONG_BLOB ); assert!(!multipoint_col.flags.contains(Flags::NUM_FLAG)); assert!(!multipoint_col.flags.contains(Flags::BLOB_FLAG)); assert!(!multipoint_col.flags.contains(Flags::SET_FLAG)); assert!(!multipoint_col.flags.contains(Flags::ENUM_FLAG)); assert!(!multipoint_col.flags.contains(Flags::BINARY_FLAG)); // older mysql and mariadb versions get back another encoding here // we test for both as there seems to be no clear pattern when one or // the other is returned let multipoint_res = to_value::(multipoint_col).unwrap(); assert!( multipoint_res == "MULTIPOINT((0 0),(10 10),(10 20),(20 20))" || multipoint_res == "MULTIPOINT(0 0,10 10,10 20,20 20)" ); let multilinestring_col = &results[30].0; assert_eq!( multilinestring_col.tpe, ffi::enum_field_types::MYSQL_TYPE_LONG_BLOB ); assert!(!multilinestring_col.flags.contains(Flags::NUM_FLAG)); assert!(!multilinestring_col.flags.contains(Flags::BLOB_FLAG)); assert!(!multilinestring_col.flags.contains(Flags::SET_FLAG)); assert!(!multilinestring_col.flags.contains(Flags::ENUM_FLAG)); assert!(!multilinestring_col.flags.contains(Flags::BINARY_FLAG)); assert_eq!( to_value::(multilinestring_col).unwrap(), "MULTILINESTRING((10 48,10 21,10 0),(16 0,16 23,16 48))" ); let polygon_col = &results[31].0; assert_eq!(polygon_col.tpe, ffi::enum_field_types::MYSQL_TYPE_LONG_BLOB); assert!(!polygon_col.flags.contains(Flags::NUM_FLAG)); assert!(!polygon_col.flags.contains(Flags::BLOB_FLAG)); assert!(!polygon_col.flags.contains(Flags::SET_FLAG)); assert!(!polygon_col.flags.contains(Flags::ENUM_FLAG)); assert!(!polygon_col.flags.contains(Flags::BINARY_FLAG)); assert_eq!( to_value::(polygon_col).unwrap(), "MULTIPOLYGON(((28 26,28 0,84 0,84 42,28 26),(52 18,66 23,73 9,48 6,52 18)),((59 18,67 18,67 13,59 13,59 18)))" ); let geometry_collection = &results[32].0; assert_eq!( geometry_collection.tpe, ffi::enum_field_types::MYSQL_TYPE_LONG_BLOB ); assert!(!geometry_collection.flags.contains(Flags::NUM_FLAG)); assert!(!geometry_collection.flags.contains(Flags::BLOB_FLAG)); assert!(!geometry_collection.flags.contains(Flags::SET_FLAG)); assert!(!geometry_collection.flags.contains(Flags::ENUM_FLAG)); assert!(!geometry_collection.flags.contains(Flags::BINARY_FLAG)); assert_eq!( to_value::(geometry_collection).unwrap(), "GEOMETRYCOLLECTION(POINT(1 1),LINESTRING(0 0,1 1,2 2,3 3,4 4))" ); let json_col = &results[33].0; // mariadb >= 10.2 and mysql >=8.0 are supporting a json type // from those mariadb >= 10.3 and mysql >= 8.0 are reporting // json here, so we assert that we get back json // mariadb 10.5 returns again blob assert!( json_col.tpe == ffi::enum_field_types::MYSQL_TYPE_JSON || json_col.tpe == ffi::enum_field_types::MYSQL_TYPE_BLOB ); assert!(!json_col.flags.contains(Flags::NUM_FLAG)); assert!(json_col.flags.contains(Flags::BLOB_FLAG)); assert!(!json_col.flags.contains(Flags::SET_FLAG)); assert!(!json_col.flags.contains(Flags::ENUM_FLAG)); assert!(json_col.flags.contains(Flags::BINARY_FLAG)); assert_eq!( to_value::(json_col).unwrap(), "{\"key1\": \"value1\", \"key2\": \"value2\"}" ); } fn query_single_table( query: &'static str, conn: &MysqlConnection, bind_tpe: impl Into<(ffi::enum_field_types, Flags)>, ) -> BindData { let stmt: Statement = conn.raw_connection.prepare(query).unwrap(); let stmt = MaybeCached::CannotCache(stmt); let bind = BindData::from_tpe_and_flags(bind_tpe.into()); let mut binds = OutputBinds(Binds { data: vec![bind] }); let stmt = stmt.execute_statement(&mut binds).unwrap(); stmt.populate_row_buffers(&mut binds).unwrap(); binds.0.data.remove(0) } fn input_bind( query: &'static str, conn: &MysqlConnection, id: i32, (mut field, tpe): (Vec, impl Into<(ffi::enum_field_types, Flags)>), ) { let mut stmt = conn.raw_connection.prepare(query).unwrap(); let length = field.len() as _; let (tpe, flags) = tpe.into(); let capacity = field.capacity(); let ptr = NonNull::new(field.as_mut_ptr()); mem::forget(field); let field_bind = BindData { tpe, bytes: ptr, capacity, length, flags, is_null: ffi::FALSE, is_truncated: None, }; let mut bytes = id.to_be_bytes().to_vec(); let length = bytes.len() as _; let capacity = bytes.capacity(); let ptr = NonNull::new(bytes.as_mut_ptr()); mem::forget(bytes); let id_bind = BindData { tpe: ffi::enum_field_types::MYSQL_TYPE_LONG, bytes: ptr, capacity, length, flags: Flags::empty(), is_null: ffi::FALSE, is_truncated: None, }; let binds = PreparedStatementBinds(Binds { data: vec![id_bind, field_bind], }); stmt.input_bind(binds).unwrap(); stmt.did_an_error_occur().unwrap(); let stmt = MaybeCached::CannotCache(stmt); unsafe { stmt.execute().unwrap(); } } #[test] fn check_json_bind() { table! { json_test { id -> Integer, json_field -> Text, } } let conn = &mut crate::test_helpers::connection(); crate::sql_query("DROP TABLE IF EXISTS json_test CASCADE") .execute(conn) .unwrap(); crate::sql_query( "CREATE TABLE json_test(id INTEGER PRIMARY KEY, json_field JSON NOT NULL)", ) .execute(conn) .unwrap(); crate::sql_query("INSERT INTO json_test(id, json_field) VALUES (1, '{\"key1\": \"value1\", \"key2\": \"value2\"}')").execute(conn).unwrap(); let json_col_as_json = query_single_table( "SELECT json_field FROM json_test", conn, (ffi::enum_field_types::MYSQL_TYPE_JSON, Flags::empty()), ); assert_eq!(json_col_as_json.tpe, ffi::enum_field_types::MYSQL_TYPE_JSON); assert!(!json_col_as_json.flags.contains(Flags::NUM_FLAG)); assert!(!json_col_as_json.flags.contains(Flags::BLOB_FLAG)); assert!(!json_col_as_json.flags.contains(Flags::SET_FLAG)); assert!(!json_col_as_json.flags.contains(Flags::ENUM_FLAG)); assert!(!json_col_as_json.flags.contains(Flags::BINARY_FLAG)); assert_eq!( to_value::(&json_col_as_json).unwrap(), "{\"key1\": \"value1\", \"key2\": \"value2\"}" ); let json_col_as_text = query_single_table( "SELECT json_field FROM json_test", conn, (ffi::enum_field_types::MYSQL_TYPE_BLOB, Flags::empty()), ); assert_eq!(json_col_as_text.tpe, ffi::enum_field_types::MYSQL_TYPE_BLOB); assert!(!json_col_as_text.flags.contains(Flags::NUM_FLAG)); assert!(!json_col_as_text.flags.contains(Flags::BLOB_FLAG)); assert!(!json_col_as_text.flags.contains(Flags::SET_FLAG)); assert!(!json_col_as_text.flags.contains(Flags::ENUM_FLAG)); assert!(!json_col_as_text.flags.contains(Flags::BINARY_FLAG)); assert_eq!( to_value::(&json_col_as_text).unwrap(), "{\"key1\": \"value1\", \"key2\": \"value2\"}" ); assert_eq!( json_col_as_json.value().unwrap().as_bytes(), json_col_as_text.value().unwrap().as_bytes() ); crate::sql_query("DELETE FROM json_test") .execute(conn) .unwrap(); input_bind( "INSERT INTO json_test(id, json_field) VALUES (?, ?)", conn, 41, ( b"{\"abc\": 42}".to_vec(), MysqlType::String, // (ffi::enum_field_types::MYSQL_TYPE_JSON, Flags::empty()), ), ); let json_col_as_json = query_single_table( "SELECT json_field FROM json_test", conn, (ffi::enum_field_types::MYSQL_TYPE_JSON, Flags::empty()), ); assert_eq!(json_col_as_json.tpe, ffi::enum_field_types::MYSQL_TYPE_JSON); assert!(!json_col_as_json.flags.contains(Flags::NUM_FLAG)); assert!(!json_col_as_json.flags.contains(Flags::BLOB_FLAG)); assert!(!json_col_as_json.flags.contains(Flags::SET_FLAG)); assert!(!json_col_as_json.flags.contains(Flags::ENUM_FLAG)); assert!(!json_col_as_json.flags.contains(Flags::BINARY_FLAG)); assert_eq!( to_value::(&json_col_as_json).unwrap(), "{\"abc\": 42}" ); let json_col_as_text = query_single_table( "SELECT json_field FROM json_test", conn, (ffi::enum_field_types::MYSQL_TYPE_BLOB, Flags::empty()), ); assert_eq!(json_col_as_text.tpe, ffi::enum_field_types::MYSQL_TYPE_BLOB); assert!(!json_col_as_text.flags.contains(Flags::NUM_FLAG)); assert!(!json_col_as_text.flags.contains(Flags::BLOB_FLAG)); assert!(!json_col_as_text.flags.contains(Flags::SET_FLAG)); assert!(!json_col_as_text.flags.contains(Flags::ENUM_FLAG)); assert!(!json_col_as_text.flags.contains(Flags::BINARY_FLAG)); assert_eq!( to_value::(&json_col_as_text).unwrap(), "{\"abc\": 42}" ); assert_eq!( json_col_as_json.value().unwrap().as_bytes(), json_col_as_text.value().unwrap().as_bytes() ); crate::sql_query("DELETE FROM json_test") .execute(conn) .unwrap(); input_bind( "INSERT INTO json_test(id, json_field) VALUES (?, ?)", conn, 41, (b"{\"abca\": 42}".to_vec(), MysqlType::String), ); let json_col_as_json = query_single_table( "SELECT json_field FROM json_test", conn, (ffi::enum_field_types::MYSQL_TYPE_JSON, Flags::empty()), ); assert_eq!(json_col_as_json.tpe, ffi::enum_field_types::MYSQL_TYPE_JSON); assert!(!json_col_as_json.flags.contains(Flags::NUM_FLAG)); assert!(!json_col_as_json.flags.contains(Flags::BLOB_FLAG)); assert!(!json_col_as_json.flags.contains(Flags::SET_FLAG)); assert!(!json_col_as_json.flags.contains(Flags::ENUM_FLAG)); assert!(!json_col_as_json.flags.contains(Flags::BINARY_FLAG)); assert_eq!( to_value::(&json_col_as_json).unwrap(), "{\"abca\": 42}" ); let json_col_as_text = query_single_table( "SELECT json_field FROM json_test", conn, (ffi::enum_field_types::MYSQL_TYPE_BLOB, Flags::empty()), ); assert_eq!(json_col_as_text.tpe, ffi::enum_field_types::MYSQL_TYPE_BLOB); assert!(!json_col_as_text.flags.contains(Flags::NUM_FLAG)); assert!(!json_col_as_text.flags.contains(Flags::BLOB_FLAG)); assert!(!json_col_as_text.flags.contains(Flags::SET_FLAG)); assert!(!json_col_as_text.flags.contains(Flags::ENUM_FLAG)); assert!(!json_col_as_text.flags.contains(Flags::BINARY_FLAG)); assert_eq!( to_value::(&json_col_as_text).unwrap(), "{\"abca\": 42}" ); assert_eq!( json_col_as_json.value().unwrap().as_bytes(), json_col_as_text.value().unwrap().as_bytes() ); } #[test] fn check_enum_bind() { let conn = &mut crate::test_helpers::connection(); crate::sql_query("DROP TABLE IF EXISTS enum_test CASCADE") .execute(conn) .unwrap(); crate::sql_query("CREATE TABLE enum_test(id INTEGER PRIMARY KEY, enum_field ENUM('red', 'green', 'blue') NOT NULL)").execute(conn) .unwrap(); crate::sql_query("INSERT INTO enum_test(id, enum_field) VALUES (1, 'green')") .execute(conn) .unwrap(); let enum_col_as_enum: BindData = query_single_table("SELECT enum_field FROM enum_test", conn, MysqlType::Enum); assert_eq!( enum_col_as_enum.tpe, ffi::enum_field_types::MYSQL_TYPE_STRING ); assert!(!enum_col_as_enum.flags.contains(Flags::NUM_FLAG)); assert!(!enum_col_as_enum.flags.contains(Flags::BLOB_FLAG)); assert!(!enum_col_as_enum.flags.contains(Flags::SET_FLAG)); assert!(enum_col_as_enum.flags.contains(Flags::ENUM_FLAG)); assert!(!enum_col_as_enum.flags.contains(Flags::BINARY_FLAG)); assert_eq!( to_value::(&enum_col_as_enum).unwrap(), "green" ); for tpe in &[ ffi::enum_field_types::MYSQL_TYPE_BLOB, ffi::enum_field_types::MYSQL_TYPE_VAR_STRING, ffi::enum_field_types::MYSQL_TYPE_TINY_BLOB, ffi::enum_field_types::MYSQL_TYPE_MEDIUM_BLOB, ffi::enum_field_types::MYSQL_TYPE_LONG_BLOB, ] { let enum_col_as_text = query_single_table( "SELECT enum_field FROM enum_test", conn, (*tpe, Flags::ENUM_FLAG), ); assert_eq!(enum_col_as_text.tpe, *tpe); assert!(!enum_col_as_text.flags.contains(Flags::NUM_FLAG)); assert!(!enum_col_as_text.flags.contains(Flags::BLOB_FLAG)); assert!(!enum_col_as_text.flags.contains(Flags::SET_FLAG)); assert!(enum_col_as_text.flags.contains(Flags::ENUM_FLAG)); assert!(!enum_col_as_text.flags.contains(Flags::BINARY_FLAG)); assert_eq!( to_value::(&enum_col_as_text).unwrap(), "green" ); assert_eq!( enum_col_as_enum.value().unwrap().as_bytes(), enum_col_as_text.value().unwrap().as_bytes() ); } let enum_col_as_text = query_single_table( "SELECT enum_field FROM enum_test", conn, (ffi::enum_field_types::MYSQL_TYPE_BLOB, Flags::empty()), ); assert_eq!(enum_col_as_text.tpe, ffi::enum_field_types::MYSQL_TYPE_BLOB); assert!(!enum_col_as_text.flags.contains(Flags::NUM_FLAG)); assert!(!enum_col_as_text.flags.contains(Flags::BLOB_FLAG)); assert!(!enum_col_as_text.flags.contains(Flags::SET_FLAG)); assert!(!enum_col_as_text.flags.contains(Flags::ENUM_FLAG)); assert!(!enum_col_as_text.flags.contains(Flags::BINARY_FLAG)); assert_eq!( to_value::(&enum_col_as_text).unwrap(), "green" ); assert_eq!( enum_col_as_enum.value().unwrap().as_bytes(), enum_col_as_text.value().unwrap().as_bytes() ); crate::sql_query("DELETE FROM enum_test") .execute(conn) .unwrap(); input_bind( "INSERT INTO enum_test(id, enum_field) VALUES (?, ?)", conn, 41, (b"blue".to_vec(), MysqlType::Enum), ); let enum_col_as_enum = query_single_table("SELECT enum_field FROM enum_test", conn, MysqlType::Enum); assert_eq!( enum_col_as_enum.tpe, ffi::enum_field_types::MYSQL_TYPE_STRING ); assert!(!enum_col_as_enum.flags.contains(Flags::NUM_FLAG)); assert!(!enum_col_as_enum.flags.contains(Flags::BLOB_FLAG)); assert!(!enum_col_as_enum.flags.contains(Flags::SET_FLAG)); assert!(enum_col_as_enum.flags.contains(Flags::ENUM_FLAG)); assert!(!enum_col_as_enum.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::(&enum_col_as_enum).unwrap(), "blue"); let enum_col_as_text = query_single_table( "SELECT enum_field FROM enum_test", conn, (ffi::enum_field_types::MYSQL_TYPE_BLOB, Flags::ENUM_FLAG), ); assert_eq!(enum_col_as_text.tpe, ffi::enum_field_types::MYSQL_TYPE_BLOB); assert!(!enum_col_as_text.flags.contains(Flags::NUM_FLAG)); assert!(!enum_col_as_text.flags.contains(Flags::BLOB_FLAG)); assert!(!enum_col_as_text.flags.contains(Flags::SET_FLAG)); assert!(enum_col_as_text.flags.contains(Flags::ENUM_FLAG)); assert!(!enum_col_as_text.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::(&enum_col_as_text).unwrap(), "blue"); assert_eq!( enum_col_as_enum.value().unwrap().as_bytes(), enum_col_as_text.value().unwrap().as_bytes() ); let enum_col_as_text = query_single_table( "SELECT enum_field FROM enum_test", conn, (ffi::enum_field_types::MYSQL_TYPE_BLOB, Flags::ENUM_FLAG), ); assert_eq!(enum_col_as_text.tpe, ffi::enum_field_types::MYSQL_TYPE_BLOB); assert!(!enum_col_as_text.flags.contains(Flags::NUM_FLAG)); assert!(!enum_col_as_text.flags.contains(Flags::BLOB_FLAG)); assert!(!enum_col_as_text.flags.contains(Flags::SET_FLAG)); assert!(enum_col_as_text.flags.contains(Flags::ENUM_FLAG)); assert!(!enum_col_as_text.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::(&enum_col_as_text).unwrap(), "blue"); assert_eq!( enum_col_as_enum.value().unwrap().as_bytes(), enum_col_as_text.value().unwrap().as_bytes() ); crate::sql_query("DELETE FROM enum_test") .execute(conn) .unwrap(); input_bind( "INSERT INTO enum_test(id, enum_field) VALUES (?, ?)", conn, 41, ( b"red".to_vec(), (ffi::enum_field_types::MYSQL_TYPE_BLOB, Flags::ENUM_FLAG), ), ); let enum_col_as_enum = query_single_table("SELECT enum_field FROM enum_test", conn, MysqlType::Enum); assert_eq!( enum_col_as_enum.tpe, ffi::enum_field_types::MYSQL_TYPE_STRING ); assert!(!enum_col_as_enum.flags.contains(Flags::NUM_FLAG)); assert!(!enum_col_as_enum.flags.contains(Flags::BLOB_FLAG)); assert!(!enum_col_as_enum.flags.contains(Flags::SET_FLAG)); assert!(enum_col_as_enum.flags.contains(Flags::ENUM_FLAG)); assert!(!enum_col_as_enum.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::(&enum_col_as_enum).unwrap(), "red"); let enum_col_as_text = query_single_table( "SELECT enum_field FROM enum_test", conn, (ffi::enum_field_types::MYSQL_TYPE_BLOB, Flags::ENUM_FLAG), ); assert_eq!(enum_col_as_text.tpe, ffi::enum_field_types::MYSQL_TYPE_BLOB); assert!(!enum_col_as_text.flags.contains(Flags::NUM_FLAG)); assert!(!enum_col_as_text.flags.contains(Flags::BLOB_FLAG)); assert!(!enum_col_as_text.flags.contains(Flags::SET_FLAG)); assert!(enum_col_as_text.flags.contains(Flags::ENUM_FLAG)); assert!(!enum_col_as_text.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::(&enum_col_as_text).unwrap(), "red"); assert_eq!( enum_col_as_enum.value().unwrap().as_bytes(), enum_col_as_text.value().unwrap().as_bytes() ); } #[test] fn check_set_bind() { let conn = &mut crate::test_helpers::connection(); crate::sql_query("DROP TABLE IF EXISTS set_test CASCADE") .execute(conn) .unwrap(); crate::sql_query("CREATE TABLE set_test(id INTEGER PRIMARY KEY, set_field SET('red', 'green', 'blue') NOT NULL)").execute(conn) .unwrap(); crate::sql_query("INSERT INTO set_test(id, set_field) VALUES (1, 'green')") .execute(conn) .unwrap(); let set_col_as_set: BindData = query_single_table("SELECT set_field FROM set_test", conn, MysqlType::Set); assert_eq!(set_col_as_set.tpe, ffi::enum_field_types::MYSQL_TYPE_STRING); assert!(!set_col_as_set.flags.contains(Flags::NUM_FLAG)); assert!(!set_col_as_set.flags.contains(Flags::BLOB_FLAG)); assert!(set_col_as_set.flags.contains(Flags::SET_FLAG)); assert!(!set_col_as_set.flags.contains(Flags::ENUM_FLAG)); assert!(!set_col_as_set.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::(&set_col_as_set).unwrap(), "green"); for tpe in &[ ffi::enum_field_types::MYSQL_TYPE_BLOB, ffi::enum_field_types::MYSQL_TYPE_VAR_STRING, ffi::enum_field_types::MYSQL_TYPE_TINY_BLOB, ffi::enum_field_types::MYSQL_TYPE_MEDIUM_BLOB, ffi::enum_field_types::MYSQL_TYPE_LONG_BLOB, ] { let set_col_as_text = query_single_table( "SELECT set_field FROM set_test", conn, (*tpe, Flags::SET_FLAG), ); assert_eq!(set_col_as_text.tpe, *tpe); assert!(!set_col_as_text.flags.contains(Flags::NUM_FLAG)); assert!(!set_col_as_text.flags.contains(Flags::BLOB_FLAG)); assert!(set_col_as_text.flags.contains(Flags::SET_FLAG)); assert!(!set_col_as_text.flags.contains(Flags::ENUM_FLAG)); assert!(!set_col_as_text.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::(&set_col_as_text).unwrap(), "green"); assert_eq!( set_col_as_set.value().unwrap().as_bytes(), set_col_as_text.value().unwrap().as_bytes() ); } let set_col_as_text = query_single_table( "SELECT set_field FROM set_test", conn, (ffi::enum_field_types::MYSQL_TYPE_BLOB, Flags::empty()), ); assert_eq!(set_col_as_text.tpe, ffi::enum_field_types::MYSQL_TYPE_BLOB); assert!(!set_col_as_text.flags.contains(Flags::NUM_FLAG)); assert!(!set_col_as_text.flags.contains(Flags::BLOB_FLAG)); assert!(!set_col_as_text.flags.contains(Flags::SET_FLAG)); assert!(!set_col_as_text.flags.contains(Flags::ENUM_FLAG)); assert!(!set_col_as_text.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::(&set_col_as_text).unwrap(), "green"); assert_eq!( set_col_as_set.value().unwrap().as_bytes(), set_col_as_text.value().unwrap().as_bytes() ); crate::sql_query("DELETE FROM set_test") .execute(conn) .unwrap(); input_bind( "INSERT INTO set_test(id, set_field) VALUES (?, ?)", conn, 41, (b"blue".to_vec(), MysqlType::Set), ); let set_col_as_set = query_single_table("SELECT set_field FROM set_test", conn, MysqlType::Set); assert_eq!(set_col_as_set.tpe, ffi::enum_field_types::MYSQL_TYPE_STRING); assert!(!set_col_as_set.flags.contains(Flags::NUM_FLAG)); assert!(!set_col_as_set.flags.contains(Flags::BLOB_FLAG)); assert!(set_col_as_set.flags.contains(Flags::SET_FLAG)); assert!(!set_col_as_set.flags.contains(Flags::ENUM_FLAG)); assert!(!set_col_as_set.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::(&set_col_as_set).unwrap(), "blue"); let set_col_as_text = query_single_table( "SELECT set_field FROM set_test", conn, (ffi::enum_field_types::MYSQL_TYPE_BLOB, Flags::SET_FLAG), ); assert_eq!(set_col_as_text.tpe, ffi::enum_field_types::MYSQL_TYPE_BLOB); assert!(!set_col_as_text.flags.contains(Flags::NUM_FLAG)); assert!(!set_col_as_text.flags.contains(Flags::BLOB_FLAG)); assert!(set_col_as_text.flags.contains(Flags::SET_FLAG)); assert!(!set_col_as_text.flags.contains(Flags::ENUM_FLAG)); assert!(!set_col_as_text.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::(&set_col_as_text).unwrap(), "blue"); assert_eq!( set_col_as_set.value().unwrap().as_bytes(), set_col_as_text.value().unwrap().as_bytes() ); crate::sql_query("DELETE FROM set_test") .execute(conn) .unwrap(); input_bind( "INSERT INTO set_test(id, set_field) VALUES (?, ?)", conn, 41, (b"red".to_vec(), MysqlType::String), ); let set_col_as_set = query_single_table("SELECT set_field FROM set_test", conn, MysqlType::Set); assert_eq!(set_col_as_set.tpe, ffi::enum_field_types::MYSQL_TYPE_STRING); assert!(!set_col_as_set.flags.contains(Flags::NUM_FLAG)); assert!(!set_col_as_set.flags.contains(Flags::BLOB_FLAG)); assert!(set_col_as_set.flags.contains(Flags::SET_FLAG)); assert!(!set_col_as_set.flags.contains(Flags::ENUM_FLAG)); assert!(!set_col_as_set.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::(&set_col_as_set).unwrap(), "red"); let set_col_as_text = query_single_table( "SELECT set_field FROM set_test", conn, (ffi::enum_field_types::MYSQL_TYPE_BLOB, Flags::SET_FLAG), ); assert_eq!(set_col_as_text.tpe, ffi::enum_field_types::MYSQL_TYPE_BLOB); assert!(!set_col_as_text.flags.contains(Flags::NUM_FLAG)); assert!(!set_col_as_text.flags.contains(Flags::BLOB_FLAG)); assert!(set_col_as_text.flags.contains(Flags::SET_FLAG)); assert!(!set_col_as_text.flags.contains(Flags::ENUM_FLAG)); assert!(!set_col_as_text.flags.contains(Flags::BINARY_FLAG)); assert_eq!(to_value::(&set_col_as_text).unwrap(), "red"); assert_eq!( set_col_as_set.value().unwrap().as_bytes(), set_col_as_text.value().unwrap().as_bytes() ); } }