Skip to main content

clickhouse_native_client/column/
date.rs

1//! Date and DateTime column implementations
2//!
3//! **ClickHouse Documentation:**
4//! - [Date](https://clickhouse.com/docs/en/sql-reference/data-types/date) -
5//!   Days since 1970-01-01 (UInt16)
6//! - [Date32](https://clickhouse.com/docs/en/sql-reference/data-types/date32)
7//!   - Extended range date (Int32)
8//! - [DateTime](https://clickhouse.com/docs/en/sql-reference/data-types/datetime)
9//!   - Unix timestamp (UInt32)
10//! - [DateTime64](https://clickhouse.com/docs/en/sql-reference/data-types/datetime64)
11//!   - High-precision timestamp (Int64)
12//!
13//! ## Storage Details
14//!
15//! | Type | Storage | Range | Precision |
16//! |------|---------|-------|-----------|
17//! | `Date` | UInt16 | 1970-01-01 to 2149-06-06 | 1 day |
18//! | `Date32` | Int32 | 1900-01-01 to 2299-12-31 | 1 day |
19//! | `DateTime` | UInt32 | 1970-01-01 00:00:00 to 2106-02-07 06:28:15 UTC | 1 second |
20//! | `DateTime64(P)` | Int64 | Large range | 10^-P seconds (P=0..9) |
21//!
22//! ## Timezones
23//!
24//! `DateTime` and `DateTime64` support optional timezone parameter:
25//! - `DateTime('UTC')` - Store as UTC timestamp
26//! - `DateTime('Europe/Moscow')` - Store with timezone info
27//!
28//! The timezone affects how values are displayed and interpreted, but storage
29//! is always in Unix time.
30
31use super::{
32    Column,
33    ColumnRef,
34};
35use crate::{
36    types::Type,
37    Error,
38    Result,
39};
40use bytes::BytesMut;
41use std::sync::Arc;
42
43const SECONDS_PER_DAY: i64 = 86400;
44
45/// Column for Date type (stored as UInt16 - days since Unix epoch 1970-01-01)
46///
47/// **Range:** 1970-01-01 to 2149-06-06
48///
49/// **ClickHouse Reference:** <https://clickhouse.com/docs/en/sql-reference/data-types/date>
50///
51/// **C++ Implementation Pattern:**
52/// Uses delegation to `ColumnUInt16` for storage, matching the C++
53/// clickhouse-cpp reference implementation's `std::shared_ptr<ColumnUInt16>
54/// data_` pattern.
55pub struct ColumnDate {
56    type_: Type,
57    data: Arc<super::ColumnUInt16>, /* Delegates to ColumnUInt16, matches
58                                     * C++ pattern */
59}
60
61impl ColumnDate {
62    /// Creates a new empty Date column with the given type.
63    pub fn new(type_: Type) -> Self {
64        Self { type_, data: Arc::new(super::ColumnUInt16::new()) }
65    }
66
67    /// Returns a new column populated with the given days-since-epoch values.
68    pub fn with_data(mut self, data: Vec<u16>) -> Self {
69        self.data =
70            Arc::new(super::ColumnUInt16::from_vec(Type::uint16(), data));
71        self
72    }
73
74    /// Append days since epoch (raw UInt16 value)
75    pub fn append(&mut self, days: u16) {
76        Arc::get_mut(&mut self.data)
77            .expect("Cannot append to shared column")
78            .append(days);
79    }
80
81    /// Append from Unix timestamp (seconds since epoch)
82    pub fn append_timestamp(&mut self, timestamp: i64) {
83        let days = (timestamp / SECONDS_PER_DAY) as u16;
84        self.append(days);
85    }
86
87    /// Get raw days value at index
88    pub fn at(&self, index: usize) -> u16 {
89        self.data.at(index)
90    }
91
92    /// Get Unix timestamp (seconds) at index
93    pub fn timestamp_at(&self, index: usize) -> i64 {
94        self.data.at(index) as i64 * SECONDS_PER_DAY
95    }
96
97    /// Returns the number of elements in the column.
98    pub fn len(&self) -> usize {
99        self.data.len()
100    }
101
102    /// Returns true if the column contains no elements.
103    pub fn is_empty(&self) -> bool {
104        self.data.is_empty()
105    }
106
107    /// Get reference to underlying data column (for advanced use)
108    pub fn data(&self) -> &super::ColumnUInt16 {
109        &self.data
110    }
111}
112
113impl Column for ColumnDate {
114    fn column_type(&self) -> &Type {
115        &self.type_
116    }
117
118    fn size(&self) -> usize {
119        self.data.size()
120    }
121
122    fn clear(&mut self) {
123        Arc::get_mut(&mut self.data)
124            .expect("Cannot clear shared column")
125            .clear();
126    }
127
128    fn reserve(&mut self, new_cap: usize) {
129        Arc::get_mut(&mut self.data)
130            .expect("Cannot reserve on shared column")
131            .reserve(new_cap);
132    }
133
134    fn append_column(&mut self, other: ColumnRef) -> Result<()> {
135        let other =
136            other.as_any().downcast_ref::<ColumnDate>().ok_or_else(|| {
137                Error::TypeMismatch {
138                    expected: self.type_.name(),
139                    actual: other.column_type().name(),
140                }
141            })?;
142
143        // Delegate to underlying ColumnUInt16
144        Arc::get_mut(&mut self.data)
145            .expect("Cannot append to shared column")
146            .append_column(other.data.clone() as ColumnRef)?;
147        Ok(())
148    }
149
150    fn load_from_buffer(
151        &mut self,
152        buffer: &mut &[u8],
153        rows: usize,
154    ) -> Result<()> {
155        // Delegate to ColumnUInt16 which has bulk copy optimization
156        Arc::get_mut(&mut self.data)
157            .expect("Cannot load into shared column")
158            .load_from_buffer(buffer, rows)
159    }
160
161    fn save_to_buffer(&self, buffer: &mut BytesMut) -> Result<()> {
162        // Delegate to ColumnUInt16 which has bulk copy optimization
163        self.data.save_to_buffer(buffer)
164    }
165
166    fn clone_empty(&self) -> ColumnRef {
167        Arc::new(ColumnDate::new(self.type_.clone()))
168    }
169
170    fn slice(&self, begin: usize, len: usize) -> Result<ColumnRef> {
171        // Delegate to underlying column and wrap result
172        let sliced_data = self.data.slice(begin, len)?;
173
174        Ok(Arc::new(ColumnDate {
175            type_: self.type_.clone(),
176            data: sliced_data
177                .as_any()
178                .downcast_ref::<super::ColumnUInt16>()
179                .map(|col| {
180                    // Create new Arc from the sliced data
181                    Arc::new(super::ColumnUInt16::from_vec(
182                        Type::uint16(),
183                        col.data().to_vec(),
184                    ))
185                })
186                .expect("Slice should return ColumnUInt16"),
187        }))
188    }
189
190    fn as_any(&self) -> &dyn std::any::Any {
191        self
192    }
193
194    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
195        self
196    }
197}
198
199/// Column for Date32 type (stored as Int32 - days since Unix epoch 1970-01-01)
200/// Extended range: 1900-01-01 to 2299-12-31
201///
202/// **C++ Implementation Pattern:**
203/// Uses delegation to `ColumnInt32` for storage, matching the C++
204/// clickhouse-cpp reference implementation's `std::shared_ptr<ColumnInt32>
205/// data_` pattern.
206pub struct ColumnDate32 {
207    type_: Type,
208    data: Arc<super::ColumnInt32>, /* Delegates to ColumnInt32, matches C++
209                                    * pattern */
210}
211
212impl ColumnDate32 {
213    /// Creates a new empty Date32 column with the given type.
214    pub fn new(type_: Type) -> Self {
215        Self { type_, data: Arc::new(super::ColumnInt32::new()) }
216    }
217
218    /// Returns a new column populated with the given days-since-epoch values.
219    pub fn with_data(mut self, data: Vec<i32>) -> Self {
220        self.data =
221            Arc::new(super::ColumnInt32::from_vec(Type::int32(), data));
222        self
223    }
224
225    /// Append days since epoch (raw Int32 value)
226    pub fn append(&mut self, days: i32) {
227        Arc::get_mut(&mut self.data)
228            .expect("Cannot append to shared column")
229            .append(days);
230    }
231
232    /// Append from Unix timestamp (seconds since epoch)
233    pub fn append_timestamp(&mut self, timestamp: i64) {
234        let days = (timestamp / SECONDS_PER_DAY) as i32;
235        self.append(days);
236    }
237
238    /// Get raw days value at index
239    pub fn at(&self, index: usize) -> i32 {
240        self.data.at(index)
241    }
242
243    /// Get Unix timestamp (seconds) at index
244    pub fn timestamp_at(&self, index: usize) -> i64 {
245        self.data.at(index) as i64 * SECONDS_PER_DAY
246    }
247
248    /// Returns the number of elements in the column.
249    pub fn len(&self) -> usize {
250        self.data.len()
251    }
252
253    /// Returns true if the column contains no elements.
254    pub fn is_empty(&self) -> bool {
255        self.data.is_empty()
256    }
257
258    /// Get reference to underlying data column (for advanced use)
259    pub fn data(&self) -> &super::ColumnInt32 {
260        &self.data
261    }
262}
263
264impl Column for ColumnDate32 {
265    fn column_type(&self) -> &Type {
266        &self.type_
267    }
268
269    fn size(&self) -> usize {
270        self.data.size()
271    }
272
273    fn clear(&mut self) {
274        Arc::get_mut(&mut self.data)
275            .expect("Cannot clear shared column")
276            .clear();
277    }
278
279    fn reserve(&mut self, new_cap: usize) {
280        Arc::get_mut(&mut self.data)
281            .expect("Cannot reserve on shared column")
282            .reserve(new_cap);
283    }
284
285    fn append_column(&mut self, other: ColumnRef) -> Result<()> {
286        let other = other.as_any().downcast_ref::<ColumnDate32>().ok_or_else(
287            || Error::TypeMismatch {
288                expected: self.type_.name(),
289                actual: other.column_type().name(),
290            },
291        )?;
292
293        // Delegate to underlying ColumnInt32
294        Arc::get_mut(&mut self.data)
295            .expect("Cannot append to shared column")
296            .append_column(other.data.clone() as ColumnRef)?;
297        Ok(())
298    }
299
300    fn load_from_buffer(
301        &mut self,
302        buffer: &mut &[u8],
303        rows: usize,
304    ) -> Result<()> {
305        // Delegate to ColumnInt32 which has bulk copy optimization
306        Arc::get_mut(&mut self.data)
307            .expect("Cannot load into shared column")
308            .load_from_buffer(buffer, rows)
309    }
310
311    fn save_to_buffer(&self, buffer: &mut BytesMut) -> Result<()> {
312        // Delegate to ColumnInt32 which has bulk copy optimization
313        self.data.save_to_buffer(buffer)
314    }
315
316    fn clone_empty(&self) -> ColumnRef {
317        Arc::new(ColumnDate32::new(self.type_.clone()))
318    }
319
320    fn slice(&self, begin: usize, len: usize) -> Result<ColumnRef> {
321        // Delegate to underlying column and wrap result
322        let sliced_data = self.data.slice(begin, len)?;
323
324        Ok(Arc::new(ColumnDate32 {
325            type_: self.type_.clone(),
326            data: sliced_data
327                .as_any()
328                .downcast_ref::<super::ColumnInt32>()
329                .map(|col| {
330                    // Create new Arc from the sliced data
331                    Arc::new(super::ColumnInt32::from_vec(
332                        Type::int32(),
333                        col.data().to_vec(),
334                    ))
335                })
336                .expect("Slice should return ColumnInt32"),
337        }))
338    }
339
340    fn as_any(&self) -> &dyn std::any::Any {
341        self
342    }
343
344    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
345        self
346    }
347}
348
349/// Column for DateTime type (stored as UInt32 - seconds since Unix epoch)
350/// Range: 1970-01-01 00:00:00 to 2106-02-07 06:28:15
351///
352/// **C++ Implementation Pattern:**
353/// Uses delegation to `ColumnUInt32` for storage, matching the C++
354/// clickhouse-cpp reference implementation's `std::shared_ptr<ColumnUInt32>
355/// data_` pattern.
356pub struct ColumnDateTime {
357    type_: Type,
358    data: Arc<super::ColumnUInt32>, /* Delegates to ColumnUInt32, matches
359                                     * C++ pattern */
360    timezone: Option<String>,
361}
362
363impl ColumnDateTime {
364    /// Creates a new empty DateTime column, extracting timezone from the type.
365    pub fn new(type_: Type) -> Self {
366        let timezone = match &type_ {
367            Type::DateTime { timezone } => timezone.clone(),
368            _ => None,
369        };
370
371        Self { type_, data: Arc::new(super::ColumnUInt32::new()), timezone }
372    }
373
374    /// Returns a new column populated with the given Unix timestamp values.
375    pub fn with_data(mut self, data: Vec<u32>) -> Self {
376        self.data =
377            Arc::new(super::ColumnUInt32::from_vec(Type::uint32(), data));
378        self
379    }
380
381    /// Append Unix timestamp (seconds since epoch)
382    pub fn append(&mut self, timestamp: u32) {
383        Arc::get_mut(&mut self.data)
384            .expect("Cannot append to shared column")
385            .append(timestamp);
386    }
387
388    /// Get timestamp at index
389    pub fn at(&self, index: usize) -> u32 {
390        self.data.at(index)
391    }
392
393    /// Get timezone
394    pub fn timezone(&self) -> Option<&str> {
395        self.timezone.as_deref()
396    }
397
398    /// Returns the number of elements in the column.
399    pub fn len(&self) -> usize {
400        self.data.len()
401    }
402
403    /// Returns true if the column contains no elements.
404    pub fn is_empty(&self) -> bool {
405        self.data.is_empty()
406    }
407
408    /// Get reference to underlying data column (for advanced use)
409    pub fn data(&self) -> &super::ColumnUInt32 {
410        &self.data
411    }
412}
413
414impl Column for ColumnDateTime {
415    fn column_type(&self) -> &Type {
416        &self.type_
417    }
418
419    fn size(&self) -> usize {
420        self.data.size()
421    }
422
423    fn clear(&mut self) {
424        Arc::get_mut(&mut self.data)
425            .expect("Cannot clear shared column")
426            .clear();
427    }
428
429    fn reserve(&mut self, new_cap: usize) {
430        Arc::get_mut(&mut self.data)
431            .expect("Cannot reserve on shared column")
432            .reserve(new_cap);
433    }
434
435    fn append_column(&mut self, other: ColumnRef) -> Result<()> {
436        let other = other
437            .as_any()
438            .downcast_ref::<ColumnDateTime>()
439            .ok_or_else(|| Error::TypeMismatch {
440                expected: self.type_.name(),
441                actual: other.column_type().name(),
442            })?;
443
444        // Delegate to underlying ColumnUInt32
445        Arc::get_mut(&mut self.data)
446            .expect("Cannot append to shared column")
447            .append_column(other.data.clone() as ColumnRef)?;
448        Ok(())
449    }
450
451    fn load_from_buffer(
452        &mut self,
453        buffer: &mut &[u8],
454        rows: usize,
455    ) -> Result<()> {
456        // Delegate to ColumnUInt32 which has bulk copy optimization
457        Arc::get_mut(&mut self.data)
458            .expect("Cannot load into shared column")
459            .load_from_buffer(buffer, rows)
460    }
461
462    fn save_to_buffer(&self, buffer: &mut BytesMut) -> Result<()> {
463        // Delegate to ColumnUInt32 which has bulk copy optimization
464        self.data.save_to_buffer(buffer)
465    }
466
467    fn clone_empty(&self) -> ColumnRef {
468        Arc::new(ColumnDateTime::new(self.type_.clone()))
469    }
470
471    fn slice(&self, begin: usize, len: usize) -> Result<ColumnRef> {
472        // Delegate to underlying column and wrap result
473        let sliced_data = self.data.slice(begin, len)?;
474
475        Ok(Arc::new(ColumnDateTime {
476            type_: self.type_.clone(),
477            timezone: self.timezone.clone(),
478            data: sliced_data
479                .as_any()
480                .downcast_ref::<super::ColumnUInt32>()
481                .map(|col| {
482                    // Create new Arc from the sliced data
483                    Arc::new(super::ColumnUInt32::from_vec(
484                        Type::uint32(),
485                        col.data().to_vec(),
486                    ))
487                })
488                .expect("Slice should return ColumnUInt32"),
489        }))
490    }
491
492    fn as_any(&self) -> &dyn std::any::Any {
493        self
494    }
495
496    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
497        self
498    }
499}
500
501/// Column for DateTime64 type (stored as Int64 - subsecond precision)
502/// Supports arbitrary sub-second precision, extended date range
503///
504/// **C++ Implementation Pattern:**
505/// C++ uses delegation to `ColumnDecimal`, but we use `ColumnInt64` for
506/// simplicity since DateTime64 is fundamentally stored as Int64.
507pub struct ColumnDateTime64 {
508    type_: Type,
509    data: Arc<super::ColumnInt64>, // Delegates to ColumnInt64
510    precision: usize,
511    timezone: Option<String>,
512}
513
514impl ColumnDateTime64 {
515    /// Creates a new empty DateTime64 column, extracting precision and
516    /// timezone from the type.
517    pub fn new(type_: Type) -> Self {
518        let (precision, timezone) = match &type_ {
519            Type::DateTime64 { precision, timezone } => {
520                (*precision, timezone.clone())
521            }
522            _ => panic!("ColumnDateTime64 requires DateTime64 type"),
523        };
524
525        Self {
526            type_,
527            data: Arc::new(super::ColumnInt64::new()),
528            precision,
529            timezone,
530        }
531    }
532
533    /// Returns a new column populated with the given sub-second timestamp
534    /// values.
535    pub fn with_data(mut self, data: Vec<i64>) -> Self {
536        self.data =
537            Arc::new(super::ColumnInt64::from_vec(Type::int64(), data));
538        self
539    }
540
541    /// Append timestamp with precision (e.g., for precision 3, value is
542    /// milliseconds)
543    pub fn append(&mut self, value: i64) {
544        Arc::get_mut(&mut self.data)
545            .expect("Cannot append to shared column")
546            .append(value);
547    }
548
549    /// Get timestamp at index
550    pub fn at(&self, index: usize) -> i64 {
551        self.data.at(index)
552    }
553
554    /// Get precision (0-9, number of decimal places)
555    pub fn precision(&self) -> usize {
556        self.precision
557    }
558
559    /// Get timezone
560    pub fn timezone(&self) -> Option<&str> {
561        self.timezone.as_deref()
562    }
563
564    /// Returns the number of elements in the column.
565    pub fn len(&self) -> usize {
566        self.data.len()
567    }
568
569    /// Returns true if the column contains no elements.
570    pub fn is_empty(&self) -> bool {
571        self.data.is_empty()
572    }
573
574    /// Get reference to underlying data column (for advanced use)
575    pub fn data(&self) -> &super::ColumnInt64 {
576        &self.data
577    }
578}
579
580impl Column for ColumnDateTime64 {
581    fn column_type(&self) -> &Type {
582        &self.type_
583    }
584
585    fn size(&self) -> usize {
586        self.data.size()
587    }
588
589    fn clear(&mut self) {
590        Arc::get_mut(&mut self.data)
591            .expect("Cannot clear shared column")
592            .clear();
593    }
594
595    fn reserve(&mut self, new_cap: usize) {
596        Arc::get_mut(&mut self.data)
597            .expect("Cannot reserve on shared column")
598            .reserve(new_cap);
599    }
600
601    fn append_column(&mut self, other: ColumnRef) -> Result<()> {
602        let other = other
603            .as_any()
604            .downcast_ref::<ColumnDateTime64>()
605            .ok_or_else(|| Error::TypeMismatch {
606                expected: self.type_.name(),
607                actual: other.column_type().name(),
608            })?;
609
610        if self.precision != other.precision {
611            return Err(Error::TypeMismatch {
612                expected: format!("DateTime64({})", self.precision),
613                actual: format!("DateTime64({})", other.precision),
614            });
615        }
616
617        // Delegate to underlying ColumnInt64
618        Arc::get_mut(&mut self.data)
619            .expect("Cannot append to shared column")
620            .append_column(other.data.clone() as ColumnRef)?;
621        Ok(())
622    }
623
624    fn load_from_buffer(
625        &mut self,
626        buffer: &mut &[u8],
627        rows: usize,
628    ) -> Result<()> {
629        // Delegate to ColumnInt64 which has bulk copy optimization
630        Arc::get_mut(&mut self.data)
631            .expect("Cannot load into shared column")
632            .load_from_buffer(buffer, rows)
633    }
634
635    fn save_to_buffer(&self, buffer: &mut BytesMut) -> Result<()> {
636        // Delegate to ColumnInt64 which has bulk copy optimization
637        self.data.save_to_buffer(buffer)
638    }
639
640    fn clone_empty(&self) -> ColumnRef {
641        Arc::new(ColumnDateTime64::new(self.type_.clone()))
642    }
643
644    fn slice(&self, begin: usize, len: usize) -> Result<ColumnRef> {
645        // Delegate to underlying column and wrap result
646        let sliced_data = self.data.slice(begin, len)?;
647
648        Ok(Arc::new(ColumnDateTime64 {
649            type_: self.type_.clone(),
650            precision: self.precision,
651            timezone: self.timezone.clone(),
652            data: sliced_data
653                .as_any()
654                .downcast_ref::<super::ColumnInt64>()
655                .map(|col| {
656                    // Create new Arc from the sliced data
657                    Arc::new(super::ColumnInt64::from_vec(
658                        Type::int64(),
659                        col.data().to_vec(),
660                    ))
661                })
662                .expect("Slice should return ColumnInt64"),
663        }))
664    }
665
666    fn as_any(&self) -> &dyn std::any::Any {
667        self
668    }
669
670    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
671        self
672    }
673}
674
675#[cfg(test)]
676#[cfg_attr(coverage_nightly, coverage(off))]
677mod tests {
678    use super::*;
679
680    #[test]
681    fn test_date_append_and_retrieve() {
682        let mut col = ColumnDate::new(Type::date());
683        col.append(19000); // Days since epoch
684        col.append(19001);
685
686        assert_eq!(col.len(), 2);
687        assert_eq!(col.at(0), 19000);
688        assert_eq!(col.at(1), 19001);
689    }
690
691    #[test]
692    fn test_date_timestamp() {
693        let mut col = ColumnDate::new(Type::date());
694        col.append_timestamp(1640995200); // 2022-01-01 00:00:00
695
696        assert_eq!(col.len(), 1);
697        let days = col.at(0);
698        assert_eq!(days, 18993);
699    }
700
701    #[test]
702    fn test_date32() {
703        let mut col = ColumnDate32::new(Type::date32());
704        col.append(-25567); // 1900-01-01 (negative days)
705        col.append(0); // 1970-01-01
706        col.append(100000); // Future date
707
708        assert_eq!(col.len(), 3);
709        assert_eq!(col.at(0), -25567);
710        assert_eq!(col.at(1), 0);
711        assert_eq!(col.at(2), 100000);
712    }
713
714    #[test]
715    fn test_datetime() {
716        let mut col = ColumnDateTime::new(Type::datetime(None));
717        col.append(1640995200); // 2022-01-01 00:00:00 UTC
718        col.append(1640995201);
719
720        assert_eq!(col.len(), 2);
721        assert_eq!(col.at(0), 1640995200);
722        assert_eq!(col.at(1), 1640995201);
723    }
724
725    #[test]
726    fn test_datetime_with_timezone() {
727        let mut col =
728            ColumnDateTime::new(Type::datetime(Some("UTC".to_string())));
729        col.append(1640995200);
730
731        assert_eq!(col.timezone(), Some("UTC"));
732        assert_eq!(col.at(0), 1640995200);
733    }
734
735    #[test]
736    fn test_datetime64() {
737        let mut col = ColumnDateTime64::new(Type::datetime64(3, None)); // millisecond precision
738        col.append(1640995200000); // 2022-01-01 00:00:00.000 UTC
739        col.append(1640995200123);
740
741        assert_eq!(col.len(), 2);
742        assert_eq!(col.precision(), 3);
743        assert_eq!(col.at(0), 1640995200000);
744        assert_eq!(col.at(1), 1640995200123);
745    }
746}