Skip to content

Commit 7f5b7f2

Browse files
Add: std duration support
1 parent c1f3257 commit 7f5b7f2

File tree

6 files changed

+538
-8
lines changed

6 files changed

+538
-8
lines changed

.taplo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
include = ["**/*.toml"]
2+
3+
[[rule]]
4+
include = ["**/Cargo.toml", ".taplo.toml", "Cargo.toml"]
5+
6+
[rule.formatting]
7+
align_entries = true
8+
align_comments = true
9+
array_trailing_comma = true
10+
column_width = 100
11+
inline_table_expand = false

src/builders/create.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::{
22
enums::ReturnClause,
33
internal_macros::push_clause,
4+
traits::IntoTimeout,
45
types::create::{ContentMode, CreateData, SetField},
56
};
67
use std::fmt::Write;
@@ -126,11 +127,32 @@ impl CreateBuilder {
126127
self
127128
}
128129

129-
/// Sets the TIMEOUT clause with a raw SurrealQL duration string.
130+
/// Sets the TIMEOUT clause.
130131
///
131-
/// Accepts SurrealQL duration syntax such as `"500ms"`, `"2s"`, `"1m"`.
132-
pub fn timeout(mut self, duration: &str) -> Self {
133-
self.data.timeout = Some(duration.to_string());
132+
/// Accepts either a raw SurrealQL duration string (e.g. `"500ms"`, `"2s"`, `"1m"`)
133+
/// or a [`std::time::Duration`], which is automatically converted to SurrealQL syntax.
134+
///
135+
/// # Examples
136+
/// ```
137+
/// # use surrealex::QueryBuilder;
138+
/// use std::time::Duration;
139+
///
140+
/// // Raw string
141+
/// let sql = QueryBuilder::create("person")
142+
/// .content("{ name: 'Tobie' }")
143+
/// .timeout("2s")
144+
/// .build();
145+
/// assert_eq!(sql, "CREATE person CONTENT { name: 'Tobie' } TIMEOUT 2s");
146+
///
147+
/// // std::time::Duration
148+
/// let sql = QueryBuilder::create("person")
149+
/// .content("{ name: 'Tobie' }")
150+
/// .timeout(Duration::from_secs(2))
151+
/// .build();
152+
/// assert_eq!(sql, "CREATE person CONTENT { name: 'Tobie' } TIMEOUT 2s");
153+
/// ```
154+
pub fn timeout(mut self, duration: impl IntoTimeout) -> Self {
155+
self.data.timeout = Some(duration.into_timeout());
134156
self
135157
}
136158

src/builders/delete.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::{
22
enums::{Condition, ExplainClause, ReturnClause},
33
internal_macros::push_clause,
4+
traits::IntoTimeout,
45
types::delete::DeleteData,
56
};
67
use std::fmt::Write;
@@ -66,11 +67,30 @@ impl DeleteBuilder {
6667
self
6768
}
6869

69-
/// Sets the TIMEOUT clause with a raw SurrealQL duration string.
70+
/// Sets the TIMEOUT clause.
7071
///
71-
/// Accepts SurrealQL duration syntax such as `"500ms"`, `"2s"`, `"1m"`.
72-
pub fn timeout(mut self, duration: &str) -> Self {
73-
self.data.timeout = Some(duration.to_string());
72+
/// Accepts either a raw SurrealQL duration string (e.g. `"500ms"`, `"2s"`, `"1m"`)
73+
/// or a [`std::time::Duration`], which is automatically converted to SurrealQL syntax.
74+
///
75+
/// # Examples
76+
/// ```
77+
/// # use surrealex::QueryBuilder;
78+
/// use std::time::Duration;
79+
///
80+
/// // Raw string
81+
/// let sql = QueryBuilder::delete("users")
82+
/// .timeout("2s")
83+
/// .build();
84+
/// assert_eq!(sql, "DELETE FROM users TIMEOUT 2s");
85+
///
86+
/// // std::time::Duration
87+
/// let sql = QueryBuilder::delete("users")
88+
/// .timeout(Duration::from_secs(2))
89+
/// .build();
90+
/// assert_eq!(sql, "DELETE FROM users TIMEOUT 2s");
91+
/// ```
92+
pub fn timeout(mut self, duration: impl IntoTimeout) -> Self {
93+
self.data.timeout = Some(duration.into_timeout());
7494
self
7595
}
7696

src/traits.rs

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::time::Duration;
2+
13
use crate::types::select::SelectField;
24

35
pub 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

Comments
 (0)