1+ use std:: time:: Duration ;
2+
13use crate :: types:: select:: SelectField ;
24
35pub trait ToSelectField {
@@ -21,3 +23,224 @@ impl ToSelectField for (&str, &str) {
2123 }
2224 }
2325}
26+
27+ /// Trait for values that can be used as a SurrealQL `TIMEOUT` duration.
28+ ///
29+ /// Implemented for:
30+ /// - `&str` — passed through as a raw SurrealQL duration string (e.g. `"500ms"`, `"2s"`, `"1m"`).
31+ /// - `String` — same as `&str` but owned.
32+ /// - [`std::time::Duration`] — automatically converted to a compound SurrealQL duration string.
33+ ///
34+ /// # SurrealQL duration units
35+ ///
36+ /// | Suffix | Unit |
37+ /// |--------|--------------|
38+ /// | `ns` | Nanoseconds |
39+ /// | `us` | Microseconds |
40+ /// | `ms` | Milliseconds |
41+ /// | `s` | Seconds |
42+ /// | `m` | Minutes |
43+ /// | `h` | Hours |
44+ /// | `d` | Days |
45+ /// | `w` | Weeks |
46+ /// | `y` | Years |
47+ ///
48+ /// # Examples
49+ ///
50+ /// ```
51+ /// # use surrealex::QueryBuilder;
52+ /// use std::time::Duration;
53+ ///
54+ /// // Using a raw string
55+ /// let sql = QueryBuilder::create("person")
56+ /// .content("{ name: 'Tobie' }")
57+ /// .timeout("2s")
58+ /// .build();
59+ /// assert_eq!(sql, "CREATE person CONTENT { name: 'Tobie' } TIMEOUT 2s");
60+ ///
61+ /// // Using std::time::Duration
62+ /// let sql = QueryBuilder::create("person")
63+ /// .content("{ name: 'Tobie' }")
64+ /// .timeout(Duration::from_secs(2))
65+ /// .build();
66+ /// assert_eq!(sql, "CREATE person CONTENT { name: 'Tobie' } TIMEOUT 2s");
67+ /// ```
68+ pub trait IntoTimeout {
69+ /// Convert this value into a SurrealQL duration string.
70+ fn into_timeout ( self ) -> String ;
71+ }
72+
73+ impl IntoTimeout for & str {
74+ fn into_timeout ( self ) -> String {
75+ self . to_string ( )
76+ }
77+ }
78+
79+ impl IntoTimeout for String {
80+ fn into_timeout ( self ) -> String {
81+ self
82+ }
83+ }
84+
85+ impl IntoTimeout for Duration {
86+ fn into_timeout ( self ) -> String {
87+ duration_to_string ( self )
88+ }
89+ }
90+
91+ const UNITS : [ ( u128 , & str ) ; 9 ] = [
92+ ( 365 * 86_400 * 1_000_000_000 , "y" ) ,
93+ ( 7 * 86_400 * 1_000_000_000 , "w" ) ,
94+ ( 86_400 * 1_000_000_000 , "d" ) ,
95+ ( 3_600 * 1_000_000_000 , "h" ) ,
96+ ( 60 * 1_000_000_000 , "m" ) ,
97+ ( 1_000_000_000 , "s" ) ,
98+ ( 1_000_000 , "ms" ) ,
99+ ( 1_000 , "us" ) ,
100+ ( 1 , "ns" ) ,
101+ ] ;
102+
103+ /// Converts a [`std::time::Duration`] into a compound SurrealQL duration string.
104+ ///
105+ /// Decomposes from largest to smallest unit, emitting only non-zero components.
106+ /// For example, `Duration::from_secs(90)` becomes `"1m30s"`.
107+ fn duration_to_string ( duration : Duration ) -> String {
108+ let mut nanos = duration. as_nanos ( ) ;
109+
110+ if nanos == 0 {
111+ return "0ns" . to_string ( ) ;
112+ }
113+
114+ let mut result = String :: new ( ) ;
115+
116+ for ( unit_nanos, suffix) in UNITS {
117+ if nanos >= unit_nanos {
118+ result. push_str ( & ( nanos / unit_nanos) . to_string ( ) ) ;
119+ result. push_str ( suffix) ;
120+ nanos %= unit_nanos;
121+ }
122+ }
123+
124+ result
125+ }
126+
127+ #[ cfg( test) ]
128+ mod tests {
129+ use super :: * ;
130+
131+ #[ test]
132+ fn zero_duration ( ) {
133+ assert_eq ! ( duration_to_string( Duration :: ZERO ) , "0ns" ) ;
134+ }
135+
136+ #[ test]
137+ fn pure_nanoseconds ( ) {
138+ assert_eq ! ( duration_to_string( Duration :: from_nanos( 42 ) ) , "42ns" ) ;
139+ }
140+
141+ #[ test]
142+ fn pure_microseconds ( ) {
143+ assert_eq ! ( duration_to_string( Duration :: from_micros( 100 ) ) , "100us" ) ;
144+ }
145+
146+ #[ test]
147+ fn pure_milliseconds ( ) {
148+ assert_eq ! ( duration_to_string( Duration :: from_millis( 500 ) ) , "500ms" ) ;
149+ }
150+
151+ #[ test]
152+ fn pure_seconds ( ) {
153+ assert_eq ! ( duration_to_string( Duration :: from_secs( 2 ) ) , "2s" ) ;
154+ }
155+
156+ #[ test]
157+ fn pure_minutes ( ) {
158+ assert_eq ! ( duration_to_string( Duration :: from_secs( 60 ) ) , "1m" ) ;
159+ }
160+
161+ #[ test]
162+ fn pure_hours ( ) {
163+ assert_eq ! ( duration_to_string( Duration :: from_secs( 3600 ) ) , "1h" ) ;
164+ }
165+
166+ #[ test]
167+ fn pure_days ( ) {
168+ assert_eq ! ( duration_to_string( Duration :: from_secs( 86_400 ) ) , "1d" ) ;
169+ }
170+
171+ #[ test]
172+ fn pure_weeks ( ) {
173+ assert_eq ! ( duration_to_string( Duration :: from_secs( 604_800 ) ) , "1w" ) ;
174+ }
175+
176+ #[ test]
177+ fn pure_years ( ) {
178+ assert_eq ! ( duration_to_string( Duration :: from_secs( 365 * 86_400 ) ) , "1y" ) ;
179+ }
180+
181+ #[ test]
182+ fn compound_minutes_and_seconds ( ) {
183+ // 90 seconds = 1m30s
184+ assert_eq ! ( duration_to_string( Duration :: from_secs( 90 ) ) , "1m30s" ) ;
185+ }
186+
187+ #[ test]
188+ fn compound_hours_minutes_seconds ( ) {
189+ // 3661 seconds = 1h1m1s
190+ assert_eq ! ( duration_to_string( Duration :: from_secs( 3661 ) ) , "1h1m1s" ) ;
191+ }
192+
193+ #[ test]
194+ fn compound_seconds_and_milliseconds ( ) {
195+ // 1500ms = 1s500ms
196+ assert_eq ! ( duration_to_string( Duration :: from_millis( 1500 ) ) , "1s500ms" ) ;
197+ }
198+
199+ #[ test]
200+ fn compound_milliseconds_and_microseconds ( ) {
201+ // 1_001 microseconds = 1ms1us
202+ assert_eq ! ( duration_to_string( Duration :: from_micros( 1_001 ) ) , "1ms1us" ) ;
203+ }
204+
205+ #[ test]
206+ fn compound_microseconds_and_nanoseconds ( ) {
207+ // 1_001 nanoseconds = 1us1ns
208+ assert_eq ! ( duration_to_string( Duration :: from_nanos( 1_001 ) ) , "1us1ns" ) ;
209+ }
210+
211+ #[ test]
212+ fn compound_days_hours_minutes ( ) {
213+ // 1 day + 2 hours + 30 minutes
214+ let secs = 86_400 + 7_200 + 1_800 ;
215+ assert_eq ! ( duration_to_string( Duration :: from_secs( secs) ) , "1d2h30m" ) ;
216+ }
217+
218+ #[ test]
219+ fn compound_weeks_and_days ( ) {
220+ // 10 days = 1w3d
221+ let secs = 10 * 86_400 ;
222+ assert_eq ! ( duration_to_string( Duration :: from_secs( secs) ) , "1w3d" ) ;
223+ }
224+
225+ #[ test]
226+ fn compound_years_weeks_days ( ) {
227+ // 1 year + 2 weeks + 3 days
228+ let secs = 365 * 86_400 + 2 * 604_800 + 3 * 86_400 ;
229+ assert_eq ! ( duration_to_string( Duration :: from_secs( secs) ) , "1y2w3d" ) ;
230+ }
231+
232+ #[ test]
233+ fn str_into_timeout ( ) {
234+ assert_eq ! ( "5s" . into_timeout( ) , "5s" ) ;
235+ }
236+
237+ #[ test]
238+ fn string_into_timeout ( ) {
239+ assert_eq ! ( String :: from( "10m" ) . into_timeout( ) , "10m" ) ;
240+ }
241+
242+ #[ test]
243+ fn duration_into_timeout ( ) {
244+ assert_eq ! ( Duration :: from_secs( 120 ) . into_timeout( ) , "2m" ) ;
245+ }
246+ }
0 commit comments