Skip to content

Commit dae6374

Browse files
committed
fix:Add support for TimeDelta type in Python and Rust
1 parent 1d2dd80 commit dae6374

File tree

6 files changed

+39
-0
lines changed

6 files changed

+39
-0
lines changed

docs/docs/core/data_types.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ This is the list of all basic types supported by CocoIndex:
2929
| Time | | `datetime.time` | `datetime.time` |
3030
| LocalDatetime | Date and time without timezone | `cocoindex.typing.LocalDateTime` | `datetime.datetime` |
3131
| OffsetDatetime | Date and time with a timezone offset | `cocoindex.typing.OffsetDateTime` | `datetime.datetime` |
32+
| TimeDelta | A duration of time | `cocoindex.typing.TimeDelta` | `datetime.timedelta` |
3233
| Vector[*type*, *N*?] | |`Annotated[list[type], cocoindex.typing.Vector(dim=N)]` | `list[type]` |
3334
| Json | | `cocoindex.typing.Json` | Any type convertible to JSON by `json` package |
3435

python/cocoindex/typing.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def __init__(self, key: str, value: Any):
2929
Json = Annotated[Any, TypeKind('Json')]
3030
LocalDateTime = Annotated[datetime.datetime, TypeKind('LocalDateTime')]
3131
OffsetDateTime = Annotated[datetime.datetime, TypeKind('OffsetDateTime')]
32+
TimeDelta = Annotated[datetime.timedelta, TypeKind('TimeDelta')]
3233

3334
COLLECTION_TYPES = ('Table', 'List')
3435

@@ -142,6 +143,8 @@ def analyze_type_info(t) -> AnalyzedTypeInfo:
142143
kind = 'Time'
143144
elif t is datetime.datetime:
144145
kind = 'OffsetDateTime'
146+
elif t is datetime.timedelta:
147+
kind = 'TimeDelta'
145148
else:
146149
raise ValueError(f"type unsupported yet: {t}")
147150

