1use std::borrow::Cow;
2use std::convert::TryFrom;
3
4use byteorder::{ByteOrder, LittleEndian};
5use bytes::Buf;
6use time::{Date, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset};
7
8use cdbc::decode::Decode;
9use cdbc::encode::{Encode, IsNull};
10use cdbc::error::{BoxDynError, UnexpectedNullError};
11use crate::protocol::text::ColumnType;
12use crate::type_info::MySqlTypeInfo;
13use crate::{MySql, MySqlValueFormat, MySqlValueRef};
14use cdbc::types::Type;
15
16impl Type<MySql> for OffsetDateTime {
17 fn type_info() -> MySqlTypeInfo {
18 MySqlTypeInfo::binary(ColumnType::Timestamp)
19 }
20
21 fn compatible(ty: &MySqlTypeInfo) -> bool {
22 matches!(ty.r#type, ColumnType::Datetime | ColumnType::Timestamp)
23 }
24}
25
26impl Encode<'_, MySql> for OffsetDateTime {
27 fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
28 let utc_dt = self.to_offset(UtcOffset::UTC);
29 let primitive_dt = PrimitiveDateTime::new(utc_dt.date(), utc_dt.time());
30
31 Encode::<MySql>::encode(&primitive_dt, buf)
32 }
33}
34
35impl<'r> Decode<'r, MySql> for OffsetDateTime {
36 fn decode(value: MySqlValueRef<'r>) -> Result<Self, BoxDynError> {
37 let primitive: PrimitiveDateTime = Decode::<MySql>::decode(value)?;
38
39 Ok(primitive.assume_utc())
40 }
41}
42
43impl Type<MySql> for Time {
44 fn type_info() -> MySqlTypeInfo {
45 MySqlTypeInfo::binary(ColumnType::Time)
46 }
47}
48
49impl Encode<'_, MySql> for Time {
50 fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
51 let len = Encode::<MySql>::size_hint(self) - 1;
52 buf.push(len as u8);
53
54 buf.push(0);
56
57 buf.extend_from_slice(&[0_u8; 4]);
60
61 encode_time(self, len > 9, buf);
62
63 IsNull::No
64 }
65
66 fn size_hint(&self) -> usize {
67 if self.nanosecond() == 0 {
68 9
70 } else {
71 13
73 }
74 }
75}
76
77impl<'r> Decode<'r, MySql> for Time {
78 fn decode(value: MySqlValueRef<'r>) -> Result<Self, BoxDynError> {
79 match value.format() {
80 MySqlValueFormat::Binary => {
81 let mut buf = value.as_bytes()?;
82
83 let len = buf.get_u8();
85
86 if len == 0 {
90 return Ok(Time::from_hms(0,0,0).unwrap());
91 }
92
93 let is_negative = buf.get_u8();
95 assert_eq!(is_negative, 0, "Negative dates/times are not supported");
96
97 buf.advance(4);
100
101 decode_time(len - 5, buf)
102 }
103
104 MySqlValueFormat::Text => {
105 let s = value.as_str()?;
106
107 let s = if s.len() < 20 {
112 Cow::Owned(format!("{:0<19}", s))
113 } else {
114 Cow::Borrowed(s)
115 };
116 let format = time::format_description::parse("[hour]:[minute]:[second]")?;
117 Time::parse(&*s as &str, &format).map_err(Into::into)
118 }
119 }
120 }
121}
122
123impl Type<MySql> for Date {
124 fn type_info() -> MySqlTypeInfo {
125 MySqlTypeInfo::binary(ColumnType::Date)
126 }
127}
128
129impl Encode<'_, MySql> for Date {
130 fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
131 buf.push(4);
132
133 encode_date(self, buf);
134
135 IsNull::No
136 }
137
138 fn size_hint(&self) -> usize {
139 5
140 }
141}
142
143impl<'r> Decode<'r, MySql> for Date {
144 fn decode(value: MySqlValueRef<'r>) -> Result<Self, BoxDynError> {
145 match value.format() {
146 MySqlValueFormat::Binary => {
147 Ok(decode_date(&value.as_bytes()?[1..])?.ok_or(UnexpectedNullError)?)
148 }
149 MySqlValueFormat::Text => {
150 let s = value.as_str()?;
151 let format = time::format_description::parse("[year]-[month]-[day]")?;
152 Date::parse(s, &format).map_err(Into::into)
153 }
154 }
155 }
156}
157
158impl Type<MySql> for PrimitiveDateTime {
159 fn type_info() -> MySqlTypeInfo {
160 MySqlTypeInfo::binary(ColumnType::Datetime)
161 }
162}
163
164impl Encode<'_, MySql> for PrimitiveDateTime {
165 fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
166 let len = Encode::<MySql>::size_hint(self) - 1;
167 buf.push(len as u8);
168
169 encode_date(&self.date(), buf);
170
171 if len > 4 {
172 encode_time(&self.time(), len > 8, buf);
173 }
174
175 IsNull::No
176 }
177
178 fn size_hint(&self) -> usize {
179 match (self.hour(), self.minute(), self.second(), self.nanosecond()) {
181 (0, 0, 0, 0) => 5,
184
185 (_, _, _, 0) => 8,
188
189 (_, _, _, _) => 12,
191 }
192 }
193}
194
195impl<'r> Decode<'r, MySql> for PrimitiveDateTime {
196 fn decode(value: MySqlValueRef<'r>) -> Result<Self, BoxDynError> {
197 match value.format() {
198 MySqlValueFormat::Binary => {
199 let buf = value.as_bytes()?;
200 let len = buf[0];
201 let date = decode_date(&buf[1..])?.ok_or(UnexpectedNullError)?;
202
203 let dt = if len > 4 {
204 date.with_time(decode_time(len - 4, &buf[5..])?)
205 } else {
206 date.midnight()
207 };
208
209 Ok(dt)
210 }
211
212 MySqlValueFormat::Text => {
213 let s = value.as_str()?;
214
215 let s = if s.len() < 31 {
220 if s.contains('.') {
221 Cow::Owned(format!("{:0<30}", s))
222 } else {
223 Cow::Owned(format!("{}.000000000", s))
224 }
225 } else {
226 Cow::Borrowed(s)
227 };
228 let format = time::format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]")?;
229 PrimitiveDateTime::parse(&*s, &format).map_err(Into::into)
230 }
231 }
232 }
233}
234
235fn encode_date(date: &Date, buf: &mut Vec<u8>) {
236 let year = u16::try_from(date.year())
238 .unwrap_or_else(|_| panic!("Date out of range for Mysql: {}", date));
239
240 buf.extend_from_slice(&year.to_le_bytes());
241 buf.push(date.month() as i32 as u8);
242 buf.push(date.day());
243}
244
245fn decode_date(buf: &[u8]) -> Result<Option<Date>, BoxDynError> {
246 if buf.is_empty() {
247 return Ok(None);
249 }
250
251 Date::from_calendar_date(
252 LittleEndian::read_u16(buf) as i32,
253 time::Month::try_from(buf[2] as u8).unwrap(),
254 buf[3] as u8,
255 )
256 .map_err(Into::into)
257 .map(Some)
258}
259
260fn encode_time(time: &Time, include_micros: bool, buf: &mut Vec<u8>) {
261 buf.push(time.hour());
262 buf.push(time.minute());
263 buf.push(time.second());
264
265 if include_micros {
266 buf.extend(&((time.nanosecond() / 1000) as u32).to_le_bytes());
267 }
268}
269
270fn decode_time(len: u8, mut buf: &[u8]) -> Result<Time, BoxDynError> {
271 let hour = buf.get_u8();
272 let minute = buf.get_u8();
273 let seconds = buf.get_u8();
274
275 let micros = if len > 3 {
276 buf.get_uint_le(buf.len())
278 } else {
279 0
280 };
281
282 Time::from_hms_micro(hour, minute, seconds, micros as u32)
283 .map_err(|e| format!("Time out of range for MySQL: {}", e).into())
284}
285
286
287impl Type<MySql> for mco::std::time::time::Time{
288 fn type_info() -> MySqlTypeInfo {
289 MySqlTypeInfo::binary(ColumnType::Timestamp)
290 }
291
292 fn compatible(ty: &MySqlTypeInfo) -> bool {
293 matches!(ty.r#type, ColumnType::Datetime | ColumnType::Timestamp)
294 }
295}
296
297impl Encode<'_, MySql> for mco::std::time::time::Time {
298 fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
299 self.inner.encode_by_ref(buf)
300 }
301}
302
303impl<'r> Decode<'r, MySql> for mco::std::time::time::Time {
304 fn decode(value: MySqlValueRef<'r>) -> Result<Self, BoxDynError> {
305 Ok(mco::std::time::time::Time{ inner: OffsetDateTime::decode(value)? })
306 }
307}