From 600d8f09e6c12ed503104c3e8df2a8232249ed6a Mon Sep 17 00:00:00 2001 From: wszhdshys <1925792291@qq.com> Date: Sat, 19 Jul 2025 14:52:27 +0800 Subject: [PATCH 1/3] feat: TimeStamp types that support precision and time zones, as well as conversion of related types, are implemented. Added precision support for the Time type. current_timestamp function added --- src/db.rs | 2 + src/function/current_timestamp.rs | 56 +++++ src/function/mod.rs | 1 + src/optimizer/core/histogram.rs | 10 +- src/types/evaluator/mod.rs | 29 ++- src/types/evaluator/time.rs | 208 ++++++++++++++++- src/types/evaluator/time64.rs | 123 ++++++++++ src/types/mod.rs | 90 ++++++-- src/types/value.rs | 364 +++++++++++++++++++++++++++--- tests/slt/sql_2016/E141_07.slt | 10 +- tests/slt/time.slt | 56 +++++ tests/slt/timestamp.slt | 85 +++++++ 12 files changed, 975 insertions(+), 59 deletions(-) create mode 100644 src/function/current_timestamp.rs create mode 100644 src/types/evaluator/time64.rs create mode 100644 tests/slt/time.slt create mode 100644 tests/slt/timestamp.slt diff --git a/src/db.rs b/src/db.rs index 5605fd19..ff08a4fa 100644 --- a/src/db.rs +++ b/src/db.rs @@ -6,6 +6,7 @@ use crate::expression::function::table::TableFunctionImpl; use crate::expression::function::FunctionSummary; use crate::function::char_length::CharLength; use crate::function::current_date::CurrentDate; +use crate::function::current_timestamp::CurrentTimeStamp; use crate::function::lower::Lower; use crate::function::numbers::Numbers; use crate::function::octet_length::OctetLength; @@ -61,6 +62,7 @@ impl DataBaseBuilder { builder = builder.register_scala_function(CharLength::new("character_length".to_lowercase())); builder = builder.register_scala_function(CurrentDate::new()); + builder = builder.register_scala_function(CurrentTimeStamp::new()); builder = builder.register_scala_function(Lower::new()); builder = builder.register_scala_function(OctetLength::new()); builder = builder.register_scala_function(Upper::new()); diff --git a/src/function/current_timestamp.rs b/src/function/current_timestamp.rs new file mode 100644 index 00000000..96a4fe07 --- /dev/null +++ b/src/function/current_timestamp.rs @@ -0,0 +1,56 @@ +use crate::catalog::ColumnRef; +use crate::errors::DatabaseError; +use crate::expression::function::scala::FuncMonotonicity; +use crate::expression::function::scala::ScalarFunctionImpl; +use crate::expression::function::FunctionSummary; +use crate::expression::ScalarExpression; +use crate::types::tuple::Tuple; +use crate::types::value::DataValue; +use crate::types::LogicalType; +use chrono::Utc; +use serde::Deserialize; +use serde::Serialize; +use std::sync::Arc; + +#[derive(Debug, Serialize, Deserialize)] +pub(crate) struct CurrentTimeStamp { + summary: FunctionSummary, +} + +impl CurrentTimeStamp { + #[allow(unused_mut)] + pub(crate) fn new() -> Arc { + let function_name = "current_timestamp".to_lowercase(); + + Arc::new(Self { + summary: FunctionSummary { + name: function_name, + arg_types: Vec::new(), + }, + }) + } +} + +#[typetag::serde] +impl ScalarFunctionImpl for CurrentTimeStamp { + #[allow(unused_variables, clippy::redundant_closure_call)] + fn eval( + &self, + _: &[ScalarExpression], + _: Option<(&Tuple, &[ColumnRef])>, + ) -> Result { + Ok(DataValue::Time64(Utc::now().timestamp(), 0, false)) + } + + fn monotonicity(&self) -> Option { + todo!() + } + + fn return_type(&self) -> &LogicalType { + &LogicalType::TimeStamp(None, false) + } + + fn summary(&self) -> &FunctionSummary { + &self.summary + } +} diff --git a/src/function/mod.rs b/src/function/mod.rs index 7930e807..0fadd0ca 100644 --- a/src/function/mod.rs +++ b/src/function/mod.rs @@ -1,5 +1,6 @@ pub(crate) mod char_length; pub(crate) mod current_date; +pub(crate) mod current_timestamp; pub(crate) mod lower; pub(crate) mod numbers; pub(crate) mod octet_length; diff --git a/src/optimizer/core/histogram.rs b/src/optimizer/core/histogram.rs index 2bb1c34f..eebbaf31 100644 --- a/src/optimizer/core/histogram.rs +++ b/src/optimizer/core/histogram.rs @@ -329,14 +329,20 @@ impl Histogram { } _ => unreachable!(), }, - LogicalType::Date | LogicalType::DateTime | LogicalType::Time => match value { + LogicalType::Date + | LogicalType::DateTime + | LogicalType::Time(_, _) + | LogicalType::TimeStamp(_, _) => match value { DataValue::Date32(value) => DataValue::Int32(*value) .cast(&LogicalType::Double)? .double(), DataValue::Date64(value) => DataValue::Int64(*value) .cast(&LogicalType::Double)? .double(), - DataValue::Time(value) => DataValue::UInt32(*value) + DataValue::Time(value, ..) => DataValue::UInt32(*value) + .cast(&LogicalType::Double)? + .double(), + DataValue::Time64(value, ..) => DataValue::Int64(*value) .cast(&LogicalType::Double)? .double(), _ => unreachable!(), diff --git a/src/types/evaluator/mod.rs b/src/types/evaluator/mod.rs index 9ec97159..2a1cae5d 100644 --- a/src/types/evaluator/mod.rs +++ b/src/types/evaluator/mod.rs @@ -10,6 +10,7 @@ pub mod int64; pub mod int8; pub mod null; pub mod time; +pub mod time64; pub mod tuple; pub mod uint16; pub mod uint32; @@ -31,6 +32,7 @@ use crate::types::evaluator::int64::*; use crate::types::evaluator::int8::*; use crate::types::evaluator::null::NullBinaryEvaluator; use crate::types::evaluator::time::*; +use crate::types::evaluator::time64::*; use crate::types::evaluator::tuple::{ TupleEqBinaryEvaluator, TupleGtBinaryEvaluator, TupleGtEqBinaryEvaluator, TupleLtBinaryEvaluator, TupleLtEqBinaryEvaluator, TupleNotEqBinaryEvaluator, @@ -191,7 +193,32 @@ impl EvaluatorFactory { LogicalType::Double => numeric_binary_evaluator!(Float64, op, LogicalType::Double), LogicalType::Date => numeric_binary_evaluator!(Date, op, LogicalType::Date), LogicalType::DateTime => numeric_binary_evaluator!(DateTime, op, LogicalType::DateTime), - LogicalType::Time => numeric_binary_evaluator!(Time, op, LogicalType::Time), + LogicalType::Time(_, _) => match op { + BinaryOperator::Plus => Ok(BinaryEvaluatorBox(Arc::new(TimePlusBinaryEvaluator))), + BinaryOperator::Minus => Ok(BinaryEvaluatorBox(Arc::new(TimeMinusBinaryEvaluator))), + BinaryOperator::Gt => Ok(BinaryEvaluatorBox(Arc::new(TimeGtBinaryEvaluator))), + BinaryOperator::GtEq => Ok(BinaryEvaluatorBox(Arc::new(TimeGtEqBinaryEvaluator))), + BinaryOperator::Lt => Ok(BinaryEvaluatorBox(Arc::new(TimeLtBinaryEvaluator))), + BinaryOperator::LtEq => Ok(BinaryEvaluatorBox(Arc::new(TimeLtEqBinaryEvaluator))), + BinaryOperator::Eq => Ok(BinaryEvaluatorBox(Arc::new(TimeEqBinaryEvaluator))), + BinaryOperator::NotEq => Ok(BinaryEvaluatorBox(Arc::new(TimeNotEqBinaryEvaluator))), + _ => Err(DatabaseError::UnsupportedBinaryOperator(ty, op)), + }, + LogicalType::TimeStamp(_, _) => match op { + BinaryOperator::Plus => Ok(BinaryEvaluatorBox(Arc::new(Time64PlusBinaryEvaluator))), + BinaryOperator::Minus => { + Ok(BinaryEvaluatorBox(Arc::new(Time64MinusBinaryEvaluator))) + } + BinaryOperator::Gt => Ok(BinaryEvaluatorBox(Arc::new(Time64GtBinaryEvaluator))), + BinaryOperator::GtEq => Ok(BinaryEvaluatorBox(Arc::new(Time64GtEqBinaryEvaluator))), + BinaryOperator::Lt => Ok(BinaryEvaluatorBox(Arc::new(Time64LtBinaryEvaluator))), + BinaryOperator::LtEq => Ok(BinaryEvaluatorBox(Arc::new(Time64LtEqBinaryEvaluator))), + BinaryOperator::Eq => Ok(BinaryEvaluatorBox(Arc::new(Time64EqBinaryEvaluator))), + BinaryOperator::NotEq => { + Ok(BinaryEvaluatorBox(Arc::new(Time64NotEqBinaryEvaluator))) + } + _ => Err(DatabaseError::UnsupportedBinaryOperator(ty, op)), + }, LogicalType::Decimal(_, _) => numeric_binary_evaluator!(Decimal, op, ty), LogicalType::Boolean => match op { BinaryOperator::And => Ok(BinaryEvaluatorBox(Arc::new(BooleanAndBinaryEvaluator))), diff --git a/src/types/evaluator/time.rs b/src/types/evaluator/time.rs index 6264eed1..4ca6e2b3 100644 --- a/src/types/evaluator/time.rs +++ b/src/types/evaluator/time.rs @@ -1,8 +1,210 @@ -use crate::numeric_binary_evaluator_definition; use crate::types::evaluator::BinaryEvaluator; use crate::types::evaluator::DataValue; -use paste::paste; use serde::{Deserialize, Serialize}; use std::hint; -numeric_binary_evaluator_definition!(Time, DataValue::Time); +#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] +pub struct TimePlusBinaryEvaluator; +#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] +pub struct TimeMinusBinaryEvaluator; +#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] +pub struct TimeGtBinaryEvaluator; +#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] +pub struct TimeGtEqBinaryEvaluator; +#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] +pub struct TimeLtBinaryEvaluator; +#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] +pub struct TimeLtEqBinaryEvaluator; +#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] +pub struct TimeEqBinaryEvaluator; +#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] +pub struct TimeNotEqBinaryEvaluator; + +#[typetag::serde] +impl BinaryEvaluator for TimePlusBinaryEvaluator { + fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { + match (left, right) { + (DataValue::Time(v1, p1, _), DataValue::Time(v2, p2, ..)) => { + let (mut v1, n1) = DataValue::unpack(*v1, *p1); + let (v2, n2) = DataValue::unpack(*v2, *p2); + let mut n = n1 + n2; + while n > 1_000_000_000 { + v1 += 1; + n -= 1_000_000_000; + } + let p = if p2 > p1 { *p2 } else { *p1 }; + if v1 + v2 > 86400 { + return DataValue::Null; + } + DataValue::Time(DataValue::pack(v1 + v2, n, p), p, false) + } + (DataValue::Time(..), DataValue::Null) + | (DataValue::Null, DataValue::Time(..)) + | (DataValue::Null, DataValue::Null) => DataValue::Null, + _ => unsafe { hint::unreachable_unchecked() }, + } + } +} +#[typetag::serde] +impl BinaryEvaluator for TimeMinusBinaryEvaluator { + fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { + match (left, right) { + (DataValue::Time(v1, p1, _), DataValue::Time(v2, p2, ..)) => { + let (mut v1, mut n1) = DataValue::unpack(*v1, *p1); + let (v2, n2) = DataValue::unpack(*v2, *p2); + while n1 < n2 { + v1 -= 1; + n1 += 1_000_000_000; + } + if v1 < v2 { + return DataValue::Null; + } + let p = if p2 > p1 { *p2 } else { *p1 }; + DataValue::Time(DataValue::pack(v1 - v2, n1 - n2, p), p, false) + } + (DataValue::Time(..), DataValue::Null) + | (DataValue::Null, DataValue::Time(..)) + | (DataValue::Null, DataValue::Null) => DataValue::Null, + _ => unsafe { hint::unreachable_unchecked() }, + } + } +} + +#[typetag::serde] +impl BinaryEvaluator for TimeGtBinaryEvaluator { + fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { + match (left, right) { + (DataValue::Time(v1, p1, _), DataValue::Time(v2, p2, ..)) => { + let (v1, n1) = DataValue::unpack(*v1, *p1); + let (v2, n2) = DataValue::unpack(*v2, *p2); + match v1 > v2 { + true => DataValue::Boolean(true), + false => match v1 < v2 { + true => DataValue::Boolean(false), + false => match n1 > n2 { + true => DataValue::Boolean(true), + false => DataValue::Boolean(false), + }, + }, + } + } + (DataValue::Time(..), DataValue::Null) + | (DataValue::Null, DataValue::Time(..)) + | (DataValue::Null, DataValue::Null) => DataValue::Null, + _ => unsafe { hint::unreachable_unchecked() }, + } + } +} +#[typetag::serde] +impl BinaryEvaluator for TimeGtEqBinaryEvaluator { + fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { + match (left, right) { + (DataValue::Time(v1, p1, _), DataValue::Time(v2, p2, ..)) => { + let (v1, n1) = DataValue::unpack(*v1, *p1); + let (v2, n2) = DataValue::unpack(*v2, *p2); + match v1 > v2 { + true => DataValue::Boolean(true), + false => match v1 < v2 { + true => DataValue::Boolean(false), + false => match n1 >= n2 { + true => DataValue::Boolean(true), + false => DataValue::Boolean(false), + }, + }, + } + } + (DataValue::Time(..), DataValue::Null) + | (DataValue::Null, DataValue::Time(..)) + | (DataValue::Null, DataValue::Null) => DataValue::Null, + _ => unsafe { hint::unreachable_unchecked() }, + } + } +} +#[typetag::serde] +impl BinaryEvaluator for TimeLtBinaryEvaluator { + fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { + match (left, right) { + (DataValue::Time(v1, p1, _), DataValue::Time(v2, p2, ..)) => { + let (v1, n1) = DataValue::unpack(*v1, *p1); + let (v2, n2) = DataValue::unpack(*v2, *p2); + match v1 < v2 { + true => DataValue::Boolean(true), + false => match v1 > v2 { + true => DataValue::Boolean(false), + false => match n1 < n2 { + true => DataValue::Boolean(true), + false => DataValue::Boolean(false), + }, + }, + } + } + (DataValue::Time(..), DataValue::Null) + | (DataValue::Null, DataValue::Time(..)) + | (DataValue::Null, DataValue::Null) => DataValue::Null, + _ => unsafe { hint::unreachable_unchecked() }, + } + } +} +#[typetag::serde] +impl BinaryEvaluator for TimeLtEqBinaryEvaluator { + fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { + match (left, right) { + (DataValue::Time(v1, p1, _), DataValue::Time(v2, p2, ..)) => { + let (v1, n1) = DataValue::unpack(*v1, *p1); + let (v2, n2) = DataValue::unpack(*v2, *p2); + match v1 < v2 { + true => DataValue::Boolean(true), + false => match v1 > v2 { + true => DataValue::Boolean(false), + false => match n1 <= n2 { + true => DataValue::Boolean(true), + false => DataValue::Boolean(false), + }, + }, + } + } + (DataValue::Time(..), DataValue::Null) + | (DataValue::Null, DataValue::Time(..)) + | (DataValue::Null, DataValue::Null) => DataValue::Null, + _ => unsafe { hint::unreachable_unchecked() }, + } + } +} +#[typetag::serde] +impl BinaryEvaluator for TimeEqBinaryEvaluator { + fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { + match (left, right) { + (DataValue::Time(v1, p1, _), DataValue::Time(v2, p2, ..)) => { + let (v1, n1) = DataValue::unpack(*v1, *p1); + let (v2, n2) = DataValue::unpack(*v2, *p2); + match (v1 == v2, n1 == n2) { + (true, true) => DataValue::Boolean(true), + _ => DataValue::Boolean(false), + } + } + (DataValue::Time(..), DataValue::Null) + | (DataValue::Null, DataValue::Time(..)) + | (DataValue::Null, DataValue::Null) => DataValue::Null, + _ => unsafe { hint::unreachable_unchecked() }, + } + } +} +#[typetag::serde] +impl BinaryEvaluator for TimeNotEqBinaryEvaluator { + fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { + match (left, right) { + (DataValue::Time(v1, p1, _), DataValue::Time(v2, p2, ..)) => { + let (v1, n1) = DataValue::unpack(*v1, *p1); + let (v2, n2) = DataValue::unpack(*v2, *p2); + match (v1 != v2, n1 != n2) { + (false, false) => DataValue::Boolean(false), + _ => DataValue::Boolean(true), + } + } + (DataValue::Time(..), DataValue::Null) + | (DataValue::Null, DataValue::Time(..)) + | (DataValue::Null, DataValue::Null) => DataValue::Null, + _ => unsafe { hint::unreachable_unchecked() }, + } + } +} diff --git a/src/types/evaluator/time64.rs b/src/types/evaluator/time64.rs new file mode 100644 index 00000000..13b1cdfa --- /dev/null +++ b/src/types/evaluator/time64.rs @@ -0,0 +1,123 @@ +use crate::types::evaluator::BinaryEvaluator; +use crate::types::evaluator::DataValue; +use serde::{Deserialize, Serialize}; +use std::hint; + +#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] +pub struct Time64PlusBinaryEvaluator; +#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] +pub struct Time64MinusBinaryEvaluator; +#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] +pub struct Time64GtBinaryEvaluator; +#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] +pub struct Time64GtEqBinaryEvaluator; +#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] +pub struct Time64LtBinaryEvaluator; +#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] +pub struct Time64LtEqBinaryEvaluator; +#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] +pub struct Time64EqBinaryEvaluator; +#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] +pub struct Time64NotEqBinaryEvaluator; + +#[typetag::serde] +impl BinaryEvaluator for Time64PlusBinaryEvaluator { + fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { + match (left, right) { + (DataValue::Time64(v1, p1, _), DataValue::Time64(v2, p2, ..)) => { + DataValue::Time64(v1 + v2, if p2 > p1 { *p2 } else { *p1 }, false) + } + (DataValue::Time64(..), DataValue::Null) + | (DataValue::Null, DataValue::Time64(..)) + | (DataValue::Null, DataValue::Null) => DataValue::Null, + _ => unsafe { hint::unreachable_unchecked() }, + } + } +} +#[typetag::serde] +impl BinaryEvaluator for Time64MinusBinaryEvaluator { + fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { + match (left, right) { + (DataValue::Time64(v1, p1, _), DataValue::Time64(v2, p2, ..)) => { + DataValue::Time64(v1 - v2, if p2 > p1 { *p2 } else { *p1 }, false) + } + (DataValue::Time64(..), DataValue::Null) + | (DataValue::Null, DataValue::Time64(..)) + | (DataValue::Null, DataValue::Null) => DataValue::Null, + _ => unsafe { hint::unreachable_unchecked() }, + } + } +} + +#[typetag::serde] +impl BinaryEvaluator for Time64GtBinaryEvaluator { + fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { + match (left, right) { + (DataValue::Time64(v1, ..), DataValue::Time64(v2, ..)) => DataValue::Boolean(v1 > v2), + (DataValue::Time64(..), DataValue::Null) + | (DataValue::Null, DataValue::Time64(..)) + | (DataValue::Null, DataValue::Null) => DataValue::Null, + _ => unsafe { hint::unreachable_unchecked() }, + } + } +} +#[typetag::serde] +impl BinaryEvaluator for Time64GtEqBinaryEvaluator { + fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { + match (left, right) { + (DataValue::Time64(v1, ..), DataValue::Time64(v2, ..)) => DataValue::Boolean(v1 >= v2), + (DataValue::Time64(..), DataValue::Null) + | (DataValue::Null, DataValue::Time64(..)) + | (DataValue::Null, DataValue::Null) => DataValue::Null, + _ => unsafe { hint::unreachable_unchecked() }, + } + } +} +#[typetag::serde] +impl BinaryEvaluator for Time64LtBinaryEvaluator { + fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { + match (left, right) { + (DataValue::Time64(v1, ..), DataValue::Time64(v2, ..)) => DataValue::Boolean(v1 < v2), + (DataValue::Time64(..), DataValue::Null) + | (DataValue::Null, DataValue::Time64(..)) + | (DataValue::Null, DataValue::Null) => DataValue::Null, + _ => unsafe { hint::unreachable_unchecked() }, + } + } +} +#[typetag::serde] +impl BinaryEvaluator for Time64LtEqBinaryEvaluator { + fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { + match (left, right) { + (DataValue::Time64(v1, ..), DataValue::Time64(v2, ..)) => DataValue::Boolean(v1 <= v2), + (DataValue::Time64(..), DataValue::Null) + | (DataValue::Null, DataValue::Time64(..)) + | (DataValue::Null, DataValue::Null) => DataValue::Null, + _ => unsafe { hint::unreachable_unchecked() }, + } + } +} +#[typetag::serde] +impl BinaryEvaluator for Time64EqBinaryEvaluator { + fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { + match (left, right) { + (DataValue::Time64(v1, ..), DataValue::Time64(v2, ..)) => DataValue::Boolean(v1 == v2), + (DataValue::Time64(..), DataValue::Null) + | (DataValue::Null, DataValue::Time64(..)) + | (DataValue::Null, DataValue::Null) => DataValue::Null, + _ => unsafe { hint::unreachable_unchecked() }, + } + } +} +#[typetag::serde] +impl BinaryEvaluator for Time64NotEqBinaryEvaluator { + fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { + match (left, right) { + (DataValue::Time64(v1, ..), DataValue::Time64(v2, ..)) => DataValue::Boolean(v1 != v2), + (DataValue::Time64(..), DataValue::Null) + | (DataValue::Null, DataValue::Time64(..)) + | (DataValue::Null, DataValue::Null) => DataValue::Null, + _ => unsafe { hint::unreachable_unchecked() }, + } + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index f8bf299a..de3e83fb 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -48,7 +48,8 @@ pub enum LogicalType { Varchar(Option, CharLengthUnits), Date, DateTime, - Time, + Time(Option, bool), + TimeStamp(Option, bool), // decimal (precision, scale) Decimal(Option, Option), Tuple(Vec), @@ -83,7 +84,7 @@ impl LogicalType { } else if type_id == TypeId::of::() { Some(LogicalType::DateTime) } else if type_id == TypeId::of::() { - Some(LogicalType::Time) + Some(LogicalType::Time(Some(0), false)) } else if type_id == TypeId::of::() { Some(LogicalType::Decimal(None, None)) } else if type_id == TypeId::of::() { @@ -116,7 +117,8 @@ impl LogicalType { LogicalType::Decimal(_, _) => Some(16), LogicalType::Date => Some(4), LogicalType::DateTime => Some(8), - LogicalType::Time => Some(4), + LogicalType::Time(_, _) => Some(4), + LogicalType::TimeStamp(_, _) => Some(8), LogicalType::Tuple(_) => unreachable!(), } } @@ -354,14 +356,14 @@ impl LogicalType { to, LogicalType::DateTime | LogicalType::Varchar(..) | LogicalType::Char(..) ), - LogicalType::DateTime => matches!( + LogicalType::DateTime | LogicalType::TimeStamp(_, _) => matches!( to, LogicalType::Date - | LogicalType::Time + | LogicalType::Time(..) | LogicalType::Varchar(..) | LogicalType::Char(..) ), - LogicalType::Time => { + LogicalType::Time(..) => { matches!(to, LogicalType::Varchar(..) | LogicalType::Char(..)) } LogicalType::Decimal(_, _) | LogicalType::Tuple(_) => false, @@ -432,17 +434,35 @@ impl TryFrom for LogicalType { Ok(LogicalType::DateTime) } sqlparser::ast::DataType::Time(precision, info) => { - if precision.is_some() { - return Err(DatabaseError::UnsupportedStmt( - "time's precision".to_string(), - )); + match precision { + Some(0..5) | None => (), + _ => { + return Err(DatabaseError::UnsupportedStmt( + "time's precision must less than 5".to_string(), + )) + } } if !matches!(info, TimezoneInfo::None) { return Err(DatabaseError::UnsupportedStmt( - "time's time zone".to_string(), + "time's zone is not supported".to_string(), )); } - Ok(LogicalType::Time) + Ok(LogicalType::Time(precision, false)) + } + sqlparser::ast::DataType::Timestamp(precision, info) => { + let mut zone = false; + match precision { + Some(3 | 6 | 9) | None => (), + _ => { + return Err(DatabaseError::UnsupportedStmt( + "timestamp's precision must 3,6,9".to_string(), + )) + } + } + if matches!(info, TimezoneInfo::WithTimeZone) { + zone = true; + } + Ok(LogicalType::TimeStamp(precision, zone)) } sqlparser::ast::DataType::Decimal(info) | sqlparser::ast::DataType::Dec(info) => { match info { @@ -479,7 +499,10 @@ impl std::fmt::Display for LogicalType { LogicalType::Varchar(len, units) => write!(f, "Varchar({:?}, {})", len, units)?, LogicalType::Date => write!(f, "Date")?, LogicalType::DateTime => write!(f, "DateTime")?, - LogicalType::Time => write!(f, "Time")?, + LogicalType::TimeStamp(precision, zone) => { + write!(f, "TimeStamp({:?}, {:?})", precision, zone)? + } + LogicalType::Time(precision, zone) => write!(f, "Time({:?}, {:?})", precision, zone)?, LogicalType::Decimal(precision, scale) => { write!(f, "Decimal({:?}, {:?})", precision, scale)? } @@ -576,7 +599,46 @@ pub(crate) mod test { )?; fn_assert(&mut cursor, &mut reference_tables, LogicalType::Date)?; fn_assert(&mut cursor, &mut reference_tables, LogicalType::DateTime)?; - fn_assert(&mut cursor, &mut reference_tables, LogicalType::Time)?; + fn_assert( + &mut cursor, + &mut reference_tables, + LogicalType::Time(Some(3), true), + )?; + fn_assert( + &mut cursor, + &mut reference_tables, + LogicalType::Time(Some(3), false), + )?; + fn_assert( + &mut cursor, + &mut reference_tables, + LogicalType::Time(None, true), + )?; + fn_assert( + &mut cursor, + &mut reference_tables, + LogicalType::Time(None, false), + )?; + fn_assert( + &mut cursor, + &mut reference_tables, + LogicalType::TimeStamp(Some(3), true), + )?; + fn_assert( + &mut cursor, + &mut reference_tables, + LogicalType::TimeStamp(Some(3), false), + )?; + fn_assert( + &mut cursor, + &mut reference_tables, + LogicalType::TimeStamp(None, true), + )?; + fn_assert( + &mut cursor, + &mut reference_tables, + LogicalType::TimeStamp(None, false), + )?; fn_assert( &mut cursor, &mut reference_tables, diff --git a/src/types/value.rs b/src/types/value.rs index 17f9af85..22dc95e5 100644 --- a/src/types/value.rs +++ b/src/types/value.rs @@ -1,6 +1,7 @@ use super::LogicalType; use crate::errors::DatabaseError; use crate::storage::table_codec::{BumpBytes, BOUND_MAX_TAG, BOUND_MIN_TAG}; +use crate::types::LogicalType::{Date, TimeStamp}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use chrono::format::{DelayedFormat, StrftimeItems}; use chrono::{DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; @@ -26,7 +27,13 @@ static UNIX_TIME: LazyLock = LazyLock::new(|| NaiveTime::from_hms_opt pub const DATE_FMT: &str = "%Y-%m-%d"; pub const DATE_TIME_FMT: &str = "%Y-%m-%d %H:%M:%S"; +pub const TIME_STAMP_FMT_WITHOUT_ZONE: &str = "%Y-%m-%d %H:%M:%S%.f"; +pub const TIME_STAMP_FMT_WITH_ZONE: &str = "%Y-%m-%d %H:%M:%S%.f%z"; +pub const TIME_STAMP_FMT_WITHOUT_PRECISION: &str = "%Y-%m-%d %H:%M:%S%z"; pub const TIME_FMT: &str = "%H:%M:%S"; +pub const TIME_FMT_WITHOUT_ZONE: &str = "%H:%M:%S%.f"; +pub const TIME_FMT_WITH_ZONE: &str = "%H:%M:%S%.f%z"; +pub const TIME_FMT_WITHOUT_PRECISION: &str = "%H:%M:%S%z"; const ENCODE_GROUP_SIZE: usize = 8; const ENCODE_MARKER: u8 = 0xFF; @@ -60,7 +67,8 @@ pub enum DataValue { Date32(i32), /// Date stored as a signed 64bit int timestamp since UNIX epoch 1970-01-01 Date64(i64), - Time(u32), + Time(u32, u64, bool), + Time64(i64, u64, bool), Decimal(Decimal), /// (values, is_upper) Tuple(Vec, bool), @@ -134,8 +142,10 @@ impl PartialEq for DataValue { (Date32(_), _) => false, (Date64(v1), Date64(v2)) => v1.eq(v2), (Date64(_), _) => false, - (Time(v1), Time(v2)) => v1.eq(v2), - (Time(_), _) => false, + (Time(v1, ..), Time(v2, ..)) => v1.eq(v2), + (Time(..), _) => false, + (Time64(v1, ..), Time64(v2, ..)) => v1.eq(v2), + (Time64(..), _) => false, (Decimal(v1), Decimal(v2)) => v1.eq(v2), (Decimal(_), _) => false, (Tuple(values_1, is_upper_1), Tuple(values_2, is_upper_2)) => { @@ -180,8 +190,10 @@ impl PartialOrd for DataValue { (Date32(_), _) => None, (Date64(v1), Date64(v2)) => v1.partial_cmp(v2), (Date64(_), _) => None, - (Time(v1), Time(v2)) => v1.partial_cmp(v2), - (Time(_), _) => None, + (Time(v1, ..), Time(v2, ..)) => v1.partial_cmp(v2), + (Time(..), _) => None, + (Time64(v1, ..), Time64(v2, ..)) => v1.partial_cmp(v2), + (Time64(..), _) => None, (Decimal(v1), Decimal(v2)) => v1.partial_cmp(v2), (Decimal(_), _) => None, (Tuple(..), _) => None, @@ -216,7 +228,8 @@ impl Hash for DataValue { Null => 1.hash(state), Date32(v) => v.hash(state), Date64(v) => v.hash(state), - Time(v) => v.hash(state), + Time(v, ..) => v.hash(state), + Time64(v, ..) => v.hash(state), Decimal(v) => v.hash(state), Tuple(values, is_upper) => { values.hash(state); @@ -293,7 +306,7 @@ impl DataValue { } pub fn time(&self) -> Option { - if let DataValue::Time(val) = self { + if let DataValue::Time(val, ..) = self { NaiveTime::from_num_seconds_from_midnight_opt(*val, 0) } else { None @@ -368,6 +381,34 @@ impl DataValue { Ok(()) } + pub fn pack(a: u32, b: u32, precision: u64) -> u32 { + assert!(b <= 1_000_000_000); + assert!(a <= 86_400); + // Scale down `a` to fit + let scaled_b = b / (1000000000 / 10_u32.pow(precision as u32)); // Now 0-1_000_000 + let p = match precision { + 1 => 28, + 2 => 25, + 3 => 22, + 4 => 18, + _ => 31, + }; + (scaled_b << p) | a + } + + pub fn unpack(combined: u32, precision: u64) -> (u32, u32) { + let p = match precision { + 1 => 28, + 2 => 25, + 3 => 22, + 4 => 18, + _ => 31, + }; + let scaled_a = combined >> p; + let b = combined & 2_u32.pow(p) - 1; + (b, scaled_a * (1000000000 / 10_u32.pow(precision as u32))) + } + fn format_date(value: i32) -> Option { Self::date_format(value).map(|fmt| format!("{}", fmt)) } @@ -376,8 +417,12 @@ impl DataValue { Self::date_time_format(value).map(|fmt| format!("{}", fmt)) } - fn format_time(value: u32) -> Option { - Self::time_format(value).map(|fmt| format!("{}", fmt)) + fn format_time(value: u32, presicion: u64) -> Option { + Self::time_format(value, presicion, false).map(|fmt| format!("{}", fmt)) + } + + fn format_timestamp(value: i64, precision: u64) -> Option { + Self::time_stamp_format(value, precision, false).map(|fmt| format!("{}", fmt)) } #[inline] @@ -412,7 +457,23 @@ impl DataValue { }, LogicalType::Date => DataValue::Date32(UNIX_DATETIME.num_days_from_ce()), LogicalType::DateTime => DataValue::Date64(UNIX_DATETIME.and_utc().timestamp()), - LogicalType::Time => DataValue::Time(UNIX_TIME.num_seconds_from_midnight()), + LogicalType::Time(precision, zone) => match precision { + Some(i) => DataValue::Time(UNIX_TIME.num_seconds_from_midnight(), *i, *zone), + None => DataValue::Time(UNIX_TIME.num_seconds_from_midnight(), 0, *zone), + }, + TimeStamp(precision, zone) => match precision { + Some(3) => DataValue::Time64(UNIX_DATETIME.and_utc().timestamp_millis(), 3, *zone), + Some(6) => DataValue::Time64(UNIX_DATETIME.and_utc().timestamp_micros(), 6, *zone), + Some(9) => { + if let Some(value) = UNIX_DATETIME.and_utc().timestamp_nanos_opt() { + DataValue::Time64(value, 9, *zone) + } else { + unreachable!() + } + } + None => DataValue::Time64(UNIX_DATETIME.and_utc().timestamp(), 0, *zone), + _ => unreachable!(), + }, LogicalType::Decimal(_, _) => DataValue::Decimal(Decimal::new(0, 0)), LogicalType::Tuple(types) => { let values = types.iter().map(DataValue::init).collect_vec(); @@ -509,10 +570,14 @@ impl DataValue { writer.write_i64::(*v)?; return Ok(()); } - DataValue::Time(v) => { + DataValue::Time(v, ..) => { writer.write_u32::(*v)?; return Ok(()); } + DataValue::Time64(v, ..) => { + writer.write_i64::(*v)?; + return Ok(()); + } DataValue::Decimal(v) => { writer.write_all(&v.serialize())?; return Ok(()); @@ -665,12 +730,27 @@ impl DataValue { } DataValue::Date64(reader.read_i64::()?) } - LogicalType::Time => { + LogicalType::Time(precision, zone) => { + let precision = match precision { + Some(precision) => *precision, + None => 0, + }; if !is_projection { reader.seek(SeekFrom::Current(4))?; return Ok(None); } - DataValue::Time(reader.read_u32::()?) + DataValue::Time(reader.read_u32::()?, precision, *zone) + } + TimeStamp(precision, zone) => { + let precision = match precision { + Some(precision) => *precision, + None => 0, + }; + if !is_projection { + reader.seek(SeekFrom::Current(8))?; + return Ok(None); + } + DataValue::Time64(reader.read_i64::()?, precision, *zone) } LogicalType::Decimal(_, _) => { if !is_projection { @@ -714,7 +794,8 @@ impl DataValue { } => LogicalType::Char(*len, *unit), DataValue::Date32(_) => LogicalType::Date, DataValue::Date64(_) => LogicalType::DateTime, - DataValue::Time(_) => LogicalType::Time, + DataValue::Time(..) => LogicalType::Time(None, false), + DataValue::Time64(..) => TimeStamp(None, false), DataValue::Decimal(_) => LogicalType::Decimal(None, None), DataValue::Tuple(values, ..) => { let types = values.iter().map(|v| v.logical_type()).collect_vec(); @@ -781,12 +862,12 @@ impl DataValue { DataValue::Int32(v) | DataValue::Date32(v) => { encode_u!(b, *v as u32 ^ 0x80000000_u32) } - DataValue::Int64(v) | DataValue::Date64(v) => { + DataValue::Int64(v) | DataValue::Date64(v) | DataValue::Time64(v, ..) => { encode_u!(b, *v as u64 ^ 0x8000000000000000_u64) } DataValue::UInt8(v) => encode_u!(b, v), DataValue::UInt16(v) => encode_u!(b, v), - DataValue::UInt32(v) | DataValue::Time(v) => encode_u!(b, v), + DataValue::UInt32(v) | DataValue::Time(v, ..) => encode_u!(b, v), DataValue::UInt64(v) => encode_u!(b, v), DataValue::Utf8 { value: v, .. } => Self::encode_bytes(b, v.as_bytes()), DataValue::Boolean(v) => b.push(if *v { b'1' } else { b'0' }), @@ -1257,7 +1338,7 @@ impl DataValue { LogicalType::Varchar(len, unit) => { varchar_cast!(value, len, Utf8Type::Variable(*len), *unit) } - LogicalType::Date => { + Date => { let value = NaiveDate::parse_from_str(&value, DATE_FMT) .map(|date| date.num_days_from_ce())?; Ok(DataValue::Date32(value)) @@ -1272,11 +1353,84 @@ impl DataValue { Ok(DataValue::Date64(value)) } - LogicalType::Time => { - let value = NaiveTime::parse_from_str(&value, TIME_FMT) - .map(|time| time.num_seconds_from_midnight())?; - - Ok(DataValue::Time(value)) + LogicalType::Time(precision, zone) => { + let precision = match precision { + Some(precision) => *precision, + None => 0, + }; + let fmt = match (precision, *zone) { + (0, false) => TIME_FMT, + (0, true) => TIME_FMT_WITHOUT_PRECISION, + (1..5, false) => TIME_FMT_WITHOUT_ZONE, + _ => TIME_FMT_WITH_ZONE, + }; + let complete_value = if *zone { + match value.contains("+") { + false => format!("{}+00:00", value.clone()), + true => value.clone(), + } + } else { + value.clone() + }; + let (value, nano) = match precision { + 0 => ( + NaiveTime::parse_from_str(&complete_value, fmt) + .map(|time| time.num_seconds_from_midnight())?, + 0, + ), + _ => NaiveTime::parse_from_str(&complete_value, fmt) + .map(|time| (time.num_seconds_from_midnight(), time.nanosecond()))?, + }; + Ok(DataValue::Time( + Self::pack(value, nano, precision), + precision, + *zone, + )) + } + TimeStamp(precision, zone) => { + let precision = match precision { + Some(precision) => *precision, + None => 0, + }; + let fmt = match (precision, *zone) { + (0, false) => DATE_TIME_FMT, + (0, true) => TIME_STAMP_FMT_WITHOUT_PRECISION, + (3 | 6 | 9, false) => TIME_STAMP_FMT_WITHOUT_ZONE, + _ => TIME_STAMP_FMT_WITH_ZONE, + }; + let complete_value = if *zone { + match value.contains("+") { + false => format!("{}+00:00", value.clone()), + true => value.clone(), + } + } else { + value.clone() + }; + if precision == 0 && !*zone { + return Ok(DataValue::Time64( + NaiveDateTime::parse_from_str(&complete_value, fmt) + .map(|date_time| date_time.and_utc().timestamp())?, + precision, + *zone, + )); + } + let value = DateTime::parse_from_str(&complete_value, fmt); + let value = match precision { + 3 => value.map(|date_time| date_time.timestamp_millis())?, + 6 => value.map(|date_time| date_time.timestamp_micros())?, + 9 => { + if let Some(value) = + value.map(|date_time| date_time.timestamp_nanos_opt())? + { + value + } else { + return Err(DatabaseError::CastFail); + } + } + 0 => value.map(|date_time| date_time.timestamp())?, + _ => unreachable!(), + }; + Ok(DataValue::Time64(value, precision, *zone)) } LogicalType::Decimal(_, _) => Ok(DataValue::Decimal(Decimal::from_str(&value)?)), _ => Err(DatabaseError::CastFail), @@ -1340,20 +1494,31 @@ impl DataValue { Ok(DataValue::Date32(value)) } LogicalType::DateTime => Ok(DataValue::Date64(value)), - LogicalType::Time => { + LogicalType::Time(precision, zone) => { + let precision = match precision { + Some(precision) => *precision, + None => 0, + }; let value = DateTime::from_timestamp(value, 0) .map(|date_time| date_time.time().num_seconds_from_midnight()) .ok_or(DatabaseError::CastFail)?; - Ok(DataValue::Time(value)) + Ok(DataValue::Time(Self::pack(value, 0, 0), precision, *zone)) + } + TimeStamp(precision, zone) => { + let precision = match precision { + Some(precision) => *precision, + None => 0, + }; + Ok(DataValue::Time64(value, precision, *zone)) } _ => Err(DatabaseError::CastFail), }, - DataValue::Time(value) => match to { + DataValue::Time(value, precision, _) => match to { LogicalType::SqlNull => Ok(DataValue::Null), LogicalType::Char(len, unit) => { varchar_cast!( - Self::format_time(value).ok_or(DatabaseError::CastFail)?, + Self::format_time(value, precision).ok_or(DatabaseError::CastFail)?, Some(len), Utf8Type::Fixed(*len), *unit @@ -1361,7 +1526,7 @@ impl DataValue { } LogicalType::Varchar(len, unit) => { varchar_cast!( - Self::format_time(value).ok_or(DatabaseError::CastFail)?, + Self::format_time(value, precision).ok_or(DatabaseError::CastFail)?, len, Utf8Type::Variable(*len), *unit @@ -1369,6 +1534,93 @@ impl DataValue { } _ => Err(DatabaseError::CastFail), }, + DataValue::Time64(value, precision, _) => match to { + LogicalType::SqlNull => Ok(DataValue::Null), + LogicalType::Char(len, unit) => { + varchar_cast!( + Self::format_timestamp(value, precision).ok_or(DatabaseError::CastFail)?, + Some(len), + Utf8Type::Fixed(*len), + *unit + ) + } + LogicalType::Varchar(len, unit) => { + varchar_cast!( + Self::format_timestamp(value, precision).ok_or(DatabaseError::CastFail)?, + len, + Utf8Type::Variable(*len), + *unit + ) + } + Date => { + let value = match precision { + 0 => DateTime::from_timestamp(value, 0), + 3 => DateTime::from_timestamp_millis(value), + 6 => DateTime::from_timestamp_micros(value), + 9 => { + let secs = value.div_euclid(1_000_000_000); + let nsecs = value.rem_euclid(1_000_000_000) as u32; + DateTime::from_timestamp(secs, nsecs) + } + _ => unreachable!(), + } + .ok_or(DatabaseError::CastFail)? + .naive_utc() + .date() + .num_days_from_ce(); + + Ok(DataValue::Date32(value)) + } + LogicalType::DateTime => { + let value = match precision { + 0 => DateTime::from_timestamp(value, 0), + 3 => DateTime::from_timestamp_millis(value), + 6 => DateTime::from_timestamp_micros(value), + 9 => { + let secs = value.div_euclid(1_000_000_000); + let nsecs = value.rem_euclid(1_000_000_000) as u32; + DateTime::from_timestamp(secs, nsecs) + } + _ => unreachable!(), + } + .ok_or(DatabaseError::CastFail)? + .timestamp(); + Ok(DataValue::Date64(value)) + } + LogicalType::Time(p, zone) => { + let p = match p { + Some(p) => *p, + None => 0, + }; + let (value, nano) = match precision { + 0 => DateTime::from_timestamp(value, 0), + 3 => DateTime::from_timestamp_millis(value), + 6 => DateTime::from_timestamp_micros(value), + 9 => { + let secs = value.div_euclid(1_000_000_000); + let nsecs = value.rem_euclid(1_000_000_000) as u32; + DateTime::from_timestamp(secs, nsecs) + } + _ => unreachable!(), + } + .map(|date_time| { + ( + date_time.time().num_seconds_from_midnight(), + date_time.time().nanosecond(), + ) + }) + .ok_or(DatabaseError::CastFail)?; + Ok(DataValue::Time(Self::pack(value, nano, p), p, *zone)) + } + TimeStamp(precision, zone) => { + let precision = match precision { + Some(precision) => *precision, + None => 0, + }; + Ok(DataValue::Time64(value, precision, *zone)) + } + _ => Err(DatabaseError::CastFail), + }, DataValue::Decimal(value) => match to { LogicalType::SqlNull => Ok(DataValue::Null), LogicalType::Float => Ok(DataValue::Float32(OrderedFloat( @@ -1464,8 +1716,36 @@ impl DataValue { DateTime::from_timestamp(v, 0).map(|date_time| date_time.format(DATE_TIME_FMT)) } - fn time_format<'a>(v: u32) -> Option>> { - NaiveTime::from_num_seconds_from_midnight_opt(v, 0).map(|time| time.format(TIME_FMT)) + fn time_format<'a>( + v: u32, + precision: u64, + _zone: bool, + ) -> Option>> { + let (v, n) = Self::unpack(v, precision); + NaiveTime::from_num_seconds_from_midnight_opt(v, n) + .map(|time| time.format(TIME_FMT_WITHOUT_ZONE)) + } + + fn time_stamp_format<'a>( + v: i64, + precision: u64, + _zone: bool, + ) -> Option>> { + match precision { + 0 => DateTime::from_timestamp(v, 0) + .map(|date_time| date_time.format(TIME_STAMP_FMT_WITHOUT_ZONE)), + 3 => DateTime::from_timestamp_millis(v) + .map(|date_time| date_time.format(TIME_STAMP_FMT_WITHOUT_ZONE)), + 6 => DateTime::from_timestamp_micros(v) + .map(|date_time| date_time.format(TIME_STAMP_FMT_WITHOUT_ZONE)), + 9 => { + let secs = v.div_euclid(1_000_000_000); + let nsecs = v.rem_euclid(1_000_000_000) as u32; + DateTime::from_timestamp(secs, nsecs) + .map(|date_time| date_time.format(TIME_STAMP_FMT_WITHOUT_ZONE)) + } + _ => unreachable!(), + } } fn decimal_format(v: &Decimal) -> String { @@ -1594,14 +1874,22 @@ impl From> for DataValue { impl From<&NaiveTime> for DataValue { fn from(value: &NaiveTime) -> Self { - DataValue::Time(value.num_seconds_from_midnight()) + DataValue::Time( + Self::pack(value.num_seconds_from_midnight(), value.nanosecond(), 4), + 6, + false, + ) } } impl From> for DataValue { fn from(value: Option<&NaiveTime>) -> Self { if let Some(value) = value { - DataValue::Time(value.num_seconds_from_midnight()) + DataValue::Time( + Self::pack(value.num_seconds_from_midnight(), value.nanosecond(), 4), + 0, + false, + ) } else { DataValue::Null } @@ -1667,7 +1955,16 @@ impl fmt::Display for DataValue { DataValue::Null => write!(f, "null")?, DataValue::Date32(e) => write!(f, "{}", DataValue::date_format(*e).unwrap())?, DataValue::Date64(e) => write!(f, "{}", DataValue::date_time_format(*e).unwrap())?, - DataValue::Time(e) => write!(f, "{}", DataValue::time_format(*e).unwrap())?, + DataValue::Time(e, precision, zone) => write!( + f, + "{}", + DataValue::time_format(*e, *precision, *zone).unwrap() + )?, + DataValue::Time64(e, precision, zone) => write!( + f, + "{}", + DataValue::time_stamp_format(*e, *precision, *zone).unwrap() + )?, DataValue::Decimal(e) => write!(f, "{}", DataValue::decimal_format(e))?, DataValue::Tuple(values, ..) => { write!(f, "(")?; @@ -1704,7 +2001,8 @@ impl fmt::Debug for DataValue { DataValue::Null => write!(f, "null"), DataValue::Date32(_) => write!(f, "Date32({})", self), DataValue::Date64(_) => write!(f, "Date64({})", self), - DataValue::Time(_) => write!(f, "Time({})", self), + DataValue::Time(..) => write!(f, "Time({})", self), + DataValue::Time64(..) => write!(f, "Time64({})", self), DataValue::Decimal(_) => write!(f, "Decimal({})", self), DataValue::Tuple(..) => { write!(f, "Tuple({}", self)?; diff --git a/tests/slt/sql_2016/E141_07.slt b/tests/slt/sql_2016/E141_07.slt index b3684508..2392f590 100644 --- a/tests/slt/sql_2016/E141_07.slt +++ b/tests/slt/sql_2016/E141_07.slt @@ -23,13 +23,11 @@ CREATE TABLE TABLE_E141_07_02_01 ( ID INT PRIMARY KEY, A DATE DEFAULT CURRENT_DA # statement ok # CREATE TABLE TABLE_E141_07_09_01 ( A NAME DEFAULT USER ) -# TODO: WITH TIME ZONE & CURRENT_TIMESTAMP -# statement ok -# CREATE TABLE TABLE_E141_07_10_01 ( A TIME WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ) +statement error +CREATE TABLE TABLE_E141_07_10_01 ( ID INT PRIMARY KEY, A TIME WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ) -# TODO: TIMESTAMP -# statement ok -# CREATE TABLE TABLE_E141_07_11_01 ( A TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ) +statement ok +CREATE TABLE TABLE_E141_07_11_01 ( ID INT PRIMARY KEY, A TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ) # TODO: LOCALTIME # statement ok diff --git a/tests/slt/time.slt b/tests/slt/time.slt new file mode 100644 index 00000000..6589544a --- /dev/null +++ b/tests/slt/time.slt @@ -0,0 +1,56 @@ +statement ok +CREATE TABLE t1 ( ID INT PRIMARY KEY, A TIME(3) ) + +statement ok +INSERT INTO t1(id,A) VALUES (1,'15:12:55.123') + +query II +SELECT * FROM t1 +---- +1 15:12:55.123 + +statement ok +INSERT INTO t1(id,A) VALUES (2,'16:12:55.123456') + +query II +SELECT * FROM t1 +---- +1 15:12:55.123 +2 16:12:55.123 + +statement ok +INSERT INTO t1(id,A) VALUES (3,'17:12:55.12') + +query II +SELECT * FROM t1 +---- +1 15:12:55.123 +2 16:12:55.123 +3 17:12:55.120 + +statement ok +DROP TABLE t1 + +query I +SELECT CAST(TIME '05:42:55' AS VARCHAR(50)) +---- +05:42:55 + +query I +SELECT CAST(TIME(3) '05:42:55.154' AS VARCHAR(50)) +---- +05:42:55.154 + +statement ok +CREATE TABLE t2 ( ID INT PRIMARY KEY, A TIME ) + +statement ok +INSERT INTO t2(id,A) VALUES (1,'15:12:55') + +query II +SELECT * FROM t2 +---- +1 15:12:55 + +statement ok +DROP TABLE t2 diff --git a/tests/slt/timestamp.slt b/tests/slt/timestamp.slt new file mode 100644 index 00000000..42b3b819 --- /dev/null +++ b/tests/slt/timestamp.slt @@ -0,0 +1,85 @@ +statement ok +CREATE TABLE t1 ( ID INT PRIMARY KEY, A TIMESTAMP(6) WITH TIME ZONE ) + +statement ok +INSERT INTO t1(id,A) VALUES (1,'2025-01-05 15:12:55.123456+09:30') + +query II +SELECT * FROM t1 +---- +1 2025-01-05 05:42:55.123456 + +statement ok +INSERT INTO t1(id,A) VALUES (2,'2025-01-06 15:12:55.123+09:30') + +query II +SELECT * FROM t1 +---- +1 2025-01-05 05:42:55.123456 +2 2025-01-06 05:42:55.123 + +statement ok +INSERT INTO t1(id,A) VALUES (3,'2025-01-07 15:12:55.123456789+09:30') + +statement ok +INSERT INTO t1(id,A) VALUES (4,'2025-01-07 15:12:55.123456') + +query II +SELECT * FROM t1 +---- +1 2025-01-05 05:42:55.123456 +2 2025-01-06 05:42:55.123 +3 2025-01-07 05:42:55.123456 +4 2025-01-07 15:12:55.123456 + +statement ok +DROP TABLE t1 + +query I +SELECT CAST(TIMESTAMP '2025-01-05 05:42:55' AS VARCHAR(50)) +---- +2025-01-05 05:42:55 + +query I +SELECT CAST(TIMESTAMP WITH TIME ZONE '2025-01-05 05:42:55+04:00' AS VARCHAR(50)) +---- +2025-01-05 01:42:55 + +query I +SELECT CAST(TIMESTAMP(6) WITH TIME ZONE '2025-01-05 05:42:55.154814+04:00' AS VARCHAR(50)) +---- +2025-01-05 01:42:55.154814 + +query I +SELECT CAST(TIMESTAMP(6) WITH TIME ZONE '2025-01-05 05:42:55.154814+04:00' AS TIME) +---- +01:42:55 + +query I +SELECT CAST(TIMESTAMP(6) WITH TIME ZONE '2025-01-05 05:42:55.154814+04:00' AS TIME(3)) +---- +01:42:55.154 + +query I +SELECT CAST(TIMESTAMP(6) WITH TIME ZONE '2025-01-05 05:42:55.154814+04:00' AS DATE) +---- +2025-01-05 + +query I +SELECT CAST(TIMESTAMP(6) WITH TIME ZONE '2025-01-05 05:42:55.154814+04:00' AS DATETIME) +---- +2025-01-05 01:42:55 + +statement ok +CREATE TABLE t2 ( ID INT PRIMARY KEY, A TIMESTAMP ) + +statement ok +INSERT INTO t2(id,A) VALUES (1,'2025-01-05 15:12:55') + +query II +SELECT * FROM t2 +---- +1 2025-01-05 15:12:55 + +statement ok +DROP TABLE t2 From 1cbf2ed4921ce677e3e4fc244489fb48baac75e6 Mon Sep 17 00:00:00 2001 From: wszhdshys <1925792291@qq.com> Date: Sun, 20 Jul 2025 14:28:03 +0800 Subject: [PATCH 2/3] Added tests on time32 and time64 operators --- docs/features.md | 2 + src/optimizer/core/histogram.rs | 2 +- src/types/evaluator/mod.rs | 166 ++++++++++++++++++++- src/types/evaluator/{time.rs => time32.rs} | 115 +++++--------- src/types/evaluator/time64.rs | 123 ++++++++++----- src/types/value.rs | 154 +++++++++---------- 6 files changed, 356 insertions(+), 206 deletions(-) rename src/types/evaluator/{time.rs => time32.rs} (56%) diff --git a/docs/features.md b/docs/features.md index efe9ecb9..9f6f88ff 100644 --- a/docs/features.md +++ b/docs/features.md @@ -133,6 +133,7 @@ let kite_sql = DataBaseBuilder::path("./data") - Right - Full - Cross (Natural\Using) +- [x] Exists - [x] Group By - [x] Having - [x] Order By @@ -170,4 +171,5 @@ let kite_sql = DataBaseBuilder::path("./data") - Date - DateTime - Time +- TimeStamp - Tuple \ No newline at end of file diff --git a/src/optimizer/core/histogram.rs b/src/optimizer/core/histogram.rs index eebbaf31..000001e7 100644 --- a/src/optimizer/core/histogram.rs +++ b/src/optimizer/core/histogram.rs @@ -339,7 +339,7 @@ impl Histogram { DataValue::Date64(value) => DataValue::Int64(*value) .cast(&LogicalType::Double)? .double(), - DataValue::Time(value, ..) => DataValue::UInt32(*value) + DataValue::Time32(value, ..) => DataValue::UInt32(*value) .cast(&LogicalType::Double)? .double(), DataValue::Time64(value, ..) => DataValue::Int64(*value) diff --git a/src/types/evaluator/mod.rs b/src/types/evaluator/mod.rs index 2a1cae5d..b2f597f8 100644 --- a/src/types/evaluator/mod.rs +++ b/src/types/evaluator/mod.rs @@ -9,7 +9,7 @@ pub mod int32; pub mod int64; pub mod int8; pub mod null; -pub mod time; +pub mod time32; pub mod time64; pub mod tuple; pub mod uint16; @@ -31,7 +31,7 @@ use crate::types::evaluator::int32::*; use crate::types::evaluator::int64::*; use crate::types::evaluator::int8::*; use crate::types::evaluator::null::NullBinaryEvaluator; -use crate::types::evaluator::time::*; +use crate::types::evaluator::time32::*; use crate::types::evaluator::time64::*; use crate::types::evaluator::tuple::{ TupleEqBinaryEvaluator, TupleGtBinaryEvaluator, TupleGtEqBinaryEvaluator, @@ -205,10 +205,6 @@ impl EvaluatorFactory { _ => Err(DatabaseError::UnsupportedBinaryOperator(ty, op)), }, LogicalType::TimeStamp(_, _) => match op { - BinaryOperator::Plus => Ok(BinaryEvaluatorBox(Arc::new(Time64PlusBinaryEvaluator))), - BinaryOperator::Minus => { - Ok(BinaryEvaluatorBox(Arc::new(Time64MinusBinaryEvaluator))) - } BinaryOperator::Gt => Ok(BinaryEvaluatorBox(Arc::new(Time64GtBinaryEvaluator))), BinaryOperator::GtEq => Ok(BinaryEvaluatorBox(Arc::new(Time64GtEqBinaryEvaluator))), BinaryOperator::Lt => Ok(BinaryEvaluatorBox(Arc::new(Time64LtBinaryEvaluator))), @@ -1059,6 +1055,164 @@ mod test { Ok(()) } + #[test] + fn test_binary_op_time32_and_time64() -> Result<(), DatabaseError> { + let evaluator_time32 = + EvaluatorFactory::binary_create(LogicalType::Time(None, false), BinaryOperator::Plus)?; + assert_eq!( + evaluator_time32.0.binary_eval( + &DataValue::Time32(4190119896, 3, false), + &DataValue::Time32(2621204256, 4, false), + ), + DataValue::Time32(2618593017, 4, false) + ); + assert_eq!( + evaluator_time32.0.binary_eval( + &DataValue::Time32(4190175696, 3, false), + &DataValue::Time32(2621224256, 4, false), + ), + DataValue::Null + ); + + let evaluator_time32 = + EvaluatorFactory::binary_create(LogicalType::Time(None, false), BinaryOperator::Minus)?; + assert_eq!( + evaluator_time32.0.binary_eval( + &DataValue::Time32(4190119896, 3, false), + &DataValue::Time32(2621204256, 4, false), + ), + DataValue::Null + ); + assert_eq!( + evaluator_time32.0.binary_eval( + &DataValue::Time32(2621204256, 4, false), + &DataValue::Time32(4190119896, 3, false), + ), + DataValue::Time32(2375496, 4, false) + ); + + let evaluator_time32 = + EvaluatorFactory::binary_create(LogicalType::Time(None, false), BinaryOperator::Gt)?; + let evaluator_time64 = EvaluatorFactory::binary_create( + LogicalType::TimeStamp(None, false), + BinaryOperator::Gt, + )?; + assert_eq!( + evaluator_time32.0.binary_eval( + &DataValue::Time32(2621204256, 4, false), + &DataValue::Time32(4190119896, 3, false), + ), + DataValue::Boolean(true) + ); + assert_eq!( + evaluator_time32.0.binary_eval( + &DataValue::Time32(4190119896, 3, false), + &DataValue::Time32(2621204256, 4, false), + ), + DataValue::Boolean(false) + ); + assert_eq!( + evaluator_time64.0.binary_eval( + &DataValue::Time64(1736055775154814, 6, false), + &DataValue::Time64(1738734177256, 3, false), + ), + DataValue::Boolean(false) + ); + assert_eq!( + evaluator_time64.0.binary_eval( + &DataValue::Time64(1738734177256, 3, false), + &DataValue::Time64(1736055775154814, 6, false), + ), + DataValue::Boolean(true) + ); + + let evaluator_time32 = + EvaluatorFactory::binary_create(LogicalType::Time(None, false), BinaryOperator::GtEq)?; + let evaluator_time64 = EvaluatorFactory::binary_create( + LogicalType::TimeStamp(None, false), + BinaryOperator::GtEq, + )?; + assert_eq!( + evaluator_time32.0.binary_eval( + &DataValue::Time32(2621204256, 4, false), + &DataValue::Time32(4190119896, 3, false), + ), + DataValue::Boolean(true) + ); + assert_eq!( + evaluator_time32.0.binary_eval( + &DataValue::Time32(4190119896, 3, false), + &DataValue::Time32(2621204256, 4, false), + ), + DataValue::Boolean(false) + ); + assert_eq!( + evaluator_time32.0.binary_eval( + &DataValue::Time32(4190119896, 3, false), + &DataValue::Time32(2618828760, 4, false), + ), + DataValue::Boolean(true) + ); + assert_eq!( + evaluator_time64.0.binary_eval( + &DataValue::Time64(1736055775154814, 6, false), + &DataValue::Time64(1738734177256, 3, false), + ), + DataValue::Boolean(false) + ); + assert_eq!( + evaluator_time64.0.binary_eval( + &DataValue::Time64(1738734177256, 3, false), + &DataValue::Time64(1736055775154814, 6, false), + ), + DataValue::Boolean(true) + ); + assert_eq!( + evaluator_time64.0.binary_eval( + &DataValue::Time64(1738734177256, 3, false), + &DataValue::Time64(1738734177256000, 6, false), + ), + DataValue::Boolean(true) + ); + + let evaluator_time32 = + EvaluatorFactory::binary_create(LogicalType::Time(None, false), BinaryOperator::Eq)?; + let evaluator_time64 = EvaluatorFactory::binary_create( + LogicalType::TimeStamp(None, false), + BinaryOperator::Eq, + )?; + assert_eq!( + evaluator_time32.0.binary_eval( + &DataValue::Time32(4190119896, 3, false), + &DataValue::Time32(2621204256, 4, false), + ), + DataValue::Boolean(false) + ); + assert_eq!( + evaluator_time32.0.binary_eval( + &DataValue::Time32(4190119896, 3, false), + &DataValue::Time32(2618828760, 4, false), + ), + DataValue::Boolean(true) + ); + assert_eq!( + evaluator_time64.0.binary_eval( + &DataValue::Time64(1738734177256, 3, false), + &DataValue::Time64(1736055775154814, 6, false), + ), + DataValue::Boolean(false) + ); + assert_eq!( + evaluator_time64.0.binary_eval( + &DataValue::Time64(1738734177256, 3, false), + &DataValue::Time64(1738734177256000, 6, false), + ), + DataValue::Boolean(true) + ); + + Ok(()) + } + #[test] fn test_reference_serialization() -> Result<(), DatabaseError> { let mut cursor = Cursor::new(Vec::new()); diff --git a/src/types/evaluator/time.rs b/src/types/evaluator/time32.rs similarity index 56% rename from src/types/evaluator/time.rs rename to src/types/evaluator/time32.rs index 4ca6e2b3..c9d99c24 100644 --- a/src/types/evaluator/time.rs +++ b/src/types/evaluator/time32.rs @@ -1,5 +1,6 @@ use crate::types::evaluator::BinaryEvaluator; use crate::types::evaluator::DataValue; +use crate::types::value::{ONE_DAY_TO_SEC, ONE_SEC_TO_NANO}; use serde::{Deserialize, Serialize}; use std::hint; @@ -24,22 +25,22 @@ pub struct TimeNotEqBinaryEvaluator; impl BinaryEvaluator for TimePlusBinaryEvaluator { fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { match (left, right) { - (DataValue::Time(v1, p1, _), DataValue::Time(v2, p2, ..)) => { + (DataValue::Time32(v1, p1, _), DataValue::Time32(v2, p2, ..)) => { let (mut v1, n1) = DataValue::unpack(*v1, *p1); let (v2, n2) = DataValue::unpack(*v2, *p2); let mut n = n1 + n2; - while n > 1_000_000_000 { + while n > ONE_SEC_TO_NANO { v1 += 1; - n -= 1_000_000_000; + n -= ONE_SEC_TO_NANO; } let p = if p2 > p1 { *p2 } else { *p1 }; - if v1 + v2 > 86400 { + if v1 + v2 > ONE_DAY_TO_SEC { return DataValue::Null; } - DataValue::Time(DataValue::pack(v1 + v2, n, p), p, false) + DataValue::Time32(DataValue::pack(v1 + v2, n, p), p, false) } - (DataValue::Time(..), DataValue::Null) - | (DataValue::Null, DataValue::Time(..)) + (DataValue::Time32(..), DataValue::Null) + | (DataValue::Null, DataValue::Time32(..)) | (DataValue::Null, DataValue::Null) => DataValue::Null, _ => unsafe { hint::unreachable_unchecked() }, } @@ -49,21 +50,21 @@ impl BinaryEvaluator for TimePlusBinaryEvaluator { impl BinaryEvaluator for TimeMinusBinaryEvaluator { fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { match (left, right) { - (DataValue::Time(v1, p1, _), DataValue::Time(v2, p2, ..)) => { + (DataValue::Time32(v1, p1, _), DataValue::Time32(v2, p2, ..)) => { let (mut v1, mut n1) = DataValue::unpack(*v1, *p1); let (v2, n2) = DataValue::unpack(*v2, *p2); while n1 < n2 { v1 -= 1; - n1 += 1_000_000_000; + n1 += ONE_SEC_TO_NANO; } if v1 < v2 { return DataValue::Null; } let p = if p2 > p1 { *p2 } else { *p1 }; - DataValue::Time(DataValue::pack(v1 - v2, n1 - n2, p), p, false) + DataValue::Time32(DataValue::pack(v1 - v2, n1 - n2, p), p, false) } - (DataValue::Time(..), DataValue::Null) - | (DataValue::Null, DataValue::Time(..)) + (DataValue::Time32(..), DataValue::Null) + | (DataValue::Null, DataValue::Time32(..)) | (DataValue::Null, DataValue::Null) => DataValue::Null, _ => unsafe { hint::unreachable_unchecked() }, } @@ -74,22 +75,13 @@ impl BinaryEvaluator for TimeMinusBinaryEvaluator { impl BinaryEvaluator for TimeGtBinaryEvaluator { fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { match (left, right) { - (DataValue::Time(v1, p1, _), DataValue::Time(v2, p2, ..)) => { + (DataValue::Time32(v1, p1, _), DataValue::Time32(v2, p2, ..)) => { let (v1, n1) = DataValue::unpack(*v1, *p1); let (v2, n2) = DataValue::unpack(*v2, *p2); - match v1 > v2 { - true => DataValue::Boolean(true), - false => match v1 < v2 { - true => DataValue::Boolean(false), - false => match n1 > n2 { - true => DataValue::Boolean(true), - false => DataValue::Boolean(false), - }, - }, - } + DataValue::Boolean(v1.cmp(&v2).then_with(|| n1.cmp(&n2)).is_gt()) } - (DataValue::Time(..), DataValue::Null) - | (DataValue::Null, DataValue::Time(..)) + (DataValue::Time32(..), DataValue::Null) + | (DataValue::Null, DataValue::Time32(..)) | (DataValue::Null, DataValue::Null) => DataValue::Null, _ => unsafe { hint::unreachable_unchecked() }, } @@ -99,22 +91,13 @@ impl BinaryEvaluator for TimeGtBinaryEvaluator { impl BinaryEvaluator for TimeGtEqBinaryEvaluator { fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { match (left, right) { - (DataValue::Time(v1, p1, _), DataValue::Time(v2, p2, ..)) => { + (DataValue::Time32(v1, p1, _), DataValue::Time32(v2, p2, ..)) => { let (v1, n1) = DataValue::unpack(*v1, *p1); let (v2, n2) = DataValue::unpack(*v2, *p2); - match v1 > v2 { - true => DataValue::Boolean(true), - false => match v1 < v2 { - true => DataValue::Boolean(false), - false => match n1 >= n2 { - true => DataValue::Boolean(true), - false => DataValue::Boolean(false), - }, - }, - } + DataValue::Boolean(!v1.cmp(&v2).then_with(|| n1.cmp(&n2)).is_lt()) } - (DataValue::Time(..), DataValue::Null) - | (DataValue::Null, DataValue::Time(..)) + (DataValue::Time32(..), DataValue::Null) + | (DataValue::Null, DataValue::Time32(..)) | (DataValue::Null, DataValue::Null) => DataValue::Null, _ => unsafe { hint::unreachable_unchecked() }, } @@ -124,22 +107,13 @@ impl BinaryEvaluator for TimeGtEqBinaryEvaluator { impl BinaryEvaluator for TimeLtBinaryEvaluator { fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { match (left, right) { - (DataValue::Time(v1, p1, _), DataValue::Time(v2, p2, ..)) => { + (DataValue::Time32(v1, p1, _), DataValue::Time32(v2, p2, ..)) => { let (v1, n1) = DataValue::unpack(*v1, *p1); let (v2, n2) = DataValue::unpack(*v2, *p2); - match v1 < v2 { - true => DataValue::Boolean(true), - false => match v1 > v2 { - true => DataValue::Boolean(false), - false => match n1 < n2 { - true => DataValue::Boolean(true), - false => DataValue::Boolean(false), - }, - }, - } + DataValue::Boolean(v1.cmp(&v2).then_with(|| n1.cmp(&n2)).is_lt()) } - (DataValue::Time(..), DataValue::Null) - | (DataValue::Null, DataValue::Time(..)) + (DataValue::Time32(..), DataValue::Null) + | (DataValue::Null, DataValue::Time32(..)) | (DataValue::Null, DataValue::Null) => DataValue::Null, _ => unsafe { hint::unreachable_unchecked() }, } @@ -149,22 +123,13 @@ impl BinaryEvaluator for TimeLtBinaryEvaluator { impl BinaryEvaluator for TimeLtEqBinaryEvaluator { fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { match (left, right) { - (DataValue::Time(v1, p1, _), DataValue::Time(v2, p2, ..)) => { + (DataValue::Time32(v1, p1, _), DataValue::Time32(v2, p2, ..)) => { let (v1, n1) = DataValue::unpack(*v1, *p1); let (v2, n2) = DataValue::unpack(*v2, *p2); - match v1 < v2 { - true => DataValue::Boolean(true), - false => match v1 > v2 { - true => DataValue::Boolean(false), - false => match n1 <= n2 { - true => DataValue::Boolean(true), - false => DataValue::Boolean(false), - }, - }, - } + DataValue::Boolean(!v1.cmp(&v2).then_with(|| n1.cmp(&n2)).is_gt()) } - (DataValue::Time(..), DataValue::Null) - | (DataValue::Null, DataValue::Time(..)) + (DataValue::Time32(..), DataValue::Null) + | (DataValue::Null, DataValue::Time32(..)) | (DataValue::Null, DataValue::Null) => DataValue::Null, _ => unsafe { hint::unreachable_unchecked() }, } @@ -174,16 +139,13 @@ impl BinaryEvaluator for TimeLtEqBinaryEvaluator { impl BinaryEvaluator for TimeEqBinaryEvaluator { fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { match (left, right) { - (DataValue::Time(v1, p1, _), DataValue::Time(v2, p2, ..)) => { + (DataValue::Time32(v1, p1, _), DataValue::Time32(v2, p2, ..)) => { let (v1, n1) = DataValue::unpack(*v1, *p1); let (v2, n2) = DataValue::unpack(*v2, *p2); - match (v1 == v2, n1 == n2) { - (true, true) => DataValue::Boolean(true), - _ => DataValue::Boolean(false), - } + DataValue::Boolean(v1.cmp(&v2).then_with(|| n1.cmp(&n2)).is_eq()) } - (DataValue::Time(..), DataValue::Null) - | (DataValue::Null, DataValue::Time(..)) + (DataValue::Time32(..), DataValue::Null) + | (DataValue::Null, DataValue::Time32(..)) | (DataValue::Null, DataValue::Null) => DataValue::Null, _ => unsafe { hint::unreachable_unchecked() }, } @@ -193,16 +155,13 @@ impl BinaryEvaluator for TimeEqBinaryEvaluator { impl BinaryEvaluator for TimeNotEqBinaryEvaluator { fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { match (left, right) { - (DataValue::Time(v1, p1, _), DataValue::Time(v2, p2, ..)) => { + (DataValue::Time32(v1, p1, _), DataValue::Time32(v2, p2, ..)) => { let (v1, n1) = DataValue::unpack(*v1, *p1); let (v2, n2) = DataValue::unpack(*v2, *p2); - match (v1 != v2, n1 != n2) { - (false, false) => DataValue::Boolean(false), - _ => DataValue::Boolean(true), - } + DataValue::Boolean(!v1.cmp(&v2).then_with(|| n1.cmp(&n2)).is_eq()) } - (DataValue::Time(..), DataValue::Null) - | (DataValue::Null, DataValue::Time(..)) + (DataValue::Time32(..), DataValue::Null) + | (DataValue::Null, DataValue::Time32(..)) | (DataValue::Null, DataValue::Null) => DataValue::Null, _ => unsafe { hint::unreachable_unchecked() }, } diff --git a/src/types/evaluator/time64.rs b/src/types/evaluator/time64.rs index 13b1cdfa..f3fa6932 100644 --- a/src/types/evaluator/time64.rs +++ b/src/types/evaluator/time64.rs @@ -3,10 +3,6 @@ use crate::types::evaluator::DataValue; use serde::{Deserialize, Serialize}; use std::hint; -#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] -pub struct Time64PlusBinaryEvaluator; -#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] -pub struct Time64MinusBinaryEvaluator; #[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] pub struct Time64GtBinaryEvaluator; #[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] @@ -20,40 +16,24 @@ pub struct Time64EqBinaryEvaluator; #[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] pub struct Time64NotEqBinaryEvaluator; -#[typetag::serde] -impl BinaryEvaluator for Time64PlusBinaryEvaluator { - fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { - match (left, right) { - (DataValue::Time64(v1, p1, _), DataValue::Time64(v2, p2, ..)) => { - DataValue::Time64(v1 + v2, if p2 > p1 { *p2 } else { *p1 }, false) - } - (DataValue::Time64(..), DataValue::Null) - | (DataValue::Null, DataValue::Time64(..)) - | (DataValue::Null, DataValue::Null) => DataValue::Null, - _ => unsafe { hint::unreachable_unchecked() }, - } - } -} -#[typetag::serde] -impl BinaryEvaluator for Time64MinusBinaryEvaluator { - fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { - match (left, right) { - (DataValue::Time64(v1, p1, _), DataValue::Time64(v2, p2, ..)) => { - DataValue::Time64(v1 - v2, if p2 > p1 { *p2 } else { *p1 }, false) - } - (DataValue::Time64(..), DataValue::Null) - | (DataValue::Null, DataValue::Time64(..)) - | (DataValue::Null, DataValue::Null) => DataValue::Null, - _ => unsafe { hint::unreachable_unchecked() }, - } - } -} - #[typetag::serde] impl BinaryEvaluator for Time64GtBinaryEvaluator { fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { match (left, right) { - (DataValue::Time64(v1, ..), DataValue::Time64(v2, ..)) => DataValue::Boolean(v1 > v2), + (DataValue::Time64(v1, p1, _), DataValue::Time64(v2, p2, _)) => { + if let (Some(v1), Some(v2)) = ( + DataValue::from_timestamp_precision(*v1, *p1), + DataValue::from_timestamp_precision(*v2, *p2), + ) { + let p = if p2 > p1 { *p2 } else { *p1 }; + DataValue::Boolean( + DataValue::timestamp_precision(v1, p) + > DataValue::timestamp_precision(v2, p), + ) + } else { + DataValue::Null + } + } (DataValue::Time64(..), DataValue::Null) | (DataValue::Null, DataValue::Time64(..)) | (DataValue::Null, DataValue::Null) => DataValue::Null, @@ -65,7 +45,20 @@ impl BinaryEvaluator for Time64GtBinaryEvaluator { impl BinaryEvaluator for Time64GtEqBinaryEvaluator { fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { match (left, right) { - (DataValue::Time64(v1, ..), DataValue::Time64(v2, ..)) => DataValue::Boolean(v1 >= v2), + (DataValue::Time64(v1, p1, _), DataValue::Time64(v2, p2, _)) => { + if let (Some(v1), Some(v2)) = ( + DataValue::from_timestamp_precision(*v1, *p1), + DataValue::from_timestamp_precision(*v2, *p2), + ) { + let p = if p2 > p1 { *p2 } else { *p1 }; + DataValue::Boolean( + DataValue::timestamp_precision(v1, p) + >= DataValue::timestamp_precision(v2, p), + ) + } else { + DataValue::Null + } + } (DataValue::Time64(..), DataValue::Null) | (DataValue::Null, DataValue::Time64(..)) | (DataValue::Null, DataValue::Null) => DataValue::Null, @@ -77,7 +70,20 @@ impl BinaryEvaluator for Time64GtEqBinaryEvaluator { impl BinaryEvaluator for Time64LtBinaryEvaluator { fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { match (left, right) { - (DataValue::Time64(v1, ..), DataValue::Time64(v2, ..)) => DataValue::Boolean(v1 < v2), + (DataValue::Time64(v1, p1, _), DataValue::Time64(v2, p2, _)) => { + if let (Some(v1), Some(v2)) = ( + DataValue::from_timestamp_precision(*v1, *p1), + DataValue::from_timestamp_precision(*v2, *p2), + ) { + let p = if p2 > p1 { *p2 } else { *p1 }; + DataValue::Boolean( + DataValue::timestamp_precision(v1, p) + < DataValue::timestamp_precision(v2, p), + ) + } else { + DataValue::Null + } + } (DataValue::Time64(..), DataValue::Null) | (DataValue::Null, DataValue::Time64(..)) | (DataValue::Null, DataValue::Null) => DataValue::Null, @@ -89,7 +95,20 @@ impl BinaryEvaluator for Time64LtBinaryEvaluator { impl BinaryEvaluator for Time64LtEqBinaryEvaluator { fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { match (left, right) { - (DataValue::Time64(v1, ..), DataValue::Time64(v2, ..)) => DataValue::Boolean(v1 <= v2), + (DataValue::Time64(v1, p1, _), DataValue::Time64(v2, p2, _)) => { + if let (Some(v1), Some(v2)) = ( + DataValue::from_timestamp_precision(*v1, *p1), + DataValue::from_timestamp_precision(*v2, *p2), + ) { + let p = if p2 > p1 { *p2 } else { *p1 }; + DataValue::Boolean( + DataValue::timestamp_precision(v1, p) + <= DataValue::timestamp_precision(v2, p), + ) + } else { + DataValue::Null + } + } (DataValue::Time64(..), DataValue::Null) | (DataValue::Null, DataValue::Time64(..)) | (DataValue::Null, DataValue::Null) => DataValue::Null, @@ -101,7 +120,20 @@ impl BinaryEvaluator for Time64LtEqBinaryEvaluator { impl BinaryEvaluator for Time64EqBinaryEvaluator { fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { match (left, right) { - (DataValue::Time64(v1, ..), DataValue::Time64(v2, ..)) => DataValue::Boolean(v1 == v2), + (DataValue::Time64(v1, p1, _), DataValue::Time64(v2, p2, _)) => { + if let (Some(v1), Some(v2)) = ( + DataValue::from_timestamp_precision(*v1, *p1), + DataValue::from_timestamp_precision(*v2, *p2), + ) { + let p = if p2 > p1 { *p2 } else { *p1 }; + DataValue::Boolean( + DataValue::timestamp_precision(v1, p) + == DataValue::timestamp_precision(v2, p), + ) + } else { + DataValue::Null + } + } (DataValue::Time64(..), DataValue::Null) | (DataValue::Null, DataValue::Time64(..)) | (DataValue::Null, DataValue::Null) => DataValue::Null, @@ -113,7 +145,20 @@ impl BinaryEvaluator for Time64EqBinaryEvaluator { impl BinaryEvaluator for Time64NotEqBinaryEvaluator { fn binary_eval(&self, left: &DataValue, right: &DataValue) -> DataValue { match (left, right) { - (DataValue::Time64(v1, ..), DataValue::Time64(v2, ..)) => DataValue::Boolean(v1 != v2), + (DataValue::Time64(v1, p1, _), DataValue::Time64(v2, p2, _)) => { + if let (Some(v1), Some(v2)) = ( + DataValue::from_timestamp_precision(*v1, *p1), + DataValue::from_timestamp_precision(*v2, *p2), + ) { + let p = if p2 > p1 { *p2 } else { *p1 }; + DataValue::Boolean( + DataValue::timestamp_precision(v1, p) + != DataValue::timestamp_precision(v2, p), + ) + } else { + DataValue::Null + } + } (DataValue::Time64(..), DataValue::Null) | (DataValue::Null, DataValue::Time64(..)) | (DataValue::Null, DataValue::Null) => DataValue::Null, diff --git a/src/types/value.rs b/src/types/value.rs index 22dc95e5..9d19c355 100644 --- a/src/types/value.rs +++ b/src/types/value.rs @@ -4,7 +4,7 @@ use crate::storage::table_codec::{BumpBytes, BOUND_MAX_TAG, BOUND_MIN_TAG}; use crate::types::LogicalType::{Date, TimeStamp}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use chrono::format::{DelayedFormat, StrftimeItems}; -use chrono::{DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; +use chrono::{DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc}; use itertools::Itertools; use ordered_float::OrderedFloat; use rust_decimal::prelude::{FromPrimitive, ToPrimitive}; @@ -35,6 +35,9 @@ pub const TIME_FMT_WITHOUT_ZONE: &str = "%H:%M:%S%.f"; pub const TIME_FMT_WITH_ZONE: &str = "%H:%M:%S%.f%z"; pub const TIME_FMT_WITHOUT_PRECISION: &str = "%H:%M:%S%z"; +pub const ONE_SEC_TO_NANO: u32 = 1_000_000_000; +pub const ONE_DAY_TO_SEC: u32 = 86_400; + const ENCODE_GROUP_SIZE: usize = 8; const ENCODE_MARKER: u8 = 0xFF; @@ -67,7 +70,7 @@ pub enum DataValue { Date32(i32), /// Date stored as a signed 64bit int timestamp since UNIX epoch 1970-01-01 Date64(i64), - Time(u32, u64, bool), + Time32(u32, u64, bool), Time64(i64, u64, bool), Decimal(Decimal), /// (values, is_upper) @@ -142,8 +145,8 @@ impl PartialEq for DataValue { (Date32(_), _) => false, (Date64(v1), Date64(v2)) => v1.eq(v2), (Date64(_), _) => false, - (Time(v1, ..), Time(v2, ..)) => v1.eq(v2), - (Time(..), _) => false, + (Time32(v1, ..), Time32(v2, ..)) => v1.eq(v2), + (Time32(..), _) => false, (Time64(v1, ..), Time64(v2, ..)) => v1.eq(v2), (Time64(..), _) => false, (Decimal(v1), Decimal(v2)) => v1.eq(v2), @@ -190,8 +193,8 @@ impl PartialOrd for DataValue { (Date32(_), _) => None, (Date64(v1), Date64(v2)) => v1.partial_cmp(v2), (Date64(_), _) => None, - (Time(v1, ..), Time(v2, ..)) => v1.partial_cmp(v2), - (Time(..), _) => None, + (Time32(v1, ..), Time32(v2, ..)) => v1.partial_cmp(v2), + (Time32(..), _) => None, (Time64(v1, ..), Time64(v2, ..)) => v1.partial_cmp(v2), (Time64(..), _) => None, (Decimal(v1), Decimal(v2)) => v1.partial_cmp(v2), @@ -228,7 +231,7 @@ impl Hash for DataValue { Null => 1.hash(state), Date32(v) => v.hash(state), Date64(v) => v.hash(state), - Time(v, ..) => v.hash(state), + Time32(v, ..) => v.hash(state), Time64(v, ..) => v.hash(state), Decimal(v) => v.hash(state), Tuple(values, is_upper) => { @@ -306,7 +309,7 @@ impl DataValue { } pub fn time(&self) -> Option { - if let DataValue::Time(val, ..) = self { + if let DataValue::Time32(val, ..) = self { NaiveTime::from_num_seconds_from_midnight_opt(*val, 0) } else { None @@ -382,8 +385,8 @@ impl DataValue { } pub fn pack(a: u32, b: u32, precision: u64) -> u32 { - assert!(b <= 1_000_000_000); - assert!(a <= 86_400); + assert!(b <= ONE_SEC_TO_NANO); + assert!(a <= ONE_DAY_TO_SEC); // Scale down `a` to fit let scaled_b = b / (1000000000 / 10_u32.pow(precision as u32)); // Now 0-1_000_000 let p = match precision { @@ -458,8 +461,8 @@ impl DataValue { LogicalType::Date => DataValue::Date32(UNIX_DATETIME.num_days_from_ce()), LogicalType::DateTime => DataValue::Date64(UNIX_DATETIME.and_utc().timestamp()), LogicalType::Time(precision, zone) => match precision { - Some(i) => DataValue::Time(UNIX_TIME.num_seconds_from_midnight(), *i, *zone), - None => DataValue::Time(UNIX_TIME.num_seconds_from_midnight(), 0, *zone), + Some(i) => DataValue::Time32(UNIX_TIME.num_seconds_from_midnight(), *i, *zone), + None => DataValue::Time32(UNIX_TIME.num_seconds_from_midnight(), 0, *zone), }, TimeStamp(precision, zone) => match precision { Some(3) => DataValue::Time64(UNIX_DATETIME.and_utc().timestamp_millis(), 3, *zone), @@ -570,7 +573,7 @@ impl DataValue { writer.write_i64::(*v)?; return Ok(()); } - DataValue::Time(v, ..) => { + DataValue::Time32(v, ..) => { writer.write_u32::(*v)?; return Ok(()); } @@ -739,7 +742,7 @@ impl DataValue { reader.seek(SeekFrom::Current(4))?; return Ok(None); } - DataValue::Time(reader.read_u32::()?, precision, *zone) + DataValue::Time32(reader.read_u32::()?, precision, *zone) } TimeStamp(precision, zone) => { let precision = match precision { @@ -794,7 +797,7 @@ impl DataValue { } => LogicalType::Char(*len, *unit), DataValue::Date32(_) => LogicalType::Date, DataValue::Date64(_) => LogicalType::DateTime, - DataValue::Time(..) => LogicalType::Time(None, false), + DataValue::Time32(..) => LogicalType::Time(None, false), DataValue::Time64(..) => TimeStamp(None, false), DataValue::Decimal(_) => LogicalType::Decimal(None, None), DataValue::Tuple(values, ..) => { @@ -867,7 +870,7 @@ impl DataValue { } DataValue::UInt8(v) => encode_u!(b, v), DataValue::UInt16(v) => encode_u!(b, v), - DataValue::UInt32(v) | DataValue::Time(v, ..) => encode_u!(b, v), + DataValue::UInt32(v) | DataValue::Time32(v, ..) => encode_u!(b, v), DataValue::UInt64(v) => encode_u!(b, v), DataValue::Utf8 { value: v, .. } => Self::encode_bytes(b, v.as_bytes()), DataValue::Boolean(v) => b.push(if *v { b'1' } else { b'0' }), @@ -1381,7 +1384,7 @@ impl DataValue { _ => NaiveTime::parse_from_str(&complete_value, fmt) .map(|time| (time.num_seconds_from_midnight(), time.nanosecond()))?, }; - Ok(DataValue::Time( + Ok(DataValue::Time32( Self::pack(value, nano, precision), precision, *zone, @@ -1503,7 +1506,7 @@ impl DataValue { .map(|date_time| date_time.time().num_seconds_from_midnight()) .ok_or(DatabaseError::CastFail)?; - Ok(DataValue::Time(Self::pack(value, 0, 0), precision, *zone)) + Ok(DataValue::Time32(Self::pack(value, 0, 0), precision, *zone)) } TimeStamp(precision, zone) => { let precision = match precision { @@ -1514,7 +1517,7 @@ impl DataValue { } _ => Err(DatabaseError::CastFail), }, - DataValue::Time(value, precision, _) => match to { + DataValue::Time32(value, precision, _) => match to { LogicalType::SqlNull => Ok(DataValue::Null), LogicalType::Char(len, unit) => { varchar_cast!( @@ -1553,38 +1556,18 @@ impl DataValue { ) } Date => { - let value = match precision { - 0 => DateTime::from_timestamp(value, 0), - 3 => DateTime::from_timestamp_millis(value), - 6 => DateTime::from_timestamp_micros(value), - 9 => { - let secs = value.div_euclid(1_000_000_000); - let nsecs = value.rem_euclid(1_000_000_000) as u32; - DateTime::from_timestamp(secs, nsecs) - } - _ => unreachable!(), - } - .ok_or(DatabaseError::CastFail)? - .naive_utc() - .date() - .num_days_from_ce(); + let value = Self::from_timestamp_precision(value, precision) + .ok_or(DatabaseError::CastFail)? + .naive_utc() + .date() + .num_days_from_ce(); Ok(DataValue::Date32(value)) } LogicalType::DateTime => { - let value = match precision { - 0 => DateTime::from_timestamp(value, 0), - 3 => DateTime::from_timestamp_millis(value), - 6 => DateTime::from_timestamp_micros(value), - 9 => { - let secs = value.div_euclid(1_000_000_000); - let nsecs = value.rem_euclid(1_000_000_000) as u32; - DateTime::from_timestamp(secs, nsecs) - } - _ => unreachable!(), - } - .ok_or(DatabaseError::CastFail)? - .timestamp(); + let value = Self::from_timestamp_precision(value, precision) + .ok_or(DatabaseError::CastFail)? + .timestamp(); Ok(DataValue::Date64(value)) } LogicalType::Time(p, zone) => { @@ -1592,25 +1575,15 @@ impl DataValue { Some(p) => *p, None => 0, }; - let (value, nano) = match precision { - 0 => DateTime::from_timestamp(value, 0), - 3 => DateTime::from_timestamp_millis(value), - 6 => DateTime::from_timestamp_micros(value), - 9 => { - let secs = value.div_euclid(1_000_000_000); - let nsecs = value.rem_euclid(1_000_000_000) as u32; - DateTime::from_timestamp(secs, nsecs) - } - _ => unreachable!(), - } - .map(|date_time| { - ( - date_time.time().num_seconds_from_midnight(), - date_time.time().nanosecond(), - ) - }) - .ok_or(DatabaseError::CastFail)?; - Ok(DataValue::Time(Self::pack(value, nano, p), p, *zone)) + let (value, nano) = Self::from_timestamp_precision(value, precision) + .map(|date_time| { + ( + date_time.time().num_seconds_from_midnight(), + date_time.time().nanosecond(), + ) + }) + .ok_or(DatabaseError::CastFail)?; + Ok(DataValue::Time32(Self::pack(value, nano, p), p, *zone)) } TimeStamp(precision, zone) => { let precision = match precision { @@ -1731,25 +1704,42 @@ impl DataValue { precision: u64, _zone: bool, ) -> Option>> { + Self::from_timestamp_precision(v, precision) + .map(|date_time| date_time.format(TIME_STAMP_FMT_WITHOUT_ZONE)) + } + + fn decimal_format(v: &Decimal) -> String { + v.to_string() + } + + pub fn timestamp_precision(v: DateTime, precision: u64) -> i64 { match precision { - 0 => DateTime::from_timestamp(v, 0) - .map(|date_time| date_time.format(TIME_STAMP_FMT_WITHOUT_ZONE)), - 3 => DateTime::from_timestamp_millis(v) - .map(|date_time| date_time.format(TIME_STAMP_FMT_WITHOUT_ZONE)), - 6 => DateTime::from_timestamp_micros(v) - .map(|date_time| date_time.format(TIME_STAMP_FMT_WITHOUT_ZONE)), + 3 => v.timestamp_millis(), + 6 => v.timestamp_micros(), 9 => { - let secs = v.div_euclid(1_000_000_000); - let nsecs = v.rem_euclid(1_000_000_000) as u32; - DateTime::from_timestamp(secs, nsecs) - .map(|date_time| date_time.format(TIME_STAMP_FMT_WITHOUT_ZONE)) + if let Some(value) = v.timestamp_nanos_opt() { + value + } else { + 0 + } } + 0 => v.timestamp(), _ => unreachable!(), } } - fn decimal_format(v: &Decimal) -> String { - v.to_string() + pub fn from_timestamp_precision(v: i64, precision: u64) -> Option> { + match precision { + 0 => DateTime::from_timestamp(v, 0), + 3 => DateTime::from_timestamp_millis(v), + 6 => DateTime::from_timestamp_micros(v), + 9 => { + let secs = v.div_euclid(ONE_SEC_TO_NANO as i64); + let nsecs = v.rem_euclid(ONE_SEC_TO_NANO as i64) as u32; + DateTime::from_timestamp(secs, nsecs) + } + _ => unreachable!(), + } } } @@ -1874,7 +1864,7 @@ impl From> for DataValue { impl From<&NaiveTime> for DataValue { fn from(value: &NaiveTime) -> Self { - DataValue::Time( + DataValue::Time32( Self::pack(value.num_seconds_from_midnight(), value.nanosecond(), 4), 6, false, @@ -1885,7 +1875,7 @@ impl From<&NaiveTime> for DataValue { impl From> for DataValue { fn from(value: Option<&NaiveTime>) -> Self { if let Some(value) = value { - DataValue::Time( + DataValue::Time32( Self::pack(value.num_seconds_from_midnight(), value.nanosecond(), 4), 0, false, @@ -1955,7 +1945,7 @@ impl fmt::Display for DataValue { DataValue::Null => write!(f, "null")?, DataValue::Date32(e) => write!(f, "{}", DataValue::date_format(*e).unwrap())?, DataValue::Date64(e) => write!(f, "{}", DataValue::date_time_format(*e).unwrap())?, - DataValue::Time(e, precision, zone) => write!( + DataValue::Time32(e, precision, zone) => write!( f, "{}", DataValue::time_format(*e, *precision, *zone).unwrap() @@ -2001,7 +1991,7 @@ impl fmt::Debug for DataValue { DataValue::Null => write!(f, "null"), DataValue::Date32(_) => write!(f, "Date32({})", self), DataValue::Date64(_) => write!(f, "Date64({})", self), - DataValue::Time(..) => write!(f, "Time({})", self), + DataValue::Time32(..) => write!(f, "Time({})", self), DataValue::Time64(..) => write!(f, "Time64({})", self), DataValue::Decimal(_) => write!(f, "Decimal({})", self), DataValue::Tuple(..) => { From 1170438242a184dc9d531612f2e6704b1447894a Mon Sep 17 00:00:00 2001 From: wszhdshys <1925792291@qq.com> Date: Sun, 20 Jul 2025 14:45:02 +0800 Subject: [PATCH 3/3] Corrected a mistake --- src/types/value.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/value.rs b/src/types/value.rs index 9d19c355..ac583a96 100644 --- a/src/types/value.rs +++ b/src/types/value.rs @@ -1991,7 +1991,7 @@ impl fmt::Debug for DataValue { DataValue::Null => write!(f, "null"), DataValue::Date32(_) => write!(f, "Date32({})", self), DataValue::Date64(_) => write!(f, "Date64({})", self), - DataValue::Time32(..) => write!(f, "Time({})", self), + DataValue::Time32(..) => write!(f, "Time32({})", self), DataValue::Time64(..) => write!(f, "Time64({})", self), DataValue::Decimal(_) => write!(f, "Decimal({})", self), DataValue::Tuple(..) => {