src/base/schema.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ pub enum BasicValueType {
5050
/// Date and time with timezone.
5151
OffsetDateTime,
5252

53+
/// A time duration.
54+
TimeDelta,
55+
5356
/// A JSON value.
5457
Json,
5558

@@ -72,6 +75,7 @@ impl std::fmt::Display for BasicValueType {
7275
BasicValueType::Time => write!(f, "time"),
7376
BasicValueType::LocalDateTime => write!(f, "local_datetime"),
7477
BasicValueType::OffsetDateTime => write!(f, "offset_datetime"),
78+
BasicValueType::TimeDelta => write!(f, "timedelta"),
7579
BasicValueType::Json => write!(f, "json"),
7680
BasicValueType::Vector(s) => write!(
7781
f,

src/base/value.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use anyhow::Result;
55
use base64::prelude::*;
66
use chrono::Offset;
77
use log::warn;
8+
use pyo3::pyclass;
89
use serde::{
910
de::{SeqAccess, Visitor},
1011
ser::{SerializeMap, SerializeSeq, SerializeTuple},
@@ -354,6 +355,7 @@ pub enum BasicValue {
354355
Time(chrono::NaiveTime),
355356
LocalDateTime(chrono::NaiveDateTime),
356357
OffsetDateTime(chrono::DateTime<chrono::FixedOffset>),
358+
TimeDelta(chrono::Duration),
357359
Json(Arc<serde_json::Value>),
358360
Vector(Arc<[BasicValue]>),
359361
}
@@ -436,6 +438,12 @@ impl From<chrono::DateTime<chrono::FixedOffset>> for BasicValue {
436438
}
437439
}
438440

441+
impl From<chrono::Duration> for BasicValue {
442+
fn from(value: chrono::Duration) -> Self {
443+
BasicValue::TimeDelta(value)
444+
}
445+
}
446+
439447
impl From<serde_json::Value> for BasicValue {
440448
fn from(value: serde_json::Value) -> Self {
441449
BasicValue::Json(Arc::from(value))
@@ -465,6 +473,7 @@ impl BasicValue {
465473
| BasicValue::Time(_)
466474
| BasicValue::LocalDateTime(_)
467475
| BasicValue::OffsetDateTime(_)
476+
| BasicValue::TimeDelta(_)
468477
| BasicValue::Json(_)
469478
| BasicValue::Vector(_) => api_bail!("invalid key value type"),
470479
};
@@ -485,6 +494,7 @@ impl BasicValue {
485494
| BasicValue::Time(_)
486495
| BasicValue::LocalDateTime(_)
487496
| BasicValue::OffsetDateTime(_)
497+
| BasicValue::TimeDelta(_)
488498
| BasicValue::Json(_)
489499
| BasicValue::Vector(_) => api_bail!("invalid key value type"),
490500
};
@@ -505,6 +515,7 @@ impl BasicValue {
505515
BasicValue::Time(_) => "time",
506516
BasicValue::LocalDateTime(_) => "local_datetime",
507517
BasicValue::OffsetDateTime(_) => "offset_datetime",
518+
BasicValue::TimeDelta(_) => "timedelta",
508519
BasicValue::Json(_) => "json",
509520
BasicValue::Vector(_) => "vector",
510521
}
@@ -860,6 +871,7 @@ impl serde::Serialize for BasicValue {
860871
BasicValue::OffsetDateTime(v) => {
861872
serializer.serialize_str(&v.to_rfc3339_opts(chrono::SecondsFormat::AutoSi, true))
862873
}
874+
BasicValue::TimeDelta(v) => serializer.serialize_str(&v.to_string()),
863875
BasicValue::Json(v) => v.serialize(serializer),
864876
BasicValue::Vector(v) => v.serialize(serializer),
865877
}
@@ -912,6 +924,7 @@ impl BasicValue {
912924
}
913925
}
914926
}
927+
(v, BasicValueType::TimeDelta) => BasicValue::TimeDelta(v.as_duration()?),
915928
(v, BasicValueType::Json) => BasicValue::Json(Arc::from(v)),
916929
(
917930
serde_json::Value::Array(v),

src/ops/storages/postgres.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ fn bind_value_field<'arg>(
134134
BasicValue::OffsetDateTime(v) => {
135135
builder.push_bind(v);
136136
}
137+
BasicValue::TimeDelta(v) => {
138+
builder.push_bind(v);
139+
}
137140
BasicValue::Json(v) => {
138141
builder.push_bind(sqlx::types::Json(&**v));
139142
}
@@ -219,6 +222,9 @@ fn from_pg_value(row: &PgRow, field_idx: usize, typ: &ValueType) -> Result<Value
219222
BasicValueType::OffsetDateTime => row
220223
.try_get::<Option<chrono::DateTime<chrono::FixedOffset>>, _>(field_idx)?
221224
.map(BasicValue::OffsetDateTime),
225+
BasicValueType::TimeDelta => row
226+
.try_get::<Option<chrono::Duration>, _>(field_idx)?
227+
.map(BasicValue::TimeDelta),
222228
BasicValueType::Json => row
223229
.try_get::<Option<serde_json::Value>, _>(field_idx)?
224230
.map(|v| BasicValue::Json(Arc::from(v))),
@@ -701,6 +707,7 @@ fn to_column_type_sql(column_type: &ValueType) -> Cow<'static, str> {
701707
BasicValueType::Time => "time".into(),
702708
BasicValueType::LocalDateTime => "timestamp".into(),
703709
BasicValueType::OffsetDateTime => "timestamp with time zone".into(),
710+
BasicValueType::TimeDelta => "interval".into(),
704711
BasicValueType::Json => "jsonb".into(),
705712
BasicValueType::Vector(vec_schema) => {
706713
if convertible_to_pgvector(vec_schema) {

src/py/convert.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use bytes::Bytes;
22
use pyo3::types::{PyList, PyTuple};
3+
use pyo3::types::{PyDateAccess, PyDateTime, PyDelta, PyTimeAccess, PyTzInfoAccess};
34
use pyo3::IntoPyObjectExt;
45
use pyo3::{exceptions::PyException, prelude::*};
56
use pythonize::{depythonize, pythonize};
@@ -70,6 +71,9 @@ fn basic_value_to_py_object<'py>(
7071
value::BasicValue::Time(v) => v.into_bound_py_any(py)?,
7172
value::BasicValue::LocalDateTime(v) => v.into_bound_py_any(py)?,
7273
value::BasicValue::OffsetDateTime(v) => v.into_bound_py_any(py)?,
74+
value::BasicValue::TimeDelta(v) => {
75+
PyDelta::new(py, 0, v.num_seconds() as i32, v.subsec_micros())?.into_bound_py_any(py)?
76+
}
7377
value::BasicValue::Json(v) => pythonize(py, v).into_py_result()?,
7478
value::BasicValue::Vector(v) => v
7579
.iter()
@@ -143,6 +147,13 @@ fn basic_value_from_py_object<'py>(
143147
schema::BasicValueType::OffsetDateTime => {
144148
value::BasicValue::OffsetDateTime(v.extract::<chrono::DateTime<chrono::FixedOffset>>()?)
145149
}
150+
schema::BasicValueType::TimeDelta => {
151+
let delta = v.extract::<&PyDelta>()?;
152+
value::BasicValue::TimeDelta(
153+
chrono::Duration::seconds(delta.get_days() as i64 * 86400 + delta.get_seconds() as i64)
154+
+ chrono::Duration::microseconds(delta.get_microseconds() as i64),
155+
)
156+
}
146157
schema::BasicValueType::Json => {
147158
value::BasicValue::Json(Arc::from(depythonize::<serde_json::Value>(v)?))
148159
}

0 commit comments

Comments
 (0)