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