1
1
use bytes:: Buf ;
2
- use chrono:: {
3
- DateTime , Datelike , Local , NaiveDate , NaiveDateTime , NaiveTime , TimeZone , Timelike , Utc ,
4
- } ;
2
+ use chrono:: { DateTime , Datelike , Local , NaiveDate , NaiveDateTime , NaiveTime , TimeZone , Timelike , Utc } ;
3
+ use sqlx_core:: database:: Database ;
5
4
6
5
use crate :: decode:: Decode ;
7
6
use crate :: encode:: { Encode , IsNull } ;
8
7
use crate :: error:: { BoxDynError , UnexpectedNullError } ;
9
8
use crate :: protocol:: text:: ColumnType ;
10
9
use crate :: type_info:: MySqlTypeInfo ;
11
- use crate :: types:: Type ;
10
+ use crate :: types:: { MySqlTime , MySqlTimeSign , Type } ;
12
11
use crate :: { MySql , MySqlValueFormat , MySqlValueRef } ;
13
12
14
13
impl Type < MySql > for DateTime < Utc > {
@@ -63,7 +62,7 @@ impl<'r> Decode<'r, MySql> for DateTime<Local> {
63
62
64
63
impl Type < MySql > for NaiveTime {
65
64
fn type_info ( ) -> MySqlTypeInfo {
66
- MySqlTypeInfo :: binary ( ColumnType :: Time )
65
+ MySqlTime :: type_info ( )
67
66
}
68
67
}
69
68
@@ -75,7 +74,7 @@ impl Encode<'_, MySql> for NaiveTime {
75
74
// NaiveTime is not negative
76
75
buf. push ( 0 ) ;
77
76
78
- // "date on 4 bytes little-endian format" (?)
77
+ // Number of days in the interval; always 0 for time-of-day values.
79
78
// https://mariadb.com/kb/en/resultset-row/#teimstamp-binary-encoding
80
79
buf. extend_from_slice ( & [ 0_u8 ; 4 ] ) ;
81
80
@@ -95,34 +94,18 @@ impl Encode<'_, MySql> for NaiveTime {
95
94
}
96
95
}
97
96
97
+ /// Decode from a `TIME` value.
98
+ ///
99
+ /// ### Errors
100
+ /// Returns an error if the `TIME` value is negative or exceeds `23:59:59.999999`.
98
101
impl < ' r > Decode < ' r , MySql > for NaiveTime {
99
102
fn decode ( value : MySqlValueRef < ' r > ) -> Result < Self , BoxDynError > {
100
103
match value. format ( ) {
101
104
MySqlValueFormat :: Binary => {
102
- let mut buf = value. as_bytes ( ) ?;
103
-
104
- // data length, expecting 8 or 12 (fractional seconds)
105
- let len = buf. get_u8 ( ) ;
106
-
107
- // MySQL specifies that if all of hours, minutes, seconds, microseconds
108
- // are 0 then the length is 0 and no further data is send
109
- // https://dev.mysql.com/doc/internals/en/binary-protocol-value.html
110
- if len == 0 {
111
- return Ok ( NaiveTime :: from_hms_micro_opt ( 0 , 0 , 0 , 0 )
112
- . expect ( "expected NaiveTime to construct from all zeroes" ) ) ;
113
- }
114
-
115
- // is negative : int<1>
116
- let is_negative = buf. get_u8 ( ) ;
117
- debug_assert_eq ! ( is_negative, 0 , "Negative dates/times are not supported" ) ;
118
-
119
- // "date on 4 bytes little-endian format" (?)
120
- // https://mariadb.com/kb/en/resultset-row/#timestamp-binary-encoding
121
- buf. advance ( 4 ) ;
122
-
123
- decode_time ( len - 5 , buf)
105
+ // Covers most possible failure modes.
106
+ MySqlTime :: decode ( value) ?. try_into ( )
124
107
}
125
-
108
+ // Retaining this parsing for now as it allows us to cross-check our impl.
126
109
MySqlValueFormat :: Text => {
127
110
let s = value. as_str ( ) ?;
128
111
NaiveTime :: parse_from_str ( s, "%H:%M:%S%.f" ) . map_err ( Into :: into)
@@ -131,6 +114,55 @@ impl<'r> Decode<'r, MySql> for NaiveTime {
131
114
}
132
115
}
133
116
117
+ impl TryFrom < MySqlTime > for NaiveTime {
118
+ type Error = BoxDynError ;
119
+
120
+ fn try_from ( time : MySqlTime ) -> Result < Self , Self :: Error > {
121
+ NaiveTime :: from_hms_micro_opt (
122
+ time. hours ( ) ,
123
+ time. minutes ( ) as u32 ,
124
+ time. seconds ( ) as u32 ,
125
+ time. microseconds ( ) ,
126
+ )
127
+ . ok_or_else ( || format ! ( "Cannot convert `MySqlTime` value to `NaiveTime`: {time}" ) . into ( ) )
128
+ }
129
+ }
130
+
131
+ impl From < MySqlTime > for chrono:: TimeDelta {
132
+ fn from ( time : MySqlTime ) -> Self {
133
+ chrono:: TimeDelta :: new ( time. whole_seconds_signed ( ) , time. subsec_nanos ( ) )
134
+ . expect ( "BUG: chrono::TimeDelta should have a greater range than MySqlTime" )
135
+ }
136
+ }
137
+
138
+ impl TryFrom < chrono:: TimeDelta > for MySqlTime {
139
+ type Error = BoxDynError ;
140
+
141
+ fn try_from ( value : chrono:: TimeDelta ) -> Result < Self , Self :: Error > {
142
+ let sign = if value < chrono:: TimeDelta :: zero ( ) { MySqlTimeSign :: Negative } else {
143
+ MySqlTimeSign :: Positive
144
+ } ;
145
+
146
+ Ok (
147
+ // `std::time::Duration` has a greater positive range than `TimeDelta`
148
+ // which makes it a great intermediate if you ignore the sign.
149
+ MySqlTime :: try_from ( value. abs ( ) . to_std ( ) ?) ?. with_sign ( sign)
150
+ )
151
+ }
152
+ }
153
+
154
+ impl Type < MySql > for chrono:: TimeDelta {
155
+ fn type_info ( ) -> MySqlTypeInfo {
156
+ MySqlTime :: type_info ( )
157
+ }
158
+ }
159
+
160
+ impl < ' r > Decode < ' r , MySql > for chrono:: TimeDelta {
161
+ fn decode ( value : <MySql as Database >:: ValueRef < ' r > ) -> Result < Self , BoxDynError > {
162
+ Ok ( MySqlTime :: decode ( value) ?. into ( ) )
163
+ }
164
+ }
165
+
134
166
impl Type < MySql > for NaiveDate {
135
167
fn type_info ( ) -> MySqlTypeInfo {
136
168
MySqlTypeInfo :: binary ( ColumnType :: Date )
@@ -155,7 +187,14 @@ impl<'r> Decode<'r, MySql> for NaiveDate {
155
187
fn decode ( value : MySqlValueRef < ' r > ) -> Result < Self , BoxDynError > {
156
188
match value. format ( ) {
157
189
MySqlValueFormat :: Binary => {
158
- decode_date ( & value. as_bytes ( ) ?[ 1 ..] ) ?. ok_or_else ( || UnexpectedNullError . into ( ) )
190
+ let buf = value. as_bytes ( ) ?;
191
+
192
+ // Row decoding should have left the length prefix.
193
+ if buf. is_empty ( ) {
194
+ return Err ( "empty buffer" . into ( ) ) ;
195
+ }
196
+
197
+ decode_date ( & buf[ 1 ..] ) ?. ok_or_else ( || UnexpectedNullError . into ( ) )
159
198
}
160
199
161
200
MySqlValueFormat :: Text => {
@@ -214,6 +253,10 @@ impl<'r> Decode<'r, MySql> for NaiveDateTime {
214
253
MySqlValueFormat :: Binary => {
215
254
let buf = value. as_bytes ( ) ?;
216
255
256
+ if buf. is_empty ( ) {
257
+ return Err ( "empty buffer" . into ( ) ) ;
258
+ }
259
+
217
260
let len = buf[ 0 ] ;
218
261
let date = decode_date ( & buf[ 1 ..] ) ?. ok_or ( UnexpectedNullError ) ?;
219
262
0 commit comments