toasty_driver_integration_suite/tests/
jiff.rs

1use crate::prelude::*;
2
3use toasty_core::{
4    driver::Operation,
5    stmt::{ExprSet, InsertTarget, Statement, Value},
6};
7
8#[driver_test(id(ID))]
9pub async fn ty_timestamp(test: &mut Test) -> Result<(), BoxError> {
10    use jiff::Timestamp;
11
12    #[derive(Debug, toasty::Model)]
13    #[allow(dead_code)]
14    struct Item {
15        #[key]
16        #[auto]
17        id: ID,
18        val: Timestamp,
19    }
20
21    let mut db = test.setup_db(models!(Item)).await;
22
23    let ts = Timestamp::from_second(946684800)?; // 2000-01-01T00:00:00Z
24
25    test.log().clear();
26
27    let created = Item::create().val(ts).exec(&mut db).await?;
28
29    // Verify the INSERT encodes the timestamp correctly for the driver.
30    // Native timestamp drivers send the value as-is; non-native drivers
31    // (SQLite, DynamoDB) encode it as a fixed-precision ISO 8601 text string.
32    let (op, _) = test.log().pop();
33
34    let expected_val = if test.capability().native_timestamp {
35        Value::Timestamp(ts)
36    } else {
37        Value::String(format!("{ts:.9}"))
38    };
39
40    assert_struct!(op, Operation::QuerySql(_ {
41        stmt: Statement::Insert(_ {
42            target: InsertTarget::Table(_ {
43                table: == table_id(&db, "items"),
44                columns: == columns(&db, "items", &["id", "val"]),
45                ..
46            }),
47            source.body: ExprSet::Values(_ {
48                rows: [=~ (Any, expected_val)],
49                ..
50            }),
51            ..
52        }),
53        ..
54    }));
55
56    // Verify round-trip with more values
57    let read = Item::get_by_id(&mut db, &created.id).await?;
58    assert_eq!(read.val, ts);
59
60    let more_values = vec![
61        Timestamp::from_second(1609459200)?, // 2021-01-01T00:00:00Z
62        Timestamp::from_second(1735689600)?, // 2025-01-01T00:00:00Z
63    ];
64    for val in &more_values {
65        let created = Item::create().val(*val).exec(&mut db).await?;
66        let read = Item::get_by_id(&mut db, &created.id).await?;
67        assert_eq!(read.val, *val, "Round-trip failed for: {}", val);
68    }
69    Ok(())
70}
71
72#[driver_test(id(ID))]
73pub async fn ty_zoned(test: &mut Test) -> Result<(), BoxError> {
74    use jiff::Zoned;
75
76    #[derive(Debug, toasty::Model)]
77    #[allow(dead_code)]
78    struct Item {
79        #[key]
80        #[auto]
81        id: ID,
82        val: Zoned,
83    }
84
85    let mut db = test.setup_db(models!(Item)).await;
86
87    let test_values = vec![
88        "2000-01-01T00:00:00+00:00[UTC]".parse::<Zoned>()?,
89        "2021-06-15T14:30:00-04:00[America/New_York]".parse::<Zoned>()?,
90        "2025-12-31T23:59:59+09:00[Asia/Tokyo]".parse::<Zoned>()?,
91        "1970-01-01T00:00:00+00:00[UTC]".parse::<Zoned>()?,
92        "2024-11-03T01:30:00-04:00[America/New_York]".parse::<Zoned>()?, // Before DST fall-back
93    ];
94
95    for val in &test_values {
96        let created = Item::create().val(val.clone()).exec(&mut db).await?;
97        let read = Item::get_by_id(&mut db, &created.id).await?;
98        assert_eq!(read.val, *val, "Round-trip failed for: {}", val);
99    }
100    Ok(())
101}
102
103#[driver_test(id(ID))]
104pub async fn ty_date(test: &mut Test) -> Result<()> {
105    use jiff::civil::Date;
106
107    #[derive(Debug, toasty::Model)]
108    #[allow(dead_code)]
109    struct Item {
110        #[key]
111        #[auto]
112        id: ID,
113        val: Date,
114    }
115
116    let mut db = test.setup_db(models!(Item)).await;
117
118    let test_values = vec![
119        Date::constant(2000, 1, 1),
120        Date::constant(2021, 6, 15),
121        Date::constant(2025, 12, 31),
122        Date::constant(1970, 1, 1),
123        Date::constant(1900, 2, 28),
124        Date::constant(2024, 2, 29), // Leap year
125        Date::constant(9999, 12, 31),
126        Date::constant(1, 1, 1),
127    ];
128
129    for val in &test_values {
130        let created = Item::create().val(*val).exec(&mut db).await?;
131        let read = Item::get_by_id(&mut db, &created.id).await?;
132        assert_eq!(read.val, *val, "Round-trip failed for: {}", val);
133    }
134    Ok(())
135}
136
137#[driver_test(id(ID))]
138pub async fn ty_time(test: &mut Test) -> Result<()> {
139    use jiff::civil::Time;
140
141    #[derive(Debug, toasty::Model)]
142    #[allow(dead_code)]
143    struct Item {
144        #[key]
145        #[auto]
146        id: ID,
147        val: Time,
148    }
149
150    let mut db = test.setup_db(models!(Item)).await;
151
152    let test_values = vec![
153        Time::constant(0, 0, 0, 0),
154        Time::constant(12, 0, 0, 0),
155        Time::constant(23, 59, 59, 999_999_000), // Microsecond precision
156        Time::constant(9, 30, 15, 0),
157        Time::constant(14, 45, 30, 500_000_000),
158        Time::constant(6, 0, 0, 123_456_000), // Microsecond precision
159    ];
160
161    for val in &test_values {
162        let created = Item::create().val(*val).exec(&mut db).await?;
163        let read = Item::get_by_id(&mut db, &created.id).await?;
164        assert_eq!(read.val, *val, "Round-trip failed for: {}", val);
165    }
166    Ok(())
167}
168
169#[driver_test(id(ID))]
170pub async fn ty_datetime(test: &mut Test) -> Result<()> {
171    use jiff::civil::DateTime;
172
173    #[derive(Debug, toasty::Model)]
174    #[allow(dead_code)]
175    struct Item {
176        #[key]
177        #[auto]
178        id: ID,
179        val: DateTime,
180    }
181
182    let mut db = test.setup_db(models!(Item)).await;
183
184    let test_values = vec![
185        DateTime::constant(2000, 1, 1, 0, 0, 0, 0),
186        DateTime::constant(2021, 6, 15, 14, 30, 0, 0),
187        DateTime::constant(2025, 12, 31, 23, 59, 59, 999_999_000), // Microsecond precision
188        DateTime::constant(1970, 1, 1, 0, 0, 0, 0),
189        DateTime::constant(1900, 2, 28, 12, 0, 0, 0),
190        DateTime::constant(2024, 2, 29, 6, 30, 15, 123_456_000), // Leap year - Microsecond precision
191        DateTime::constant(2099, 12, 31, 23, 59, 59, 0),
192        DateTime::constant(1901, 1, 1, 0, 0, 0, 0),
193    ];
194
195    for val in &test_values {
196        let created = Item::create().val(*val).exec(&mut db).await?;
197        let read = Item::get_by_id(&mut db, &created.id).await?;
198        assert_eq!(read.val, *val, "Round-trip failed for: {}", val);
199    }
200    Ok(())
201}
202
203#[driver_test(id(ID), requires(native_timestamp))]
204pub async fn ty_timestamp_precision_2(test: &mut Test) -> Result<(), BoxError> {
205    use jiff::Timestamp;
206
207    #[derive(Debug, toasty::Model)]
208    #[allow(dead_code)]
209    struct Item {
210        #[key]
211        #[auto]
212        id: ID,
213        #[column(type = timestamp(2))]
214        val: Timestamp,
215    }
216
217    let mut db = test.setup_db(models!(Item)).await;
218
219    // Test value with nanosecond precision
220    let original = Timestamp::from_second(946684800)?
221        .checked_add(jiff::Span::new().nanoseconds(123_456_789))?;
222
223    // Expected value truncated to 2 decimal places (centiseconds = 10ms precision)
224    // 123_456_789 ns -> 120_000_000 ns (truncated to centiseconds)
225    let expected = Timestamp::from_second(946684800)?
226        .checked_add(jiff::Span::new().nanoseconds(120_000_000))?;
227
228    let created = Item::create().val(original).exec(&mut db).await?;
229    let read = Item::get_by_id(&mut db, &created.id).await?;
230
231    assert_eq!(
232        read.val, expected,
233        "Precision truncation failed: original={}, read={}, expected={}",
234        original, read.val, expected
235    );
236    Ok(())
237}
238
239#[driver_test(id(ID), requires(native_time))]
240pub async fn ty_time_precision_2(test: &mut Test) -> Result<()> {
241    use jiff::civil::Time;
242
243    #[derive(Debug, toasty::Model)]
244    #[allow(dead_code)]
245    struct Item {
246        #[key]
247        #[auto]
248        id: ID,
249        #[column(type = time(2))]
250        val: Time,
251    }
252
253    let mut db = test.setup_db(models!(Item)).await;
254
255    // Test value with nanosecond precision
256    let original = Time::constant(14, 30, 45, 123_456_789);
257
258    // Expected value truncated to 2 decimal places (centiseconds = 10ms precision)
259    // 123_456_789 ns -> 120_000_000 ns
260    let expected = Time::constant(14, 30, 45, 120_000_000);
261
262    let created = Item::create().val(original).exec(&mut db).await?;
263    let read = Item::get_by_id(&mut db, &created.id).await?;
264
265    assert_eq!(
266        read.val, expected,
267        "Precision truncation failed: original={}, read={}, expected={}",
268        original, read.val, expected
269    );
270    Ok(())
271}
272
273#[driver_test(id(ID), requires(native_datetime))]
274pub async fn ty_datetime_precision_2(test: &mut Test) -> Result<()> {
275    use jiff::civil::DateTime;
276
277    #[derive(Debug, toasty::Model)]
278    #[allow(dead_code)]
279    struct Item {
280        #[key]
281        #[auto]
282        id: ID,
283        #[column(type = datetime(2))]
284        val: DateTime,
285    }
286
287    let mut db = test.setup_db(models!(Item)).await;
288
289    // Test value with nanosecond precision
290    let original = DateTime::constant(2024, 6, 15, 14, 30, 45, 123_456_789);
291
292    // Expected value truncated to 2 decimal places (centiseconds = 10ms precision)
293    // 123_456_789 ns -> 120_000_000 ns
294    let expected = DateTime::constant(2024, 6, 15, 14, 30, 45, 120_000_000);
295
296    let created = Item::create().val(original).exec(&mut db).await?;
297    let read = Item::get_by_id(&mut db, &created.id).await?;
298
299    assert_eq!(
300        read.val, expected,
301        "Precision truncation failed: original={}, read={}, expected={}",
302        original, read.val, expected
303    );
304    Ok(())
305}
306
307#[driver_test(id(ID))]
308pub async fn ty_timestamp_as_text(test: &mut Test) -> Result<(), BoxError> {
309    use jiff::Timestamp;
310
311    #[derive(Debug, toasty::Model)]
312    #[allow(dead_code)]
313    struct Item {
314        #[key]
315        #[auto]
316        id: ID,
317        #[column(type = text)]
318        val: Timestamp,
319    }
320
321    let mut db = test.setup_db(models!(Item)).await;
322
323    let ts = Timestamp::from_second(946684800)?; // 2000-01-01T00:00:00Z
324    let ts_text = format!("{ts:.9}");
325
326    test.log().clear();
327
328    let created = Item::create().val(ts).exec(&mut db).await?;
329
330    // Verify the INSERT encodes the timestamp as a fixed-precision text string.
331    // The #[column(type = text)] forces text encoding on all drivers.
332    let (op, _) = test.log().pop();
333    assert_struct!(op, Operation::QuerySql(_ {
334        stmt: Statement::Insert(_ {
335            target: InsertTarget::Table(_ {
336                table: == table_id(&db, "items"),
337                columns: == columns(&db, "items", &["id", "val"]),
338                ..
339            }),
340            source.body: ExprSet::Values(_ {
341                rows: [=~ (Any, ts_text)],
342                ..
343            }),
344            ..
345        }),
346        ..
347    }));
348
349    // Verify round-trip
350    let read = Item::get_by_id(&mut db, &created.id).await?;
351    assert_eq!(read.val, ts);
352
353    Ok(())
354}
355
356#[driver_test(id(ID))]
357pub async fn ty_date_as_text(test: &mut Test) -> Result<()> {
358    use jiff::civil::Date;
359
360    #[derive(Debug, toasty::Model)]
361    #[allow(dead_code)]
362    struct Item {
363        #[key]
364        #[auto]
365        id: ID,
366        #[column(type = text)]
367        val: Date,
368    }
369
370    let mut db = test.setup_db(models!(Item)).await;
371
372    let test_values = vec![
373        Date::constant(2000, 1, 1),
374        Date::constant(2021, 6, 15),
375        Date::constant(2025, 12, 31),
376    ];
377
378    for val in &test_values {
379        let created = Item::create().val(*val).exec(&mut db).await?;
380        let read = Item::get_by_id(&mut db, &created.id).await?;
381        assert_eq!(read.val, *val, "Round-trip failed for: {}", val);
382    }
383    Ok(())
384}
385
386#[driver_test(id(ID))]
387pub async fn ty_time_as_text(test: &mut Test) -> Result<()> {
388    use jiff::civil::Time;
389
390    #[derive(Debug, toasty::Model)]
391    #[allow(dead_code)]
392    struct Item {
393        #[key]
394        #[auto]
395        id: ID,
396        #[column(type = text)]
397        val: Time,
398    }
399
400    let mut db = test.setup_db(models!(Item)).await;
401
402    let test_values = vec![
403        Time::constant(0, 0, 0, 0),
404        Time::constant(12, 0, 0, 0),
405        Time::constant(23, 59, 59, 999_999_000), // Microsecond precision
406        Time::constant(9, 30, 15, 0),
407    ];
408
409    for val in &test_values {
410        let created = Item::create().val(*val).exec(&mut db).await?;
411        let read = Item::get_by_id(&mut db, &created.id).await?;
412        assert_eq!(read.val, *val, "Round-trip failed for: {}", val);
413    }
414    Ok(())
415}
416
417#[driver_test(id(ID))]
418pub async fn ty_datetime_as_text(test: &mut Test) -> Result<()> {
419    use jiff::civil::DateTime;
420
421    #[derive(Debug, toasty::Model)]
422    #[allow(dead_code)]
423    struct Item {
424        #[key]
425        #[auto]
426        id: ID,
427        #[column(type = text)]
428        val: DateTime,
429    }
430
431    let mut db = test.setup_db(models!(Item)).await;
432
433    let test_values = vec![
434        DateTime::constant(2000, 1, 1, 0, 0, 0, 0),
435        DateTime::constant(2021, 6, 15, 14, 30, 0, 0),
436        DateTime::constant(2025, 12, 31, 23, 59, 59, 999_999_000), // Microsecond precision
437    ];
438
439    for val in &test_values {
440        let created = Item::create().val(*val).exec(&mut db).await?;
441        let read = Item::get_by_id(&mut db, &created.id).await?;
442        assert_eq!(read.val, *val, "Round-trip failed for: {}", val);
443    }
444    Ok(())
445}
446
447#[driver_test(id(ID), requires(sql))]
448pub async fn order_by_timestamp(test: &mut Test) -> Result<(), BoxError> {
449    use jiff::Timestamp;
450
451    #[derive(Debug, toasty::Model)]
452    #[allow(dead_code)]
453    struct Item {
454        #[key]
455        #[auto]
456        id: ID,
457
458        #[column(type = text)]
459        val: Timestamp,
460    }
461
462    let mut db = test.setup_db(models!(Item)).await;
463
464    let timestamps = vec![
465        Timestamp::from_second(1609459200)?, // 2021-01-01
466        Timestamp::from_second(946684800)?,  // 2000-01-01
467        Timestamp::from_second(1735689600)?, // 2025-01-01
468    ];
469
470    for val in &timestamps {
471        Item::create().val(*val).exec(&mut db).await?;
472    }
473
474    let asc: Vec<_> = Item::all()
475        .order_by(Item::fields().val().asc())
476        .exec(&mut db)
477        .await?;
478
479    assert_eq!(asc.len(), 3);
480    assert!(asc[0].val < asc[1].val);
481    assert!(asc[1].val < asc[2].val);
482
483    let desc: Vec<_> = Item::all()
484        .order_by(Item::fields().val().desc())
485        .exec(&mut db)
486        .await?;
487
488    assert_eq!(desc.len(), 3);
489    assert!(desc[0].val > desc[1].val);
490    assert!(desc[1].val > desc[2].val);
491
492    Ok(())
493}
494
495#[driver_test(id(ID))]
496pub async fn filter_by_timestamp(test: &mut Test) -> Result<(), BoxError> {
497    use jiff::Timestamp;
498
499    #[derive(Debug, toasty::Model)]
500    #[allow(dead_code)]
501    struct Event {
502        #[key]
503        #[auto]
504        id: ID,
505        #[index]
506        at: Timestamp,
507        name: String,
508    }
509
510    let mut db = test.setup_db(models!(Event)).await;
511
512    let ts1 = Timestamp::from_second(946684800)?; // 2000-01-01T00:00:00Z
513    let ts2 = Timestamp::from_second(1609459200)?; // 2021-01-01T00:00:00Z
514    let ts3 = Timestamp::from_second(1735689600)?; // 2025-01-01T00:00:00Z
515
516    Event::create().at(ts1).name("a").exec(&mut db).await?;
517    Event::create().at(ts2).name("b").exec(&mut db).await?;
518    Event::create().at(ts3).name("c").exec(&mut db).await?;
519
520    let results = Event::filter_by_at(ts2).exec(&mut db).await?;
521    assert_struct!(results, [{ name: "b", at: == ts2 }]);
522
523    // No match
524    let results = Event::filter_by_at(Timestamp::from_second(0)?)
525        .exec(&mut db)
526        .await?;
527    assert!(results.is_empty());
528
529    Ok(())
530}