reifydb-core 0.4.12

Core database interfaces and data structures for ReifyDB
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2025 ReifyDB

use std::ptr;

use reifydb_type::value::{duration::Duration, r#type::Type};

use crate::encoded::{row::EncodedRow, shape::RowShape};

impl RowShape {
	pub fn set_duration(&self, row: &mut EncodedRow, index: usize, value: Duration) {
		let field = &self.fields()[index];
		debug_assert!(row.len() >= self.total_static_size());
		debug_assert_eq!(*field.constraint.get_type().inner_type(), Type::Duration);
		row.set_valid(index, true);

		let months = value.get_months();
		let days = value.get_days();
		let nanos = value.get_nanos();
		unsafe {
			// Write months (i32) at offset
			ptr::write_unaligned(
				row.make_mut().as_mut_ptr().add(field.offset as usize) as *mut i32,
				months,
			);
			// Write days (i32) at offset + 4
			ptr::write_unaligned(
				row.make_mut().as_mut_ptr().add(field.offset as usize + 4) as *mut i32,
				days,
			);
			// Write nanos (i64) at offset + 8
			ptr::write_unaligned(
				row.make_mut().as_mut_ptr().add(field.offset as usize + 8) as *mut i64,
				nanos,
			);
		}
	}

	pub fn get_duration(&self, row: &EncodedRow, index: usize) -> Duration {
		let field = &self.fields()[index];
		debug_assert!(row.len() >= self.total_static_size());
		debug_assert_eq!(*field.constraint.get_type().inner_type(), Type::Duration);
		unsafe {
			// Read months (i32) from offset
			let months = (row.as_ptr().add(field.offset as usize) as *const i32).read_unaligned();
			// Read days (i32) from offset + 4
			let days = (row.as_ptr().add(field.offset as usize + 4) as *const i32).read_unaligned();
			// Read nanos (i64) from offset + 8
			let nanos = (row.as_ptr().add(field.offset as usize + 8) as *const i64).read_unaligned();
			Duration::new(months, days, nanos).expect("stored duration must be valid")
		}
	}

	pub fn try_get_duration(&self, row: &EncodedRow, index: usize) -> Option<Duration> {
		if row.is_defined(index) && self.fields()[index].constraint.get_type() == Type::Duration {
			Some(self.get_duration(row, index))
		} else {
			None
		}
	}
}

#[cfg(test)]
pub mod tests {
	use reifydb_type::value::{duration::Duration, r#type::Type};

	use crate::encoded::shape::RowShape;

	#[test]
	fn test_set_get_duration() {
		let shape = RowShape::testing(&[Type::Duration]);
		let mut row = shape.allocate();

		let value = Duration::from_seconds(-7200).unwrap();
		shape.set_duration(&mut row, 0, value.clone());
		assert_eq!(shape.get_duration(&row, 0), value);
	}

	#[test]
	fn test_try_get_duration() {
		let shape = RowShape::testing(&[Type::Duration]);
		let mut row = shape.allocate();

		assert_eq!(shape.try_get_duration(&row, 0), None);

		let test_duration = Duration::from_days(30).unwrap();
		shape.set_duration(&mut row, 0, test_duration.clone());
		assert_eq!(shape.try_get_duration(&row, 0), Some(test_duration));
	}

	#[test]
	fn test_zero() {
		let shape = RowShape::testing(&[Type::Duration]);
		let mut row = shape.allocate();

		let zero = Duration::default(); // Zero duration
		shape.set_duration(&mut row, 0, zero.clone());
		assert_eq!(shape.get_duration(&row, 0), zero);
	}

	#[test]
	fn test_various_durations() {
		let shape = RowShape::testing(&[Type::Duration]);

		let test_durations = [
			Duration::from_seconds(0).unwrap(),     // Zero
			Duration::from_seconds(60).unwrap(),    // 1 minute
			Duration::from_seconds(3600).unwrap(),  // 1 hour
			Duration::from_seconds(86400).unwrap(), // 1 day
			Duration::from_days(7).unwrap(),        // 1 week
			Duration::from_days(30).unwrap(),       // ~1 month
			Duration::from_weeks(52).unwrap(),      // ~1 year
		];

		for duration in test_durations {
			let mut row = shape.allocate();
			shape.set_duration(&mut row, 0, duration.clone());
			assert_eq!(shape.get_duration(&row, 0), duration);
		}
	}

	#[test]
	fn test_negative_durations() {
		let shape = RowShape::testing(&[Type::Duration]);

		let negative_durations = [
			Duration::from_seconds(-60).unwrap(),    // -1 minute
			Duration::from_seconds(-3600).unwrap(),  // -1 hour
			Duration::from_seconds(-86400).unwrap(), // -1 day
			Duration::from_days(-7).unwrap(),        // -1 week
			Duration::from_weeks(-4).unwrap(),       // -1 month
		];

		for duration in negative_durations {
			let mut row = shape.allocate();
			shape.set_duration(&mut row, 0, duration.clone());
			assert_eq!(shape.get_duration(&row, 0), duration);
		}
	}

	#[test]
	fn test_complex_parts() {
		let shape = RowShape::testing(&[Type::Duration]);
		let mut row = shape.allocate();

		// Create a duration with all components
		let complex_duration = Duration::new(
			6,         // 6 months
			15,        // 15 days
			123456789, // nanoseconds
		)
		.unwrap();
		shape.set_duration(&mut row, 0, complex_duration.clone());
		assert_eq!(shape.get_duration(&row, 0), complex_duration);
	}

	#[test]
	fn test_mixed_with_other_types() {
		let shape = RowShape::testing(&[Type::Duration, Type::Boolean, Type::Duration, Type::Int8]);
		let mut row = shape.allocate();

		let duration1 = Duration::from_hours(24).unwrap();
		let duration2 = Duration::from_minutes(-30).unwrap();

		shape.set_duration(&mut row, 0, duration1.clone());
		shape.set_bool(&mut row, 1, true);
		shape.set_duration(&mut row, 2, duration2.clone());
		shape.set_i64(&mut row, 3, 987654321);

		assert_eq!(shape.get_duration(&row, 0), duration1);
		assert_eq!(shape.get_bool(&row, 1), true);
		assert_eq!(shape.get_duration(&row, 2), duration2);
		assert_eq!(shape.get_i64(&row, 3), 987654321);
	}

	#[test]
	fn test_undefined_handling() {
		let shape = RowShape::testing(&[Type::Duration, Type::Duration]);
		let mut row = shape.allocate();

		let duration = Duration::from_days(100).unwrap();
		shape.set_duration(&mut row, 0, duration.clone());

		assert_eq!(shape.try_get_duration(&row, 0), Some(duration));
		assert_eq!(shape.try_get_duration(&row, 1), None);

		shape.set_none(&mut row, 0);
		assert_eq!(shape.try_get_duration(&row, 0), None);
	}

	#[test]
	fn test_large_values() {
		let shape = RowShape::testing(&[Type::Duration]);
		let mut row = shape.allocate();

		// Test with large values
		let large_duration = Duration::new(
			120,             // 10 years in months
			3650,            // ~10 years in days
			123456789012345, // Large nanosecond value
		)
		.unwrap();
		shape.set_duration(&mut row, 0, large_duration.clone());
		assert_eq!(shape.get_duration(&row, 0), large_duration);
	}

	#[test]
	fn test_precision_preservation() {
		let shape = RowShape::testing(&[Type::Duration]);
		let mut row = shape.allocate();

		// Test that all components are preserved exactly
		let precise_duration = Duration::new(
			5,         // 5 months
			20,        // 20 days
			999999999, // 999,999,999 nanoseconds
		)
		.unwrap();
		shape.set_duration(&mut row, 0, precise_duration.clone());

		let retrieved = shape.get_duration(&row, 0);
		assert_eq!(retrieved, precise_duration);

		let orig_months = precise_duration.get_months();
		let orig_days = precise_duration.get_days();
		let orig_nanos = precise_duration.get_nanos();
		let ret_months = retrieved.get_months();
		let ret_days = retrieved.get_days();
		let ret_nanos = retrieved.get_nanos();
		assert_eq!(orig_months, ret_months);
		assert_eq!(orig_days, ret_days);
		assert_eq!(orig_nanos, ret_nanos);
	}

	#[test]
	fn test_common_durations() {
		let shape = RowShape::testing(&[Type::Duration]);

		// Test common durations used in applications
		let common_durations = [
			Duration::from_seconds(1).unwrap(),  // 1 second
			Duration::from_seconds(30).unwrap(), // 30 seconds
			Duration::from_minutes(5).unwrap(),  // 5 minutes
			Duration::from_minutes(15).unwrap(), // 15 minutes
			Duration::from_hours(1).unwrap(),    // 1 hour
			Duration::from_hours(8).unwrap(),    // Work day
			Duration::from_days(1).unwrap(),     // 1 day
			Duration::from_weeks(1).unwrap(),    // 1 week
			Duration::from_weeks(2).unwrap(),    // 2 weeks
		];

		for duration in common_durations {
			let mut row = shape.allocate();
			shape.set_duration(&mut row, 0, duration.clone());
			assert_eq!(shape.get_duration(&row, 0), duration);
		}
	}

	#[test]
	fn test_boundary_values() {
		let shape = RowShape::testing(&[Type::Duration]);

		// Test boundary values for each component
		let boundary_durations = [
			Duration::new(i32::MAX, 0, 0).unwrap(), // Max months
			Duration::new(i32::MIN, 0, 0).unwrap(), // Min months
			Duration::new(0, i32::MAX, 0).unwrap(), // Max days
			Duration::new(0, i32::MIN, 0).unwrap(), // Min days
			Duration::new(0, 0, i64::MAX).unwrap(), // Max nanoseconds
			Duration::new(0, 0, i64::MIN).unwrap(), // Min nanoseconds
		];

		for duration in boundary_durations {
			let mut row = shape.allocate();
			shape.set_duration(&mut row, 0, duration.clone());
			assert_eq!(shape.get_duration(&row, 0), duration);
		}
	}

	#[test]
	fn test_try_get_duration_wrong_type() {
		let shape = RowShape::testing(&[Type::Boolean]);
		let mut row = shape.allocate();

		shape.set_bool(&mut row, 0, true);

		assert_eq!(shape.try_get_duration(&row, 0), None);
	}
}