Skip to main content

reifydb_core/encoded/
duration.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Copyright (c) 2026 ReifyDB
3
4use std::ptr;
5
6use reifydb_value::value::{duration::Duration, value_type::ValueType};
7
8use crate::encoded::{row::EncodedRow, shape::RowShape};
9
10impl RowShape {
11	pub fn set_duration(&self, row: &mut EncodedRow, index: usize, value: Duration) {
12		let field = &self.fields()[index];
13		debug_assert!(row.len() >= self.total_static_size());
14		debug_assert_eq!(*field.constraint.get_type().inner_type(), ValueType::Duration);
15		row.set_valid(index, true);
16
17		let months = value.get_months();
18		let days = value.get_days();
19		let nanos = value.get_nanos();
20		unsafe {
21			ptr::write_unaligned(
22				row.make_mut().as_mut_ptr().add(field.offset as usize) as *mut i32,
23				months,
24			);
25
26			ptr::write_unaligned(
27				row.make_mut().as_mut_ptr().add(field.offset as usize + 4) as *mut i32,
28				days,
29			);
30
31			ptr::write_unaligned(
32				row.make_mut().as_mut_ptr().add(field.offset as usize + 8) as *mut i64,
33				nanos,
34			);
35		}
36	}
37
38	pub fn get_duration(&self, row: &EncodedRow, index: usize) -> Duration {
39		let field = &self.fields()[index];
40		debug_assert!(row.len() >= self.total_static_size());
41		debug_assert_eq!(*field.constraint.get_type().inner_type(), ValueType::Duration);
42		unsafe {
43			let months = (row.as_ptr().add(field.offset as usize) as *const i32).read_unaligned();
44
45			let days = (row.as_ptr().add(field.offset as usize + 4) as *const i32).read_unaligned();
46
47			let nanos = (row.as_ptr().add(field.offset as usize + 8) as *const i64).read_unaligned();
48			Duration::new(months, days, nanos).expect("stored duration must be valid")
49		}
50	}
51
52	pub fn try_get_duration(&self, row: &EncodedRow, index: usize) -> Option<Duration> {
53		if row.is_defined(index) && self.fields()[index].constraint.get_type() == ValueType::Duration {
54			Some(self.get_duration(row, index))
55		} else {
56			None
57		}
58	}
59}
60
61#[cfg(test)]
62pub mod tests {
63	use reifydb_value::value::{duration::Duration, value_type::ValueType};
64
65	use crate::encoded::shape::RowShape;
66
67	#[test]
68	fn test_set_get_duration() {
69		let shape = RowShape::testing(&[ValueType::Duration]);
70		let mut row = shape.allocate();
71
72		let value = Duration::from_seconds(-7200).unwrap();
73		shape.set_duration(&mut row, 0, value.clone());
74		assert_eq!(shape.get_duration(&row, 0), value);
75	}
76
77	#[test]
78	fn test_try_get_duration() {
79		let shape = RowShape::testing(&[ValueType::Duration]);
80		let mut row = shape.allocate();
81
82		assert_eq!(shape.try_get_duration(&row, 0), None);
83
84		let test_duration = Duration::from_days(30).unwrap();
85		shape.set_duration(&mut row, 0, test_duration.clone());
86		assert_eq!(shape.try_get_duration(&row, 0), Some(test_duration));
87	}
88
89	#[test]
90	fn test_zero() {
91		let shape = RowShape::testing(&[ValueType::Duration]);
92		let mut row = shape.allocate();
93
94		let zero = Duration::default(); // Zero duration
95		shape.set_duration(&mut row, 0, zero.clone());
96		assert_eq!(shape.get_duration(&row, 0), zero);
97	}
98
99	#[test]
100	fn test_various_durations() {
101		let shape = RowShape::testing(&[ValueType::Duration]);
102
103		let test_durations = [
104			Duration::from_seconds(0).unwrap(),     // Zero
105			Duration::from_seconds(60).unwrap(),    // 1 minute
106			Duration::from_seconds(3600).unwrap(),  // 1 hour
107			Duration::from_seconds(86400).unwrap(), // 1 day
108			Duration::from_days(7).unwrap(),        // 1 week
109			Duration::from_days(30).unwrap(),       // ~1 month
110			Duration::from_weeks(52).unwrap(),      // ~1 year
111		];
112
113		for duration in test_durations {
114			let mut row = shape.allocate();
115			shape.set_duration(&mut row, 0, duration.clone());
116			assert_eq!(shape.get_duration(&row, 0), duration);
117		}
118	}
119
120	#[test]
121	fn test_negative_durations() {
122		let shape = RowShape::testing(&[ValueType::Duration]);
123
124		let negative_durations = [
125			Duration::from_seconds(-60).unwrap(),    // -1 minute
126			Duration::from_seconds(-3600).unwrap(),  // -1 hour
127			Duration::from_seconds(-86400).unwrap(), // -1 day
128			Duration::from_days(-7).unwrap(),        // -1 week
129			Duration::from_weeks(-4).unwrap(),       // -1 month
130		];
131
132		for duration in negative_durations {
133			let mut row = shape.allocate();
134			shape.set_duration(&mut row, 0, duration.clone());
135			assert_eq!(shape.get_duration(&row, 0), duration);
136		}
137	}
138
139	#[test]
140	fn test_complex_parts() {
141		let shape = RowShape::testing(&[ValueType::Duration]);
142		let mut row = shape.allocate();
143
144		// Create a duration with all components
145		let complex_duration = Duration::new(
146			6,         // 6 months
147			15,        // 15 days
148			123456789, // nanoseconds
149		)
150		.unwrap();
151		shape.set_duration(&mut row, 0, complex_duration.clone());
152		assert_eq!(shape.get_duration(&row, 0), complex_duration);
153	}
154
155	#[test]
156	fn test_mixed_with_other_types() {
157		let shape = RowShape::testing(&[
158			ValueType::Duration,
159			ValueType::Boolean,
160			ValueType::Duration,
161			ValueType::Int8,
162		]);
163		let mut row = shape.allocate();
164
165		let duration1 = Duration::from_hours(24).unwrap();
166		let duration2 = Duration::from_minutes(-30).unwrap();
167
168		shape.set_duration(&mut row, 0, duration1.clone());
169		shape.set_bool(&mut row, 1, true);
170		shape.set_duration(&mut row, 2, duration2.clone());
171		shape.set_i64(&mut row, 3, 987654321);
172
173		assert_eq!(shape.get_duration(&row, 0), duration1);
174		assert_eq!(shape.get_bool(&row, 1), true);
175		assert_eq!(shape.get_duration(&row, 2), duration2);
176		assert_eq!(shape.get_i64(&row, 3), 987654321);
177	}
178
179	#[test]
180	fn test_undefined_handling() {
181		let shape = RowShape::testing(&[ValueType::Duration, ValueType::Duration]);
182		let mut row = shape.allocate();
183
184		let duration = Duration::from_days(100).unwrap();
185		shape.set_duration(&mut row, 0, duration.clone());
186
187		assert_eq!(shape.try_get_duration(&row, 0), Some(duration));
188		assert_eq!(shape.try_get_duration(&row, 1), None);
189
190		shape.set_none(&mut row, 0);
191		assert_eq!(shape.try_get_duration(&row, 0), None);
192	}
193
194	#[test]
195	fn test_large_values() {
196		let shape = RowShape::testing(&[ValueType::Duration]);
197		let mut row = shape.allocate();
198
199		// Test with large values
200		let large_duration = Duration::new(
201			120,             // 10 years in months
202			3650,            // ~10 years in days
203			123456789012345, // Large nanosecond value
204		)
205		.unwrap();
206		shape.set_duration(&mut row, 0, large_duration.clone());
207		assert_eq!(shape.get_duration(&row, 0), large_duration);
208	}
209
210	#[test]
211	fn test_precision_preservation() {
212		let shape = RowShape::testing(&[ValueType::Duration]);
213		let mut row = shape.allocate();
214
215		// Test that all components are preserved exactly
216		let precise_duration = Duration::new(
217			5,         // 5 months
218			20,        // 20 days
219			999999999, // 999,999,999 nanoseconds
220		)
221		.unwrap();
222		shape.set_duration(&mut row, 0, precise_duration.clone());
223
224		let retrieved = shape.get_duration(&row, 0);
225		assert_eq!(retrieved, precise_duration);
226
227		let orig_months = precise_duration.get_months();
228		let orig_days = precise_duration.get_days();
229		let orig_nanos = precise_duration.get_nanos();
230		let ret_months = retrieved.get_months();
231		let ret_days = retrieved.get_days();
232		let ret_nanos = retrieved.get_nanos();
233		assert_eq!(orig_months, ret_months);
234		assert_eq!(orig_days, ret_days);
235		assert_eq!(orig_nanos, ret_nanos);
236	}
237
238	#[test]
239	fn test_common_durations() {
240		let shape = RowShape::testing(&[ValueType::Duration]);
241
242		// Test common durations used in applications
243		let common_durations = [
244			Duration::from_seconds(1).unwrap(),  // 1 second
245			Duration::from_seconds(30).unwrap(), // 30 seconds
246			Duration::from_minutes(5).unwrap(),  // 5 minutes
247			Duration::from_minutes(15).unwrap(), // 15 minutes
248			Duration::from_hours(1).unwrap(),    // 1 hour
249			Duration::from_hours(8).unwrap(),    // Work day
250			Duration::from_days(1).unwrap(),     // 1 day
251			Duration::from_weeks(1).unwrap(),    // 1 week
252			Duration::from_weeks(2).unwrap(),    // 2 weeks
253		];
254
255		for duration in common_durations {
256			let mut row = shape.allocate();
257			shape.set_duration(&mut row, 0, duration.clone());
258			assert_eq!(shape.get_duration(&row, 0), duration);
259		}
260	}
261
262	#[test]
263	fn test_boundary_values() {
264		let shape = RowShape::testing(&[ValueType::Duration]);
265
266		// Test boundary values for each component
267		let boundary_durations = [
268			Duration::new(i32::MAX, 0, 0).unwrap(), // Max months
269			Duration::new(i32::MIN, 0, 0).unwrap(), // Min months
270			Duration::new(0, i32::MAX, 0).unwrap(), // Max days
271			Duration::new(0, i32::MIN, 0).unwrap(), // Min days
272			Duration::new(0, 0, i64::MAX).unwrap(), // Max nanoseconds
273			Duration::new(0, 0, i64::MIN).unwrap(), // Min nanoseconds
274		];
275
276		for duration in boundary_durations {
277			let mut row = shape.allocate();
278			shape.set_duration(&mut row, 0, duration.clone());
279			assert_eq!(shape.get_duration(&row, 0), duration);
280		}
281	}
282
283	#[test]
284	fn test_try_get_duration_wrong_type() {
285		let shape = RowShape::testing(&[ValueType::Boolean]);
286		let mut row = shape.allocate();
287
288		shape.set_bool(&mut row, 0, true);
289
290		assert_eq!(shape.try_get_duration(&row, 0), None);
291	}
292}