diff --git a/crates/proof-of-sql-parser/src/posql_time/timezone.rs b/crates/proof-of-sql-parser/src/posql_time/timezone.rs index 39cdfbae9..27fe125df 100644 --- a/crates/proof-of-sql-parser/src/posql_time/timezone.rs +++ b/crates/proof-of-sql-parser/src/posql_time/timezone.rs @@ -22,6 +22,15 @@ impl PoSQLTimeZone { PoSQLTimeZone::FixedOffset(offset) } } + + /// For comparisons, normalize a timestamp based on a timezone offset so + /// it can be compared to another timestamp. + pub fn normalize_to_utc(timestamp: i64, tz: &PoSQLTimeZone) -> i64 { + match tz { + PoSQLTimeZone::Utc => timestamp, // No adjustment needed + PoSQLTimeZone::FixedOffset(offset) => timestamp - *offset as i64, // Adjust by offset in seconds + } + } } impl TryFrom<&Option>> for PoSQLTimeZone { @@ -77,7 +86,7 @@ impl fmt::Display for PoSQLTimeZone { #[cfg(test)] mod timezone_parsing_tests { - use crate::posql_time::timezone; + use crate::posql_time::{timezone, PoSQLTimeZone, PoSQLTimestamp}; use alloc::format; #[test] @@ -97,11 +106,6 @@ mod timezone_parsing_tests { let timezone = timezone::PoSQLTimeZone::Utc; assert_eq!(format!("{timezone}"), "+00:00"); } -} - -#[cfg(test)] -mod timezone_offset_tests { - use crate::posql_time::{timestamp::PoSQLTimestamp, timezone}; #[test] fn test_utc_timezone() { @@ -134,4 +138,43 @@ mod timezone_offset_tests { let result = PoSQLTimestamp::try_from(input).unwrap(); assert_eq!(result.timezone(), expected_timezone); } + + #[test] + fn test_from_offset() { + // UTC case + let tz = PoSQLTimeZone::from_offset(0); + assert!(matches!(tz, PoSQLTimeZone::Utc)); + + // Fixed offset case + let tz = PoSQLTimeZone::from_offset(3600); // UTC+1 + assert!(matches!(tz, PoSQLTimeZone::FixedOffset(3600))); + + // Negative offset case (UTC-5) + let tz = PoSQLTimeZone::from_offset(-18000); + assert!(matches!(tz, PoSQLTimeZone::FixedOffset(-18000))); + } + + #[test] + fn test_normalize_to_utc_with_utc() { + let timestamp = 1231006505; // Unix timestamp in seconds + let tz = PoSQLTimeZone::Utc; + let normalized = PoSQLTimeZone::normalize_to_utc(timestamp, &tz); + assert_eq!(normalized, timestamp); // No adjustment for UTC + } + + #[test] + fn test_normalize_to_utc_with_positive_offset() { + let timestamp = 1231006505; // Unix timestamp in seconds + let tz = PoSQLTimeZone::FixedOffset(3600); // UTC+1 (3600 seconds offset) + let normalized = PoSQLTimeZone::normalize_to_utc(timestamp, &tz); + assert_eq!(normalized, 1231006505 - 3600); // Adjusted by 1 hour + } + + #[test] + fn test_normalize_to_utc_with_negative_offset() { + let timestamp = 1231006505; // Unix timestamp in seconds + let tz = PoSQLTimeZone::FixedOffset(-18000); // UTC-5 (18000 seconds offset) + let normalized = PoSQLTimeZone::normalize_to_utc(timestamp, &tz); + assert_eq!(normalized, 1231006505 + 18000); // Adjusted by 5 hours + } } diff --git a/crates/proof-of-sql-parser/src/posql_time/unit.rs b/crates/proof-of-sql-parser/src/posql_time/unit.rs index 215fa7959..0192ab3b7 100644 --- a/crates/proof-of-sql-parser/src/posql_time/unit.rs +++ b/crates/proof-of-sql-parser/src/posql_time/unit.rs @@ -16,6 +16,40 @@ pub enum PoSQLTimeUnit { Nanosecond, } +impl PoSQLTimeUnit { + /// Convert the numerical unit of one timeunit to another for comparison purposes. + pub fn normalize_timeunit( + timestamp: i64, + from_unit: PoSQLTimeUnit, + to_unit: PoSQLTimeUnit, + ) -> i64 { + match (from_unit, to_unit) { + // Conversions from Seconds + (PoSQLTimeUnit::Second, PoSQLTimeUnit::Millisecond) => timestamp * 1000, + (PoSQLTimeUnit::Second, PoSQLTimeUnit::Microsecond) => timestamp * 1_000_000, + (PoSQLTimeUnit::Second, PoSQLTimeUnit::Nanosecond) => timestamp * 1_000_000_000, + + // Conversions from Milliseconds + (PoSQLTimeUnit::Millisecond, PoSQLTimeUnit::Second) => timestamp / 1000, + (PoSQLTimeUnit::Millisecond, PoSQLTimeUnit::Microsecond) => timestamp * 1000, + (PoSQLTimeUnit::Millisecond, PoSQLTimeUnit::Nanosecond) => timestamp * 1_000_000, + + // Conversions from Microseconds + (PoSQLTimeUnit::Microsecond, PoSQLTimeUnit::Second) => timestamp / 1_000_000, + (PoSQLTimeUnit::Microsecond, PoSQLTimeUnit::Millisecond) => timestamp / 1000, + (PoSQLTimeUnit::Microsecond, PoSQLTimeUnit::Nanosecond) => timestamp * 1000, + + // Conversions from Nanoseconds + (PoSQLTimeUnit::Nanosecond, PoSQLTimeUnit::Second) => timestamp / 1_000_000_000, + (PoSQLTimeUnit::Nanosecond, PoSQLTimeUnit::Millisecond) => timestamp / 1_000_000, + (PoSQLTimeUnit::Nanosecond, PoSQLTimeUnit::Microsecond) => timestamp / 1000, + + // If units are the same, no adjustment is needed + _ => timestamp, + } + } +} + impl TryFrom<&str> for PoSQLTimeUnit { type Error = PoSQLTimestampError; fn try_from(value: &str) -> Result { @@ -107,4 +141,147 @@ mod time_unit_tests { expected.timestamp_nanos_opt().unwrap() ); } + #[test] + fn test_normalize_timeunit_seconds_to_milliseconds() { + let timestamp = 1231006505; // seconds + let result = PoSQLTimeUnit::normalize_timeunit( + timestamp, + PoSQLTimeUnit::Second, + PoSQLTimeUnit::Millisecond, + ); + assert_eq!(result, 1231006505000); // converted to milliseconds + } + + #[test] + fn test_normalize_timeunit_seconds_to_microseconds() { + let timestamp = 1231006505; // seconds + let result = PoSQLTimeUnit::normalize_timeunit( + timestamp, + PoSQLTimeUnit::Second, + PoSQLTimeUnit::Microsecond, + ); + assert_eq!(result, 1231006505000000); // converted to microseconds + } + + #[test] + fn test_normalize_timeunit_seconds_to_nanoseconds() { + let timestamp = 1231006505; // seconds + let result = PoSQLTimeUnit::normalize_timeunit( + timestamp, + PoSQLTimeUnit::Second, + PoSQLTimeUnit::Nanosecond, + ); + assert_eq!(result, 1231006505000000000); // converted to nanoseconds + } + + #[test] + fn test_normalize_timeunit_milliseconds_to_seconds() { + let timestamp = 1231006505000; // milliseconds + let result = PoSQLTimeUnit::normalize_timeunit( + timestamp, + PoSQLTimeUnit::Millisecond, + PoSQLTimeUnit::Second, + ); + assert_eq!(result, 1231006505); // converted to seconds + } + + #[test] + fn test_normalize_timeunit_milliseconds_to_microseconds() { + let timestamp = 1231006505; // milliseconds + let result = PoSQLTimeUnit::normalize_timeunit( + timestamp, + PoSQLTimeUnit::Millisecond, + PoSQLTimeUnit::Microsecond, + ); + assert_eq!(result, 1231006505000); // converted to microseconds + } + + #[test] + fn test_normalize_timeunit_milliseconds_to_nanoseconds() { + let timestamp = 1231006505; // milliseconds + let result = PoSQLTimeUnit::normalize_timeunit( + timestamp, + PoSQLTimeUnit::Millisecond, + PoSQLTimeUnit::Nanosecond, + ); + assert_eq!(result, 1231006505000000); // converted to nanoseconds + } + + #[test] + fn test_normalize_timeunit_microseconds_to_seconds() { + let timestamp = 1231006505000000; // microseconds + let result = PoSQLTimeUnit::normalize_timeunit( + timestamp, + PoSQLTimeUnit::Microsecond, + PoSQLTimeUnit::Second, + ); + assert_eq!(result, 1231006505); // converted to seconds + } + + #[test] + fn test_normalize_timeunit_microseconds_to_milliseconds() { + let timestamp = 1231006505000; // microseconds + let result = PoSQLTimeUnit::normalize_timeunit( + timestamp, + PoSQLTimeUnit::Microsecond, + PoSQLTimeUnit::Millisecond, + ); + assert_eq!(result, 1231006505); // converted to milliseconds + } + + #[test] + fn test_normalize_timeunit_microseconds_to_nanoseconds() { + let timestamp = 1231006505; // microseconds + let result = PoSQLTimeUnit::normalize_timeunit( + timestamp, + PoSQLTimeUnit::Microsecond, + PoSQLTimeUnit::Nanosecond, + ); + assert_eq!(result, 1231006505000); // converted to nanoseconds + } + + #[test] + fn test_normalize_timeunit_nanoseconds_to_seconds() { + let timestamp = 1231006505000000000; // nanoseconds + let result = PoSQLTimeUnit::normalize_timeunit( + timestamp, + PoSQLTimeUnit::Nanosecond, + PoSQLTimeUnit::Second, + ); + assert_eq!(result, 1231006505); // converted to seconds + } + + #[test] + fn test_normalize_timeunit_nanoseconds_to_milliseconds() { + let timestamp = 1231006505000000; // nanoseconds + let result = PoSQLTimeUnit::normalize_timeunit( + timestamp, + PoSQLTimeUnit::Nanosecond, + PoSQLTimeUnit::Millisecond, + ); + assert_eq!(result, 1231006505); // converted to milliseconds + } + + #[test] + fn test_normalize_timeunit_nanoseconds_to_microseconds() { + let timestamp = 1231006505000; // nanoseconds + let result = PoSQLTimeUnit::normalize_timeunit( + timestamp, + PoSQLTimeUnit::Nanosecond, + PoSQLTimeUnit::Microsecond, + ); + assert_eq!(result, 1231006505); // converted to microseconds + } + + #[test] + fn test_normalize_timeunit_same_units() { + // If from_unit and to_unit are the same, it should return the timestamp as is + let timestamp = 1231006505; + let result = PoSQLTimeUnit::normalize_timeunit( + timestamp, + PoSQLTimeUnit::Second, + PoSQLTimeUnit::Second, + ); + assert_eq!(result, timestamp); // No conversion needed + } } diff --git a/crates/proof-of-sql/src/base/database/column_operation.rs b/crates/proof-of-sql/src/base/database/column_operation.rs index be6a2fdde..b62410a78 100644 --- a/crates/proof-of-sql/src/base/database/column_operation.rs +++ b/crates/proof-of-sql/src/base/database/column_operation.rs @@ -12,7 +12,7 @@ use num_traits::{ ops::checked::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub}, Zero, }; -use proof_of_sql_parser::intermediate_ast::BinaryOperator; +use proof_of_sql_parser::{intermediate_ast::BinaryOperator, posql_time::PoSQLTimeUnit}; // For decimal type manipulation please refer to // https://learn.microsoft.com/en-us/sql/t-sql/data-types/precision-scale-and-length-transact-sql?view=sql-server-ver16 @@ -926,11 +926,203 @@ where )) } +/// Check that slices of timestamps are equal, considering possibly different timeunits +pub(crate) fn slice_eq_with_casting_and_timeunit( + lhs: &[i64], + lhs_unit: &PoSQLTimeUnit, + rhs: &[i64], + rhs_unit: &PoSQLTimeUnit, +) -> Vec { + let lhs_adjusted: Vec = lhs + .iter() + .map(|&ts| PoSQLTimeUnit::normalize_timeunit(ts, *lhs_unit, *rhs_unit)) + .collect(); + + slice_eq(&lhs_adjusted, rhs) +} + +/// Check that slices of timestamps are less than or equal, considering possibly different timeunits +pub(crate) fn slice_le_with_casting_and_timeunit( + lhs: &[i64], + lhs_unit: &PoSQLTimeUnit, + rhs: &[i64], + rhs_unit: &PoSQLTimeUnit, +) -> Vec { + let lhs_adjusted: Vec = lhs + .iter() + .map(|&ts| PoSQLTimeUnit::normalize_timeunit(ts, *lhs_unit, *rhs_unit)) + .collect(); + + lhs_adjusted + .iter() + .zip(rhs.iter()) + .map(|(&lhs_ts, &rhs_ts)| lhs_ts <= rhs_ts) + .collect() +} + +/// Check that slices of timestamps are greater than or equal, considering possibly different timeunits +pub(crate) fn slice_ge_with_casting_and_timeunit( + lhs: &[i64], + lhs_unit: &PoSQLTimeUnit, + rhs: &[i64], + rhs_unit: &PoSQLTimeUnit, +) -> Vec { + let lhs_adjusted: Vec = lhs + .iter() + .map(|&ts| PoSQLTimeUnit::normalize_timeunit(ts, *lhs_unit, *rhs_unit)) + .collect(); + + lhs_adjusted + .iter() + .zip(rhs.iter()) + .map(|(&lhs_ts, &rhs_ts)| lhs_ts >= rhs_ts) + .collect() +} + #[cfg(test)] mod test { use super::*; use crate::base::scalar::Curve25519Scalar; + #[test] + fn we_can_slice_le_with_casting_and_timeunit_equal() { + // Both slices are equal and in seconds + let lhs = vec![1231006505, 1231006506, 1231006507]; + let rhs = vec![1231006505, 1231006506, 1231006507]; + let result = slice_le_with_casting_and_timeunit( + &lhs, + &PoSQLTimeUnit::Second, + &rhs, + &PoSQLTimeUnit::Second, + ); + assert_eq!(result, vec![true, true, true]); + } + + #[test] + fn we_can_slice_ge_with_casting_and_timeunit_equal() { + // Both slices are equal and in seconds + let lhs = vec![1231006505, 1231006506, 1231006507]; + let rhs = vec![1231006505, 1231006506, 1231006507]; + let result = slice_ge_with_casting_and_timeunit( + &lhs, + &PoSQLTimeUnit::Second, + &rhs, + &PoSQLTimeUnit::Second, + ); + assert_eq!(result, vec![true, true, true]); + } + + #[test] + fn we_can_slice_le_with_casting_and_timeunit_less_than() { + // LHS is less than RHS + let lhs = vec![1231006504, 1231006505, 1231006506]; + let rhs = vec![1231006505, 1231006506, 1231006507]; + let result = slice_le_with_casting_and_timeunit( + &lhs, + &PoSQLTimeUnit::Second, + &rhs, + &PoSQLTimeUnit::Second, + ); + assert_eq!(result, vec![true, true, true]); + } + + #[test] + fn we_can_slice_ge_with_casting_and_timeunit_greater_than() { + // LHS is greater than RHS + let lhs = vec![1231006506, 1231006507, 1231006508]; + let rhs = vec![1231006505, 1231006506, 1231006507]; + let result = slice_ge_with_casting_and_timeunit( + &lhs, + &PoSQLTimeUnit::Second, + &rhs, + &PoSQLTimeUnit::Second, + ); + assert_eq!(result, vec![true, true, true]); + } + + #[test] + fn we_can_slice_le_with_casting_and_timeunit_mixed_time_units() { + // LHS in seconds, RHS in milliseconds + let lhs = vec![1231006505, 1231006506, 1231006507]; // in seconds + let rhs = vec![1231006505000, 1231006506000, 1231006507000]; // in milliseconds + let result = slice_le_with_casting_and_timeunit( + &lhs, + &PoSQLTimeUnit::Second, + &rhs, + &PoSQLTimeUnit::Millisecond, + ); + assert_eq!(result, vec![true, true, true]); + } + + #[test] + fn we_can_slice_ge_with_casting_and_timeunit_mixed_time_units() { + // LHS in seconds, RHS in milliseconds + let lhs = vec![1231006505, 1231006506, 1231006507]; // in seconds + let rhs = vec![1231006505000, 1231006506000, 1231006507000]; // in milliseconds + let result = slice_ge_with_casting_and_timeunit( + &lhs, + &PoSQLTimeUnit::Second, + &rhs, + &PoSQLTimeUnit::Millisecond, + ); + assert_eq!(result, vec![true, true, true]); + } + + #[test] + fn we_can_slice_le_with_casting_and_timeunit_mixed_results() { + let lhs = vec![1231006505, 1231006508, 1231006506]; + let rhs = vec![1231006506, 1231006507, 1231006508]; + let result = slice_le_with_casting_and_timeunit( + &lhs, + &PoSQLTimeUnit::Second, + &rhs, + &PoSQLTimeUnit::Second, + ); + assert_eq!(result, vec![true, false, true]); + } + + #[test] + fn we_can_slice_ge_with_casting_and_timeunit_mixed_results() { + // Mixed values + let lhs = vec![1231006506, 1231006507, 1231006508]; + let rhs = vec![1231006505, 1231006507, 1231006508]; + let result = slice_ge_with_casting_and_timeunit( + &lhs, + &PoSQLTimeUnit::Second, + &rhs, + &PoSQLTimeUnit::Second, + ); + assert_eq!(result, vec![true, true, true]); + } + + #[test] + fn we_can_slice_le_with_casting_and_timeunit_edge_case() { + // LHS in seconds, RHS in microseconds + let lhs = vec![1231006505]; // in seconds + let rhs = vec![1231006505000000]; // in microseconds + let result = slice_le_with_casting_and_timeunit( + &lhs, + &PoSQLTimeUnit::Second, + &rhs, + &PoSQLTimeUnit::Microsecond, + ); + assert_eq!(result, vec![true]); + } + + #[test] + fn we_can_slice_ge_with_casting_and_timeunit_edge_case() { + // LHS in seconds, RHS in microseconds + let lhs = vec![1231006505]; // in seconds + let rhs = vec![1231006505000000]; // in microseconds + let result = slice_ge_with_casting_and_timeunit( + &lhs, + &PoSQLTimeUnit::Second, + &rhs, + &PoSQLTimeUnit::Microsecond, + ); + assert_eq!(result, vec![true]); + } + #[test] fn we_can_add_numeric_types() { // lhs and rhs are integers with the same precision diff --git a/crates/proof-of-sql/src/base/database/owned_column_operation.rs b/crates/proof-of-sql/src/base/database/owned_column_operation.rs index 29a131553..5e8a19020 100644 --- a/crates/proof-of-sql/src/base/database/owned_column_operation.rs +++ b/crates/proof-of-sql/src/base/database/owned_column_operation.rs @@ -4,7 +4,10 @@ use crate::base::{ scalar::Scalar, }; use core::ops::{Add, Div, Mul, Sub}; -use proof_of_sql_parser::intermediate_ast::{BinaryOperator, UnaryOperator}; +use proof_of_sql_parser::{ + intermediate_ast::{BinaryOperator, UnaryOperator}, + posql_time::PoSQLTimeZone, +}; impl OwnedColumn { /// Element-wise NOT operation for a column @@ -224,8 +227,41 @@ impl OwnedColumn { (Self::Boolean(lhs), Self::Boolean(rhs)) => Ok(Self::Boolean(slice_eq(lhs, rhs))), (Self::Scalar(lhs), Self::Scalar(rhs)) => Ok(Self::Boolean(slice_eq(lhs, rhs))), (Self::VarChar(lhs), Self::VarChar(rhs)) => Ok(Self::Boolean(slice_eq(lhs, rhs))), - (Self::TimestampTZ(_, _, _), Self::TimestampTZ(_, _, _)) => { - todo!("Implement equality check for TimeStampTZ") + ( + Self::TimestampTZ(lhs_unit, lhs_tz, lhs), + Self::TimestampTZ(rhs_unit, rhs_tz, rhs), + ) => { + if lhs.len() != rhs.len() { + return Err(ColumnOperationError::DifferentColumnLength( + lhs.len(), + rhs.len(), + )); + } + + let lhs_normalized: Vec = lhs + .iter() + .map(|&ts| PoSQLTimeZone::normalize_to_utc(ts, lhs_tz)) + .collect(); + + let rhs_normalized: Vec = rhs + .iter() + .map(|&ts| PoSQLTimeZone::normalize_to_utc(ts, rhs_tz)) + .collect(); + + let timeunit_match = lhs_unit == rhs_unit; + + let result = if !timeunit_match { + slice_eq_with_casting_and_timeunit( + &lhs_normalized, + lhs_unit, + &rhs_normalized, + rhs_unit, + ) + } else { + slice_eq(&lhs_normalized, &rhs_normalized) + }; + + Ok(Self::Boolean(result)) } _ => Err(ColumnOperationError::BinaryOperationInvalidColumnType { operator: BinaryOperator::Equal, @@ -404,8 +440,45 @@ impl OwnedColumn { } (Self::Boolean(lhs), Self::Boolean(rhs)) => Ok(Self::Boolean(slice_le(lhs, rhs))), (Self::Scalar(lhs), Self::Scalar(rhs)) => Ok(Self::Boolean(slice_le(lhs, rhs))), - (Self::TimestampTZ(_, _, _), Self::TimestampTZ(_, _, _)) => { - todo!("Implement inequality check for TimeStampTZ") + ( + Self::TimestampTZ(lhs_unit, lhs_tz, lhs), + Self::TimestampTZ(rhs_unit, rhs_tz, rhs), + ) => { + if lhs.len() != rhs.len() { + return Err(ColumnOperationError::DifferentColumnLength( + lhs.len(), + rhs.len(), + )); + } + + let lhs_normalized: Vec = lhs + .iter() + .map(|&ts| PoSQLTimeZone::normalize_to_utc(ts, lhs_tz)) + .collect(); + + let rhs_normalized: Vec = rhs + .iter() + .map(|&ts| PoSQLTimeZone::normalize_to_utc(ts, rhs_tz)) + .collect(); + + let timeunit_match = lhs_unit == rhs_unit; + + let result = if !timeunit_match { + slice_le_with_casting_and_timeunit( + &lhs_normalized, + lhs_unit, + &rhs_normalized, + rhs_unit, + ) + } else { + lhs_normalized + .iter() + .zip(rhs_normalized.iter()) + .map(|(&lhs_ts, &rhs_ts)| lhs_ts <= rhs_ts) + .collect() + }; + + Ok(Self::Boolean(result)) } _ => Err(ColumnOperationError::BinaryOperationInvalidColumnType { operator: BinaryOperator::LessThanOrEqual, @@ -584,8 +657,45 @@ impl OwnedColumn { } (Self::Boolean(lhs), Self::Boolean(rhs)) => Ok(Self::Boolean(slice_ge(lhs, rhs))), (Self::Scalar(lhs), Self::Scalar(rhs)) => Ok(Self::Boolean(slice_ge(lhs, rhs))), - (Self::TimestampTZ(_, _, _), Self::TimestampTZ(_, _, _)) => { - todo!("Implement inequality check for TimeStampTZ") + ( + Self::TimestampTZ(lhs_unit, lhs_tz, lhs), + Self::TimestampTZ(rhs_unit, rhs_tz, rhs), + ) => { + if lhs.len() != rhs.len() { + return Err(ColumnOperationError::DifferentColumnLength( + lhs.len(), + rhs.len(), + )); + } + + let lhs_normalized: Vec = lhs + .iter() + .map(|&ts| PoSQLTimeZone::normalize_to_utc(ts, lhs_tz)) + .collect(); + + let rhs_normalized: Vec = rhs + .iter() + .map(|&ts| PoSQLTimeZone::normalize_to_utc(ts, rhs_tz)) + .collect(); + + let timeunit_match = lhs_unit == rhs_unit; + + let result = if !timeunit_match { + slice_ge_with_casting_and_timeunit( + &lhs_normalized, + lhs_unit, + &rhs_normalized, + rhs_unit, + ) + } else { + lhs_normalized + .iter() + .zip(rhs_normalized.iter()) + .map(|(&lhs_ts, &rhs_ts)| lhs_ts >= rhs_ts) + .collect() + }; + + Ok(Self::Boolean(result)) } _ => Err(ColumnOperationError::BinaryOperationInvalidColumnType { operator: BinaryOperator::GreaterThanOrEqual, @@ -1384,6 +1494,7 @@ impl Div for OwnedColumn { mod test { use super::*; use crate::base::{math::decimal::Precision, scalar::Curve25519Scalar}; + use proof_of_sql_parser::posql_time::PoSQLTimeUnit; #[test] fn we_cannot_do_binary_operation_on_columns_with_different_lengths() { @@ -1615,10 +1726,70 @@ mod test { true, false, true ])) ); + + // TimestampTZ + let lhs_timestamps = vec![1231006505, 1231006510, 1231006520]; // Unix timestamps in seconds + let rhs_timestamps = vec![1231006505, 1231006510, 1231006520]; + let lhs = OwnedColumn::::TimestampTZ( + PoSQLTimeUnit::Second, + PoSQLTimeZone::Utc, + lhs_timestamps.clone(), + ); + let rhs = OwnedColumn::::TimestampTZ( + PoSQLTimeUnit::Second, + PoSQLTimeZone::Utc, + rhs_timestamps.clone(), + ); + let result = lhs.element_wise_eq(&rhs); + assert_eq!( + result, + Ok(OwnedColumn::::Boolean(vec![ + true, true, true + ])) + ); + + // TimestampTZ with different time zones + let lhs = OwnedColumn::::TimestampTZ( + PoSQLTimeUnit::Second, + PoSQLTimeZone::Utc, + lhs_timestamps.clone(), + ); + let rhs = OwnedColumn::::TimestampTZ( + PoSQLTimeUnit::Second, + PoSQLTimeZone::FixedOffset(3600), // UTC+1 + rhs_timestamps.clone(), + ); + let result = lhs.element_wise_eq(&rhs); + assert_eq!( + result, + Ok(OwnedColumn::::Boolean(vec![ + false, false, false + ])) + ); + + // TimestampTZ with different precision (seconds vs milliseconds) + let lhs = OwnedColumn::::TimestampTZ( + PoSQLTimeUnit::Second, + PoSQLTimeZone::Utc, + lhs_timestamps, + ); + let rhs_milliseconds = vec![1231006505000, 1231006511000, 1231006520000]; // Converted to milliseconds + let rhs = OwnedColumn::::TimestampTZ( + PoSQLTimeUnit::Millisecond, + PoSQLTimeZone::Utc, + rhs_milliseconds, + ); + let result = lhs.element_wise_eq(&rhs); + assert_eq!( + result, + Ok(OwnedColumn::::Boolean(vec![ + true, false, true // Precision mismatch should still work after adjustment + ])) + ); } #[test] - fn we_can_do_le_operation_on_numeric_and_boolean_columns() { + fn we_can_do_le_operation_on_numeric_boolean_and_timestamp_columns() { // Booleans let lhs = OwnedColumn::::Boolean(vec![true, false, true]); let rhs = OwnedColumn::::Boolean(vec![true, true, false]); @@ -1693,7 +1864,7 @@ mod test { } #[test] - fn we_can_do_ge_operation_on_numeric_and_boolean_columns() { + fn we_can_do_ge_operation_on_numeric_boolean_and_timestamp_columns() { // Booleans let lhs = OwnedColumn::::Boolean(vec![true, false, true]); let rhs = OwnedColumn::::Boolean(vec![true, true, false]); @@ -1765,6 +1936,66 @@ mod test { true, true, false ])) ); + + // TimestampTZ + let lhs_timestamps = vec![1231006505, 1231006510, 1231006520]; // Unix timestamps in seconds + let rhs_timestamps = vec![1231006504, 1231006509, 1231006521]; // Slight difference + let lhs = OwnedColumn::::TimestampTZ( + PoSQLTimeUnit::Second, + PoSQLTimeZone::Utc, + lhs_timestamps.clone(), + ); + let rhs = OwnedColumn::::TimestampTZ( + PoSQLTimeUnit::Second, + PoSQLTimeZone::Utc, + rhs_timestamps.clone(), + ); + let result = lhs.element_wise_ge(&rhs); + assert_eq!( + result, + Ok(OwnedColumn::::Boolean(vec![ + true, true, false + ])) + ); + + // TimestampTZ with different time zones + let lhs = OwnedColumn::::TimestampTZ( + PoSQLTimeUnit::Second, + PoSQLTimeZone::Utc, + lhs_timestamps.clone(), + ); + let rhs = OwnedColumn::::TimestampTZ( + PoSQLTimeUnit::Second, + PoSQLTimeZone::FixedOffset(3600), // UTC+1 + rhs_timestamps.clone(), + ); + let result = lhs.element_wise_ge(&rhs); + assert_eq!( + result, + Ok(OwnedColumn::::Boolean(vec![ + true, true, true + ])) + ); + + // TimestampTZ with different precision (seconds vs milliseconds) + let lhs = OwnedColumn::::TimestampTZ( + PoSQLTimeUnit::Second, + PoSQLTimeZone::Utc, + lhs_timestamps, + ); + let rhs_milliseconds = vec![1231006505000, 1231006510000, 1231006521000]; // Converted to milliseconds + let rhs = OwnedColumn::::TimestampTZ( + PoSQLTimeUnit::Millisecond, + PoSQLTimeZone::Utc, + rhs_milliseconds, + ); + let result = lhs.element_wise_ge(&rhs); + assert_eq!( + result, + Ok(OwnedColumn::::Boolean(vec![ + true, true, false // Precision mismatch should still work after adjustment + ])) + ); } #[test]