Skip to main content

reifydb_core/encoded/
duration.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::ptr;
5
6use reifydb_type::value::{duration::Duration, r#type::Type};
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(), Type::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(), Type::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() == Type::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_type::value::{duration::Duration, r#type::Type};
64
65	use crate::encoded::shape::RowShape;
66
67	#[test]
68	fn test_set_get_duration() {
69		let shape = RowShape::testing(&[Type::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(&[Type::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(&[Type::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(&[Type::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(&[Type::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(&[Type::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(&[Type::Duration, Type::Boolean, Type::Duration, Type::Int8]);
158		let mut row = shape.allocate();
159
160		let duration1 = Duration::from_hours(24).unwrap();
161		let duration2 = Duration::from_minutes(-30).unwrap();
162
163		shape.set_duration(&mut row, 0, duration1.clone());
164		shape.set_bool(&mut row, 1, true);
165		shape.set_duration(&mut row, 2, duration2.clone());
166		shape.set_i64(&mut row, 3, 987654321);
167
168		assert_eq!(shape.get_duration(&row, 0), duration1);
169		assert_eq!(shape.get_bool(&row, 1), true);
170		assert_eq!(shape.get_duration(&row, 2), duration2);
171		assert_eq!(shape.get_i64(&row, 3), 987654321);
172	}
173
174	#[test]
175	fn test_undefined_handling() {
176		let shape = RowShape::testing(&[Type::Duration, Type::Duration]);
177		let mut row = shape.allocate();
178
179		let duration = Duration::from_days(100).unwrap();
180		shape.set_duration(&mut row, 0, duration.clone());
181
182		assert_eq!(shape.try_get_duration(&row, 0), Some(duration));
183		assert_eq!(shape.try_get_duration(&row, 1), None);
184
185		shape.set_none(&mut row, 0);
186		assert_eq!(shape.try_get_duration(&row, 0), None);
187	}
188
189	#[test]
190	fn test_large_values() {
191		let shape = RowShape::testing(&[Type::Duration]);
192		let mut row = shape.allocate();
193
194		// Test with large values
195		let large_duration = Duration::new(
196			120,             // 10 years in months
197			3650,            // ~10 years in days
198			123456789012345, // Large nanosecond value
199		)
200		.unwrap();
201		shape.set_duration(&mut row, 0, large_duration.clone());
202		assert_eq!(shape.get_duration(&row, 0), large_duration);
203	}
204
205	#[test]
206	fn test_precision_preservation() {
207		let shape = RowShape::testing(&[Type::Duration]);
208		let mut row = shape.allocate();
209
210		// Test that all components are preserved exactly
211		let precise_duration = Duration::new(
212			5,         // 5 months
213			20,        // 20 days
214			999999999, // 999,999,999 nanoseconds
215		)
216		.unwrap();
217		shape.set_duration(&mut row, 0, precise_duration.clone());
218
219		let retrieved = shape.get_duration(&row, 0);
220		assert_eq!(retrieved, precise_duration);
221
222		let orig_months = precise_duration.get_months();
223		let orig_days = precise_duration.get_days();
224		let orig_nanos = precise_duration.get_nanos();
225		let ret_months = retrieved.get_months();
226		let ret_days = retrieved.get_days();
227		let ret_nanos = retrieved.get_nanos();
228		assert_eq!(orig_months, ret_months);
229		assert_eq!(orig_days, ret_days);
230		assert_eq!(orig_nanos, ret_nanos);
231	}
232
233	#[test]
234	fn test_common_durations() {
235		let shape = RowShape::testing(&[Type::Duration]);
236
237		// Test common durations used in applications
238		let common_durations = [
239			Duration::from_seconds(1).unwrap(),  // 1 second
240			Duration::from_seconds(30).unwrap(), // 30 seconds
241			Duration::from_minutes(5).unwrap(),  // 5 minutes
242			Duration::from_minutes(15).unwrap(), // 15 minutes
243			Duration::from_hours(1).unwrap(),    // 1 hour
244			Duration::from_hours(8).unwrap(),    // Work day
245			Duration::from_days(1).unwrap(),     // 1 day
246			Duration::from_weeks(1).unwrap(),    // 1 week
247			Duration::from_weeks(2).unwrap(),    // 2 weeks
248		];
249
250		for duration in common_durations {
251			let mut row = shape.allocate();
252			shape.set_duration(&mut row, 0, duration.clone());
253			assert_eq!(shape.get_duration(&row, 0), duration);
254		}
255	}
256
257	#[test]
258	fn test_boundary_values() {
259		let shape = RowShape::testing(&[Type::Duration]);
260
261		// Test boundary values for each component
262		let boundary_durations = [
263			Duration::new(i32::MAX, 0, 0).unwrap(), // Max months
264			Duration::new(i32::MIN, 0, 0).unwrap(), // Min months
265			Duration::new(0, i32::MAX, 0).unwrap(), // Max days
266			Duration::new(0, i32::MIN, 0).unwrap(), // Min days
267			Duration::new(0, 0, i64::MAX).unwrap(), // Max nanoseconds
268			Duration::new(0, 0, i64::MIN).unwrap(), // Min nanoseconds
269		];
270
271		for duration in boundary_durations {
272			let mut row = shape.allocate();
273			shape.set_duration(&mut row, 0, duration.clone());
274			assert_eq!(shape.get_duration(&row, 0), duration);
275		}
276	}
277
278	#[test]
279	fn test_try_get_duration_wrong_type() {
280		let shape = RowShape::testing(&[Type::Boolean]);
281		let mut row = shape.allocate();
282
283		shape.set_bool(&mut row, 0, true);
284
285		assert_eq!(shape.try_get_duration(&row, 0), None);
286	}
287}