Skip to main content

reifydb_core/encoded/
duration.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Copyright (c) 2025 ReifyDB
3
4use std::ptr;
5
6use reifydb_type::value::{duration::Duration, r#type::Type};
7
8use crate::encoded::{encoded::EncodedValues, schema::Schema};
9
10impl Schema {
11	pub fn set_duration(&self, row: &mut EncodedValues, 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: &EncodedValues, 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)
51		}
52	}
53
54	pub fn try_get_duration(&self, row: &EncodedValues, 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::Schema;
68
69	#[test]
70	fn test_set_get_duration() {
71		let schema = Schema::testing(&[Type::Duration]);
72		let mut row = schema.allocate();
73
74		let value = Duration::from_seconds(-7200);
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 = Schema::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);
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 = Schema::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 = Schema::testing(&[Type::Duration]);
104
105		let test_durations = [
106			Duration::from_seconds(0),     // Zero
107			Duration::from_seconds(60),    // 1 minute
108			Duration::from_seconds(3600),  // 1 hour
109			Duration::from_seconds(86400), // 1 day
110			Duration::from_days(7),        // 1 week
111			Duration::from_days(30),       // ~1 month
112			Duration::from_weeks(52),      // ~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 = Schema::testing(&[Type::Duration]);
125
126		let negative_durations = [
127			Duration::from_seconds(-60),    // -1 minute
128			Duration::from_seconds(-3600),  // -1 hour
129			Duration::from_seconds(-86400), // -1 day
130			Duration::from_days(-7),        // -1 week
131			Duration::from_weeks(-4),       // -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 = Schema::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		schema.set_duration(&mut row, 0, complex_duration.clone());
153		assert_eq!(schema.get_duration(&row, 0), complex_duration);
154	}
155
156	#[test]
157	fn test_mixed_with_other_types() {
158		let schema = Schema::testing(&[Type::Duration, Type::Boolean, Type::Duration, Type::Int8]);
159		let mut row = schema.allocate();
160
161		let duration1 = Duration::from_hours(24);
162		let duration2 = Duration::from_minutes(-30);
163
164		schema.set_duration(&mut row, 0, duration1.clone());
165		schema.set_bool(&mut row, 1, true);
166		schema.set_duration(&mut row, 2, duration2.clone());
167		schema.set_i64(&mut row, 3, 987654321);
168
169		assert_eq!(schema.get_duration(&row, 0), duration1);
170		assert_eq!(schema.get_bool(&row, 1), true);
171		assert_eq!(schema.get_duration(&row, 2), duration2);
172		assert_eq!(schema.get_i64(&row, 3), 987654321);
173	}
174
175	#[test]
176	fn test_undefined_handling() {
177		let schema = Schema::testing(&[Type::Duration, Type::Duration]);
178		let mut row = schema.allocate();
179
180		let duration = Duration::from_days(100);
181		schema.set_duration(&mut row, 0, duration.clone());
182
183		assert_eq!(schema.try_get_duration(&row, 0), Some(duration));
184		assert_eq!(schema.try_get_duration(&row, 1), None);
185
186		schema.set_undefined(&mut row, 0);
187		assert_eq!(schema.try_get_duration(&row, 0), None);
188	}
189
190	#[test]
191	fn test_large_values() {
192		let schema = Schema::testing(&[Type::Duration]);
193		let mut row = schema.allocate();
194
195		// Test with large values
196		let large_duration = Duration::new(
197			120,             // 10 years in months
198			3650,            // ~10 years in days
199			123456789012345, // Large nanosecond value
200		);
201		schema.set_duration(&mut row, 0, large_duration.clone());
202		assert_eq!(schema.get_duration(&row, 0), large_duration);
203	}
204
205	#[test]
206	fn test_precision_preservation() {
207		let schema = Schema::testing(&[Type::Duration]);
208		let mut row = schema.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		schema.set_duration(&mut row, 0, precise_duration.clone());
217
218		let retrieved = schema.get_duration(&row, 0);
219		assert_eq!(retrieved, precise_duration);
220
221		let orig_months = precise_duration.get_months();
222		let orig_days = precise_duration.get_days();
223		let orig_nanos = precise_duration.get_nanos();
224		let ret_months = retrieved.get_months();
225		let ret_days = retrieved.get_days();
226		let ret_nanos = retrieved.get_nanos();
227		assert_eq!(orig_months, ret_months);
228		assert_eq!(orig_days, ret_days);
229		assert_eq!(orig_nanos, ret_nanos);
230	}
231
232	#[test]
233	fn test_common_durations() {
234		let schema = Schema::testing(&[Type::Duration]);
235
236		// Test common durations used in applications
237		let common_durations = [
238			Duration::from_seconds(1),  // 1 second
239			Duration::from_seconds(30), // 30 seconds
240			Duration::from_minutes(5),  // 5 minutes
241			Duration::from_minutes(15), // 15 minutes
242			Duration::from_hours(1),    // 1 hour
243			Duration::from_hours(8),    // Work day
244			Duration::from_days(1),     // 1 day
245			Duration::from_weeks(1),    // 1 week
246			Duration::from_weeks(2),    // 2 weeks
247		];
248
249		for duration in common_durations {
250			let mut row = schema.allocate();
251			schema.set_duration(&mut row, 0, duration.clone());
252			assert_eq!(schema.get_duration(&row, 0), duration);
253		}
254	}
255
256	#[test]
257	fn test_boundary_values() {
258		let schema = Schema::testing(&[Type::Duration]);
259
260		// Test boundary values for each component
261		let boundary_durations = [
262			Duration::new(i32::MAX, 0, 0), // Max months
263			Duration::new(i32::MIN, 0, 0), // Min months
264			Duration::new(0, i32::MAX, 0), // Max days
265			Duration::new(0, i32::MIN, 0), // Min days
266			Duration::new(0, 0, i64::MAX), // Max nanoseconds
267			Duration::new(0, 0, i64::MIN), // Min nanoseconds
268		];
269
270		for duration in boundary_durations {
271			let mut row = schema.allocate();
272			schema.set_duration(&mut row, 0, duration.clone());
273			assert_eq!(schema.get_duration(&row, 0), duration);
274		}
275	}
276
277	#[test]
278	fn test_try_get_duration_wrong_type() {
279		let schema = Schema::testing(&[Type::Boolean]);
280		let mut row = schema.allocate();
281
282		schema.set_bool(&mut row, 0, true);
283
284		assert_eq!(schema.try_get_duration(&row, 0), None);
285	}
286}