1- use std:: fmt:: { LowerHex , UpperHex } ;
1+ use chrono:: { LocalResult , TimeZone , Utc } ;
2+ use scylla_cql:: frame:: response:: result:: CqlValue ;
3+
4+ use std:: borrow:: Borrow ;
5+ use std:: fmt:: { Display , LowerHex , UpperHex } ;
26
37pub ( crate ) struct HexBytes < ' a > ( pub ( crate ) & ' a [ u8 ] ) ;
48
@@ -19,3 +23,313 @@ impl<'a> UpperHex for HexBytes<'a> {
1923 Ok ( ( ) )
2024 }
2125}
26+
27+ // Displays a CqlValue. The syntax should resemble the CQL literal syntax
28+ // (but no guarantee is given that it's always the same).
29+ pub ( crate ) struct CqlValueDisplayer < C > ( pub C ) ;
30+
31+ impl < C > Display for CqlValueDisplayer < C >
32+ where
33+ C : Borrow < CqlValue > ,
34+ {
35+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
36+ match self . 0 . borrow ( ) {
37+ // Scalar types
38+ CqlValue :: Ascii ( a) => write ! ( f, "{}" , CqlStringLiteralDisplayer ( a) ) ?,
39+ CqlValue :: Text ( t) => write ! ( f, "{}" , CqlStringLiteralDisplayer ( t) ) ?,
40+ CqlValue :: Blob ( b) => write ! ( f, "0x{:x}" , HexBytes ( b) ) ?,
41+ CqlValue :: Empty => write ! ( f, "0x" ) ?,
42+ CqlValue :: Decimal ( d) => write ! ( f, "{}" , d) ?,
43+ CqlValue :: Float ( fl) => write ! ( f, "{}" , fl) ?,
44+ CqlValue :: Double ( d) => write ! ( f, "{}" , d) ?,
45+ CqlValue :: Boolean ( b) => write ! ( f, "{}" , b) ?,
46+ CqlValue :: Int ( i) => write ! ( f, "{}" , i) ?,
47+ CqlValue :: BigInt ( bi) => write ! ( f, "{}" , bi) ?,
48+ CqlValue :: Inet ( i) => write ! ( f, "'{}'" , i) ?,
49+ CqlValue :: SmallInt ( si) => write ! ( f, "{}" , si) ?,
50+ CqlValue :: TinyInt ( ti) => write ! ( f, "{}" , ti) ?,
51+ CqlValue :: Varint ( vi) => write ! ( f, "{}" , vi) ?,
52+ CqlValue :: Counter ( c) => write ! ( f, "{}" , c. 0 ) ?,
53+ CqlValue :: Date ( d) => {
54+ let days_since_epoch = chrono:: Duration :: days ( * d as i64 - ( 1 << 31 ) ) ;
55+
56+ // TODO: chrono::NaiveDate does not handle the whole range
57+ // supported by the `date` datatype
58+ match chrono:: NaiveDate :: from_ymd_opt ( 1970 , 1 , 1 )
59+ . unwrap ( )
60+ . checked_add_signed ( days_since_epoch)
61+ {
62+ Some ( d) => write ! ( f, "'{}'" , d) ?,
63+ None => f. write_str ( "<date out of representable range>" ) ?,
64+ }
65+ }
66+ CqlValue :: Duration ( d) => write ! ( f, "{}mo{}d{}ns" , d. months, d. days, d. nanoseconds) ?,
67+ CqlValue :: Time ( t) => {
68+ write ! (
69+ f,
70+ "'{:02}:{:02}:{:02}.{:09}'" ,
71+ t. num_hours( ) % 24 ,
72+ t. num_minutes( ) % 60 ,
73+ t. num_seconds( ) % 60 ,
74+ t. num_nanoseconds( ) . unwrap_or( 0 ) % 1_000_000_000 ,
75+ ) ?;
76+ }
77+ CqlValue :: Timestamp ( t) => {
78+ match Utc . timestamp_millis_opt ( t. num_milliseconds ( ) ) {
79+ LocalResult :: Ambiguous ( _, _) => unreachable ! ( ) , // not supposed to happen with timestamp_millis_opt
80+ LocalResult :: Single ( d) => {
81+ write ! ( f, "{}" , d. format( "'%Y-%m-%d %H:%M:%S%.3f%z'" ) ) ?
82+ }
83+ LocalResult :: None => f. write_str ( "<timestamp out of representable range>" ) ?,
84+ }
85+ }
86+ CqlValue :: Timeuuid ( t) => write ! ( f, "{}" , t) ?,
87+ CqlValue :: Uuid ( u) => write ! ( f, "{}" , u) ?,
88+
89+ // Compound types
90+ CqlValue :: Tuple ( t) => {
91+ f. write_str ( "(" ) ?;
92+ CommaSeparatedDisplayer (
93+ t. iter ( )
94+ . map ( |x| MaybeNullDisplayer ( x. as_ref ( ) . map ( CqlValueDisplayer ) ) ) ,
95+ )
96+ . fmt ( f) ?;
97+ f. write_str ( ")" ) ?;
98+ }
99+ CqlValue :: List ( v) => {
100+ f. write_str ( "[" ) ?;
101+ CommaSeparatedDisplayer ( v. iter ( ) . map ( CqlValueDisplayer ) ) . fmt ( f) ?;
102+ f. write_str ( "]" ) ?;
103+ }
104+ CqlValue :: Set ( v) => {
105+ f. write_str ( "{" ) ?;
106+ CommaSeparatedDisplayer ( v. iter ( ) . map ( CqlValueDisplayer ) ) . fmt ( f) ?;
107+ f. write_str ( "}" ) ?;
108+ }
109+ CqlValue :: Map ( m) => {
110+ f. write_str ( "{" ) ?;
111+ CommaSeparatedDisplayer (
112+ m. iter ( )
113+ . map ( |( k, v) | PairDisplayer ( CqlValueDisplayer ( k) , CqlValueDisplayer ( v) ) ) ,
114+ )
115+ . fmt ( f) ?;
116+ f. write_str ( "}" ) ?;
117+ }
118+ CqlValue :: UserDefinedType {
119+ keyspace : _,
120+ type_name : _,
121+ fields,
122+ } => {
123+ f. write_str ( "{" ) ?;
124+ CommaSeparatedDisplayer ( fields. iter ( ) . map ( |( k, v) | {
125+ PairDisplayer ( k, MaybeNullDisplayer ( v. as_ref ( ) . map ( CqlValueDisplayer ) ) )
126+ } ) )
127+ . fmt ( f) ?;
128+ f. write_str ( "}" ) ?;
129+ }
130+ }
131+ Ok ( ( ) )
132+ }
133+ }
134+
135+ pub ( crate ) struct CqlStringLiteralDisplayer < ' a > ( & ' a str ) ;
136+
137+ impl < ' a > Display for CqlStringLiteralDisplayer < ' a > {
138+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
139+ // CQL string literals use single quotes. The only character that
140+ // needs escaping is singular quote, and escaping is done by repeating
141+ // the quote character.
142+ f. write_str ( "'" ) ?;
143+ let mut first = true ;
144+ for part in self . 0 . split ( '\'' ) {
145+ if first {
146+ first = false ;
147+ } else {
148+ f. write_str ( "''" ) ?;
149+ }
150+ f. write_str ( part) ?;
151+ }
152+ f. write_str ( "'" ) ?;
153+ Ok ( ( ) )
154+ }
155+ }
156+
157+ pub ( crate ) struct CommaSeparatedDisplayer < I > ( pub I ) ;
158+
159+ impl < I , T > Display for CommaSeparatedDisplayer < I >
160+ where
161+ I : Iterator < Item = T > + Clone ,
162+ T : Display ,
163+ {
164+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
165+ let mut first = true ;
166+ for t in self . 0 . clone ( ) {
167+ if first {
168+ first = false ;
169+ } else {
170+ f. write_str ( "," ) ?;
171+ }
172+ write ! ( f, "{}" , t) ?;
173+ }
174+ Ok ( ( ) )
175+ }
176+ }
177+
178+ pub ( crate ) struct PairDisplayer < K , V > ( K , V ) ;
179+
180+ impl < K , V > Display for PairDisplayer < K , V >
181+ where
182+ K : Display ,
183+ V : Display ,
184+ {
185+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
186+ write ! ( f, "{}:{}" , self . 0 , self . 1 )
187+ }
188+ }
189+
190+ pub ( crate ) struct MaybeNullDisplayer < T > ( Option < T > ) ;
191+
192+ impl < T > Display for MaybeNullDisplayer < T >
193+ where
194+ T : Display ,
195+ {
196+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
197+ match & self . 0 {
198+ None => write ! ( f, "null" ) ?,
199+ Some ( v) => write ! ( f, "{}" , v) ?,
200+ }
201+ Ok ( ( ) )
202+ }
203+ }
204+
205+ #[ cfg( test) ]
206+ mod tests {
207+ use std:: str:: FromStr ;
208+ use std:: time:: Duration ;
209+
210+ use bigdecimal:: BigDecimal ;
211+ use chrono:: { NaiveDate , NaiveDateTime , NaiveTime } ;
212+
213+ use scylla_cql:: frame:: response:: result:: CqlValue ;
214+ use scylla_cql:: frame:: value:: CqlDuration ;
215+
216+ use crate :: utils:: pretty:: CqlValueDisplayer ;
217+
218+ #[ test]
219+ fn test_cql_value_displayer ( ) {
220+ assert_eq ! (
221+ format!( "{}" , CqlValueDisplayer ( CqlValue :: Boolean ( true ) ) ) ,
222+ "true"
223+ ) ;
224+ assert_eq ! ( format!( "{}" , CqlValueDisplayer ( CqlValue :: Int ( 123 ) ) ) , "123" ) ;
225+ assert_eq ! (
226+ format!(
227+ "{}" ,
228+ CqlValueDisplayer ( CqlValue :: Decimal ( BigDecimal :: from_str( "123.456" ) . unwrap( ) ) )
229+ ) ,
230+ "123.456"
231+ ) ;
232+ assert_eq ! (
233+ format!( "{}" , CqlValueDisplayer ( CqlValue :: Float ( 12.75 ) ) ) ,
234+ "12.75"
235+ ) ;
236+ assert_eq ! (
237+ format!(
238+ "{}" ,
239+ CqlValueDisplayer ( CqlValue :: Text ( "Ala ma kota" . to_owned( ) ) )
240+ ) ,
241+ "'Ala ma kota'"
242+ ) ;
243+ assert_eq ! (
244+ format!( "{}" , CqlValueDisplayer ( CqlValue :: Text ( "Foo's" . to_owned( ) ) ) ) ,
245+ "'Foo''s'"
246+ ) ;
247+
248+ // Time types are the most tricky
249+ assert_eq ! (
250+ format!( "{}" , CqlValueDisplayer ( CqlValue :: Date ( 40 + ( 1 << 31 ) ) ) ) ,
251+ "'1970-02-10'"
252+ ) ;
253+ assert_eq ! (
254+ format!(
255+ "{}" ,
256+ CqlValueDisplayer ( CqlValue :: Duration ( CqlDuration {
257+ months: 1 ,
258+ days: 2 ,
259+ nanoseconds: 3 ,
260+ } ) )
261+ ) ,
262+ "1mo2d3ns"
263+ ) ;
264+ let t = Duration :: from_nanos ( 123 )
265+ + Duration :: from_secs ( 4 )
266+ + Duration :: from_secs ( 5 * 60 )
267+ + Duration :: from_secs ( 6 * 60 * 60 ) ;
268+ assert_eq ! (
269+ format!(
270+ "{}" ,
271+ CqlValueDisplayer ( CqlValue :: Time ( chrono:: Duration :: from_std( t) . unwrap( ) ) )
272+ ) ,
273+ "'06:05:04.000000123'"
274+ ) ;
275+
276+ let t = NaiveDate :: from_ymd_opt ( 2005 , 4 , 2 )
277+ . unwrap ( )
278+ . and_time ( NaiveTime :: from_hms_opt ( 19 , 37 , 42 ) . unwrap ( ) ) ;
279+ assert_eq ! (
280+ format!(
281+ "{}" ,
282+ CqlValueDisplayer ( CqlValue :: Timestamp (
283+ t. signed_duration_since( NaiveDateTime :: default ( ) )
284+ ) )
285+ ) ,
286+ "'2005-04-02 19:37:42.000+0000'"
287+ ) ;
288+
289+ // Compound types
290+ let list_or_set = vec ! [ CqlValue :: Int ( 1 ) , CqlValue :: Int ( 3 ) , CqlValue :: Int ( 2 ) ] ;
291+ assert_eq ! (
292+ format!( "{}" , CqlValueDisplayer ( CqlValue :: List ( list_or_set. clone( ) ) ) ) ,
293+ "[1,3,2]"
294+ ) ;
295+ assert_eq ! (
296+ format!( "{}" , CqlValueDisplayer ( CqlValue :: Set ( list_or_set. clone( ) ) ) ) ,
297+ "{1,3,2}"
298+ ) ;
299+
300+ let tuple: Vec < _ > = list_or_set
301+ . into_iter ( )
302+ . map ( Some )
303+ . chain ( std:: iter:: once ( None ) )
304+ . collect ( ) ;
305+ assert_eq ! (
306+ format!( "{}" , CqlValueDisplayer ( CqlValue :: Tuple ( tuple) ) ) ,
307+ "(1,3,2,null)"
308+ ) ;
309+
310+ let map = vec ! [
311+ ( CqlValue :: Text ( "foo" . to_owned( ) ) , CqlValue :: Int ( 123 ) ) ,
312+ ( CqlValue :: Text ( "bar" . to_owned( ) ) , CqlValue :: Int ( 321 ) ) ,
313+ ] ;
314+ assert_eq ! (
315+ format!( "{}" , CqlValueDisplayer ( CqlValue :: Map ( map) ) ) ,
316+ "{'foo':123,'bar':321}"
317+ ) ;
318+
319+ let fields = vec ! [
320+ ( "foo" . to_owned( ) , Some ( CqlValue :: Int ( 123 ) ) ) ,
321+ ( "bar" . to_owned( ) , Some ( CqlValue :: Int ( 321 ) ) ) ,
322+ ] ;
323+ assert_eq ! (
324+ format!(
325+ "{}" ,
326+ CqlValueDisplayer ( CqlValue :: UserDefinedType {
327+ keyspace: "ks" . to_owned( ) ,
328+ type_name: "typ" . to_owned( ) ,
329+ fields,
330+ } )
331+ ) ,
332+ "{foo:123,bar:321}"
333+ ) ;
334+ }
335+ }
0 commit comments