22
33use crate :: { protocol:: Format , ProtocolError } ;
44use bytes:: { BufMut , BytesMut } ;
5- #[ cfg( feature = "with-chrono" ) ]
6- use chrono:: { NaiveDate , NaiveDateTime } ;
7- use std:: io:: { Error , ErrorKind } ;
85
96/// This trait explains how to encode values to the protocol format
107pub trait ToProtocolValue : std:: fmt:: Debug {
@@ -107,49 +104,12 @@ impl_primitive!(i64);
107104impl_primitive ! ( f32 ) ;
108105impl_primitive ! ( f64 ) ;
109106
110- // POSTGRES_EPOCH_JDATE
111- #[ cfg( feature = "with-chrono" ) ]
112- fn pg_base_date_epoch ( ) -> NaiveDateTime {
113- NaiveDate :: from_ymd_opt ( 2000 , 1 , 1 )
114- . unwrap ( )
115- . and_hms_opt ( 0 , 0 , 0 )
116- . unwrap ( )
117- }
118-
119- #[ cfg( feature = "with-chrono" ) ]
120- impl ToProtocolValue for NaiveDate {
121- // date_out - https://github.com/postgres/postgres/blob/REL_14_4/src/backend/utils/adt/date.c#L176
122- fn to_text ( & self , buf : & mut BytesMut ) -> Result < ( ) , ProtocolError > {
123- self . to_string ( ) . to_text ( buf)
124- }
125-
126- // date_send - https://github.com/postgres/postgres/blob/REL_14_4/src/backend/utils/adt/date.c#L223
127- fn to_binary ( & self , buf : & mut BytesMut ) -> Result < ( ) , ProtocolError > {
128- let n = self
129- . signed_duration_since ( pg_base_date_epoch ( ) . date ( ) )
130- . num_days ( ) ;
131- if n > ( i32:: MAX as i64 ) {
132- return Err ( Error :: new (
133- ErrorKind :: Other ,
134- format ! (
135- "value too large to store in the binary format (i32), actual: {}" ,
136- n
137- ) ,
138- )
139- . into ( ) ) ;
140- }
141-
142- buf. put_i32 ( 4 ) ;
143- buf. put_i32 ( n as i32 ) ;
144-
145- Ok ( ( ) )
146- }
147- }
148-
149107#[ cfg( test) ]
150108mod tests {
151109 use crate :: * ;
152110 use bytes:: BytesMut ;
111+ #[ cfg( feature = "with-chrono" ) ]
112+ use chrono:: NaiveDate ;
153113
154114 fn assert_text_encode < T : ToProtocolValue > ( value : T , expected : & [ u8 ] ) {
155115 let mut buf = BytesMut :: new ( ) ;
@@ -179,20 +139,48 @@ mod tests {
179139 105 , 110 , 115 , 32 , 53 , 46 , 48 , 48 , 48 , 48 , 48 , 54 , 32 , 115 , 101 , 99 , 115 ,
180140 ] ,
181141 ) ;
182- assert_text_encode (
183- TimestampValue :: new ( 0 , None ) ,
184- & [
185- 0 , 0 , 0 , 26 , 49 , 57 , 55 , 48 , 45 , 48 , 49 , 45 , 48 , 49 , 32 , 48 , 48 , 58 , 48 , 48 , 58 ,
186- 48 , 48 , 46 , 48 , 48 , 48 , 48 , 48 , 48 ,
187- ] ,
188- ) ;
189- assert_text_encode (
190- TimestampValue :: new ( 1650890322000000000 , None ) ,
191- & [
192- 0 , 0 , 0 , 26 , 50 , 48 , 50 , 50 , 45 , 48 , 52 , 45 , 50 , 53 , 32 , 49 , 50 , 58 , 51 , 56 , 58 ,
193- 52 , 50 , 46 , 48 , 48 , 48 , 48 , 48 , 48 ,
194- ] ,
195- ) ;
142+
143+ #[ cfg( feature = "with-chrono" ) ]
144+ {
145+ // Test TimestampValue encoding
146+ assert_text_encode (
147+ TimestampValue :: new ( 0 , None ) ,
148+ & [
149+ 0 , 0 , 0 , 26 , 49 , 57 , 55 , 48 , 45 , 48 , 49 , 45 , 48 , 49 , 32 , 48 , 48 , 58 , 48 , 48 ,
150+ 58 , 48 , 48 , 46 , 48 , 48 , 48 , 48 , 48 , 48 ,
151+ ] ,
152+ ) ;
153+ assert_text_encode (
154+ TimestampValue :: new ( 1650890322000000000 , None ) ,
155+ & [
156+ 0 , 0 , 0 , 26 , 50 , 48 , 50 , 50 , 45 , 48 , 52 , 45 , 50 , 53 , 32 , 49 , 50 , 58 , 51 , 56 ,
157+ 58 , 52 , 50 , 46 , 48 , 48 , 48 , 48 , 48 , 48 ,
158+ ] ,
159+ ) ;
160+
161+ // Test NaiveDate encoding
162+ assert_text_encode (
163+ NaiveDate :: from_ymd_opt ( 2025 , 8 , 8 ) . unwrap ( ) ,
164+ & [
165+ 0 , 0 , 0 , 10 , // length: 10 bytes
166+ 50 , 48 , 50 , 53 , 45 , 48 , 56 , 45 , 48 , 56 , // "2025-08-08"
167+ ] ,
168+ ) ;
169+ assert_text_encode (
170+ NaiveDate :: from_ymd_opt ( 2000 , 1 , 1 ) . unwrap ( ) ,
171+ & [
172+ 0 , 0 , 0 , 10 , // length: 10 bytes
173+ 50 , 48 , 48 , 48 , 45 , 48 , 49 , 45 , 48 , 49 , // "2000-01-01"
174+ ] ,
175+ ) ;
176+ assert_text_encode (
177+ NaiveDate :: from_ymd_opt ( 1999 , 12 , 31 ) . unwrap ( ) ,
178+ & [
179+ 0 , 0 , 0 , 10 , // length: 10 bytes
180+ 49 , 57 , 57 , 57 , 45 , 49 , 50 , 45 , 51 , 49 , // "1999-12-31"
181+ ] ,
182+ ) ;
183+ }
196184
197185 Ok ( ( ) )
198186 }
@@ -218,14 +206,45 @@ mod tests {
218206 0 , 0 , 0 , 16 , 0 , 0 , 0 , 2 , 146 , 85 , 83 , 70 , 0 , 0 , 0 , 2 , 0 , 0 , 0 , 1 ,
219207 ] ,
220208 ) ;
221- assert_bind_encode (
222- TimestampValue :: new ( 0 , None ) ,
223- & [ 0 , 0 , 0 , 8 , 255 , 252 , 162 , 254 , 196 , 200 , 32 , 0 ] ,
224- ) ;
225- assert_bind_encode (
226- TimestampValue :: new ( 1650890322000000000 , None ) ,
227- & [ 0 , 0 , 0 , 8 , 0 , 2 , 128 , 120 , 159 , 252 , 216 , 128 ] ,
228- ) ;
209+
210+ #[ cfg( feature = "with-chrono" ) ]
211+ {
212+ // Test TimestampValue binary encoding
213+ assert_bind_encode (
214+ TimestampValue :: new ( 0 , None ) ,
215+ & [ 0 , 0 , 0 , 8 , 255 , 252 , 162 , 254 , 196 , 200 , 32 , 0 ] ,
216+ ) ;
217+ assert_bind_encode (
218+ TimestampValue :: new ( 1650890322000000000 , None ) ,
219+ & [ 0 , 0 , 0 , 8 , 0 , 2 , 128 , 120 , 159 , 252 , 216 , 128 ] ,
220+ ) ;
221+
222+ // Test NaiveDate binary encoding
223+ // PostgreSQL epoch is 2000-01-01, so this date should be 0 days
224+ assert_bind_encode (
225+ NaiveDate :: from_ymd_opt ( 2000 , 1 , 1 ) . unwrap ( ) ,
226+ & [
227+ 0 , 0 , 0 , 4 , // length: 4 bytes
228+ 0 , 0 , 0 , 0 , // 0 days from epoch
229+ ] ,
230+ ) ;
231+ // Date after epoch: 2025-08-08 is 9351 days after 2000-01-01
232+ assert_bind_encode (
233+ NaiveDate :: from_ymd_opt ( 2025 , 8 , 8 ) . unwrap ( ) ,
234+ & [
235+ 0 , 0 , 0 , 4 , // length: 4 bytes
236+ 0 , 0 , 36 , 135 , // 9351 days from epoch (0x2487 in hex)
237+ ] ,
238+ ) ;
239+ // Date before epoch: 1999-12-31 is -1 day from 2000-01-01
240+ assert_bind_encode (
241+ NaiveDate :: from_ymd_opt ( 1999 , 12 , 31 ) . unwrap ( ) ,
242+ & [
243+ 0 , 0 , 0 , 4 , // length: 4 bytes
244+ 255 , 255 , 255 , 255 , // -1 in two's complement
245+ ] ,
246+ ) ;
247+ }
229248
230249 Ok ( ( ) )
231250 }
0 commit comments