Skip to content
This repository was archived by the owner on Feb 26, 2024. It is now read-only.

Commit 7b0f96e

Browse files
authored
Merge pull request ClickHouse#271 from troyanov/feature/datetime64
Add DateTime64 support
2 parents 4668557 + fbf07a0 commit 7b0f96e

File tree

4 files changed

+160
-6
lines changed

4 files changed

+160
-6
lines changed

clickhouse_test.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ func Test_Insert(t *testing.T) {
9090
fString FixedString(2),
9191
date Date,
9292
datetime DateTime,
93+
datetime64 DateTime64,
9394
ipv4 IPv4,
9495
ipv6 IPv6,
9596
ipv4str FixedString(16),
@@ -112,6 +113,7 @@ func Test_Insert(t *testing.T) {
112113
fString,
113114
date,
114115
datetime,
116+
datetime64,
115117
ipv4,
116118
ipv6,
117119
ipv4str,
@@ -153,6 +155,7 @@ func Test_Insert(t *testing.T) {
153155
fString,
154156
date,
155157
datetime,
158+
datetime64,
156159
ipv4,
157160
ipv6,
158161
ipv4str,
@@ -170,11 +173,12 @@ func Test_Insert(t *testing.T) {
170173
-1*i, -2*i, -4*i, -8*i, // int
171174
uint8(1*i), uint16(2*i), uint32(4*i), uint64(8*i), // uint
172175
1.32*float32(i), 1.64*float64(i), //float
173-
fmt.Sprintf("string %d", i), // string
174-
"RU", //fixedstring,
175-
time.Now(), //date
176-
time.Now(), //datetime
177-
"1.2.3.4", // ipv4
176+
fmt.Sprintf("string %d", i), // string
177+
"RU", //fixedstring,
178+
time.Now(), //date
179+
time.Now(), //datetime
180+
time.Now(), //datetime64
181+
"1.2.3.4", // ipv4
178182
"2001:0db8:85a3:0000:0000:8a2e:0370:7334", //ipv6
179183
column.IP(net.ParseIP("127.0.0.1").To4()),
180184
column.IP(net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")),
@@ -202,6 +206,7 @@ func Test_Insert(t *testing.T) {
202206
FixedString string
203207
Date time.Time
204208
DateTime time.Time
209+
DateTime64 time.Time
205210
Ipv6 column.IP
206211
Ipv4 column.IP
207212
Ipv4str column.IP
@@ -226,6 +231,7 @@ func Test_Insert(t *testing.T) {
226231
&item.FixedString,
227232
&item.Date,
228233
&item.DateTime,
234+
&item.DateTime64,
229235
&item.Ipv4,
230236
&item.Ipv6,
231237
&item.Ipv4str,

lib/column/column.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ func Factory(name, chType string, timezone *time.Location) (Column, error) {
146146
}, nil
147147
}
148148
switch {
149-
case strings.HasPrefix(chType, "DateTime"):
149+
case strings.HasPrefix(chType, "DateTime") && !strings.HasPrefix(chType, "DateTime64"):
150150
return &DateTime{
151151
base: base{
152152
name: name,
@@ -155,6 +155,15 @@ func Factory(name, chType string, timezone *time.Location) (Column, error) {
155155
},
156156
Timezone: timezone,
157157
}, nil
158+
case strings.HasPrefix(chType, "DateTime64"):
159+
return &DateTime64{
160+
base: base{
161+
name: name,
162+
chType: chType,
163+
valueOf: columnBaseTypes[time.Time{}],
164+
},
165+
Timezone: timezone,
166+
}, nil
158167
case strings.HasPrefix(chType, "Array"):
159168
return parseArray(name, chType, timezone)
160169
case strings.HasPrefix(chType, "Nullable"):

lib/column/column_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,45 @@ func Test_Column_DateTime(t *testing.T) {
465465
}
466466
}
467467

468+
func Test_Column_DateTime64(t *testing.T) {
469+
var (
470+
buf bytes.Buffer
471+
timeNow = time.Now().UTC()
472+
encoder = binary.NewEncoder(&buf)
473+
decoder = binary.NewDecoder(&buf)
474+
)
475+
if column, err := columns.Factory("column_name", "DateTime64(6)", time.UTC); assert.NoError(t, err) {
476+
if err := column.Write(encoder, timeNow); assert.NoError(t, err) {
477+
if v, err := column.Read(decoder); assert.NoError(t, err) {
478+
assert.Equal(t, timeNow, v)
479+
}
480+
}
481+
if err := column.Write(encoder, timeNow.In(time.UTC).Format("2006-01-02 15:04:05.999999")); assert.NoError(t, err) {
482+
if v, err := column.Read(decoder); assert.NoError(t, err) {
483+
assert.Equal(t, timeNow, v)
484+
}
485+
}
486+
if assert.Equal(t, "column_name", column.Name()) && assert.Equal(t, "DateTime64(6)", column.CHType()) {
487+
assert.Equal(t, reflect.TypeOf(time.Time{}).Kind(), column.ScanType().Kind())
488+
}
489+
if err := column.Write(encoder, int8(0)); assert.Error(t, err) {
490+
if e, ok := err.(*columns.ErrUnexpectedType); assert.True(t, ok) {
491+
assert.Equal(t, int8(0), e.T)
492+
}
493+
}
494+
if err := column.Write(encoder, int16(0)); assert.Error(t, err) {
495+
if e, ok := err.(*columns.ErrUnexpectedType); assert.True(t, ok) {
496+
assert.Equal(t, int16(0), e.T)
497+
}
498+
}
499+
if err := column.Write(encoder, int32(0)); assert.Error(t, err) {
500+
if e, ok := err.(*columns.ErrUnexpectedType); assert.True(t, ok) {
501+
assert.Equal(t, int32(0), e.T)
502+
}
503+
}
504+
}
505+
}
506+
468507
func Test_Column_DateTimeWithTZ(t *testing.T) {
469508
var (
470509
buf bytes.Buffer

lib/column/datetime64.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package column
2+
3+
import (
4+
"math"
5+
"strconv"
6+
"strings"
7+
"time"
8+
9+
"github.com/ClickHouse/clickhouse-go/lib/binary"
10+
)
11+
12+
type DateTime64 struct {
13+
base
14+
Timezone *time.Location
15+
}
16+
17+
func (dt *DateTime64) Read(decoder *binary.Decoder) (interface{}, error) {
18+
value, err := decoder.Int64()
19+
if err != nil {
20+
return nil, err
21+
}
22+
23+
precision, err := dt.getPrecision()
24+
if err != nil {
25+
return nil, err
26+
}
27+
28+
var nano int64
29+
if precision < 19 {
30+
nano = value * int64(math.Pow10(9-precision))
31+
}
32+
33+
sec := nano / int64(10e8)
34+
nsec := nano - sec*10e8
35+
36+
return time.Unix(sec, nsec).In(dt.Timezone), nil
37+
}
38+
39+
func (dt *DateTime64) Write(encoder *binary.Encoder, v interface{}) error {
40+
var timestamp int64
41+
switch value := v.(type) {
42+
case time.Time:
43+
if !value.IsZero() {
44+
timestamp = value.UnixNano()
45+
}
46+
case uint64:
47+
timestamp = int64(value)
48+
case int64:
49+
timestamp = value
50+
case string:
51+
var err error
52+
timestamp, err = dt.parse(value)
53+
if err != nil {
54+
return err
55+
}
56+
case *time.Time:
57+
if value != nil && !(*value).IsZero() {
58+
timestamp = (*value).UnixNano()
59+
}
60+
case *int64:
61+
timestamp = *value
62+
case *string:
63+
var err error
64+
timestamp, err = dt.parse(*value)
65+
if err != nil {
66+
return err
67+
}
68+
default:
69+
return &ErrUnexpectedType{
70+
T: v,
71+
Column: dt,
72+
}
73+
}
74+
75+
precision, err := dt.getPrecision()
76+
if err != nil {
77+
return err
78+
}
79+
80+
timestamp = timestamp / int64(math.Pow10(9-precision))
81+
82+
return encoder.Int64(timestamp)
83+
}
84+
85+
func (dt *DateTime64) parse(value string) (int64, error) {
86+
tv, err := time.Parse("2006-01-02 15:04:05.999", value)
87+
if err != nil {
88+
return 0, err
89+
}
90+
return tv.UnixNano(), nil
91+
}
92+
93+
func (dt *DateTime64) getPrecision() (int, error) {
94+
dtParams := dt.base.chType[11 : len(dt.base.chType)-1]
95+
precision, err := strconv.Atoi(strings.Split(dtParams, ",")[0])
96+
if err != nil {
97+
return 0, err
98+
}
99+
return precision, nil
100+
}

0 commit comments

Comments
 (0)