Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(proto): improve DateTime64 precision handling #133

Merged
merged 1 commit into from
Jun 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions block_fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,12 +188,11 @@ func FuzzDecodeBlockAuto(f *testing.F) {
{},
{100},
}),
proto.ColDateTime64{
Precision: 9,
(&proto.ColDateTime64{
Data: []proto.DateTime64{
1, 2, 3,
},
},
}).WithPrecision(9),
makeArr[string](new(proto.ColStr).LowCardinality(), [][]string{
{"foo", "bar", "baz"},
{"1000", "20000", "3000", "40000", "5000", "6000", "abc"},
Expand Down
4 changes: 2 additions & 2 deletions otel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ type OTEL struct {
func (t *OTEL) Input() proto.Input {
return proto.Input{
{Name: "body", Data: t.Body},
{Name: "timestamp", Data: t.Timestamp.Wrap(proto.PrecisionNano)},
{Name: "timestamp", Data: t.Timestamp},
{Name: "trace_id", Data: t.TraceID},
{Name: "span_id", Data: t.SpanID},
{Name: "severity_text", Data: &t.SevText},
Expand Down Expand Up @@ -91,7 +91,7 @@ type OTELRow struct {

func (t *OTEL) Append(row OTELRow) {
t.Body.AppendBytes(row.Body)
t.Timestamp.Append(proto.DateTime64(row.Timestamp).Time(proto.PrecisionNano))
t.Timestamp.AppendRaw(proto.DateTime64(row.Timestamp))
t.SevNumber.Append(row.SeverityNumber)
t.SevText.Append(row.SeverityText)

Expand Down
74 changes: 65 additions & 9 deletions proto/col_datetime64.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package proto

import (
"fmt"
"strconv"
"strings"
"time"

"github.com/go-faster/errors"
Expand All @@ -14,14 +16,19 @@ var (
)

// ColDateTime64 implements ColumnOf[time.Time].
//
// If Precision is not set, Append and Row() panics.
// Use ColDateTime64Raw to work with raw DateTime64 values.
type ColDateTime64 struct {
Data []DateTime64
Precision Precision
Location *time.Location
Data []DateTime64
Location *time.Location
Precision Precision
PrecisionSet bool
}

func (c *ColDateTime64) WithPrecision(p Precision) *ColDateTime64 {
c.Precision = p
c.PrecisionSet = true
return c
}

Expand All @@ -39,14 +46,26 @@ func (c *ColDateTime64) Reset() {
}

func (c ColDateTime64) Type() ColumnType {
sub := ColumnType(strconv.Itoa(int(c.Precision)))
return ColumnTypeDateTime64.Sub(sub)
var elems []string
if p := c.Precision; c.PrecisionSet {
elems = append(elems, strconv.Itoa(int(p)))
}
if loc := c.Location; loc != nil {
elems = append(elems, fmt.Sprintf(`'%s'`, loc))
}
return ColumnTypeDateTime64.With(elems...)
}

func (c *ColDateTime64) Infer(t ColumnType) error {
// TODO(ernado): handle (ignore) timezone
pRaw := t.Elem()
n, err := strconv.ParseUint(string(pRaw), 10, 8)
elem := string(t.Elem())
if elem == "" {
return errors.Errorf("invalid DateTime64: no elements in %q", t)
}
elems := strings.SplitN(elem, ",", 2)
for i := range elems {
elems[i] = strings.Trim(elems[i], `' `)
}
n, err := strconv.ParseUint(elems[0], 10, 8)
if err != nil {
return errors.Wrap(err, "parse precision")
}
Expand All @@ -55,10 +74,21 @@ func (c *ColDateTime64) Infer(t ColumnType) error {
return errors.Errorf("precision %d is invalid", n)
}
c.Precision = p
c.PrecisionSet = true
if len(elems) > 1 {
loc, err := time.LoadLocation(elems[1])
if err != nil {
return errors.Wrap(err, "invalid location")
}
c.Location = loc
}
return nil
}

func (c ColDateTime64) Row(i int) time.Time {
if !c.PrecisionSet {
panic("DateTime64: no precision set")
}
return c.Data[i].Time(c.Precision).In(c.loc())
}

Expand All @@ -70,6 +100,32 @@ func (c ColDateTime64) loc() *time.Location {
return c.Location
}

func (c *ColDateTime64) AppendRaw(v DateTime64) {
c.Data = append(c.Data, v)
}

func (c *ColDateTime64) Append(v time.Time) {
c.Data = append(c.Data, ToDateTime64(v, c.Precision))
if !c.PrecisionSet {
panic("DateTime64: no precision set")
}
c.AppendRaw(ToDateTime64(v, c.Precision))
}

// Raw version of ColDateTime64 for ColumnOf[DateTime64].
func (c ColDateTime64) Raw() *ColDateTime64Raw {
return &ColDateTime64Raw{ColDateTime64: c}
}

var (
_ ColumnOf[DateTime64] = (*ColDateTime64Raw)(nil)
_ Inferable = (*ColDateTime64Raw)(nil)
_ Column = (*ColDateTime64Raw)(nil)
)

// ColDateTime64Raw is DateTime64 wrapper to implement ColumnOf[DateTime64].
type ColDateTime64Raw struct {
ColDateTime64
}

func (c *ColDateTime64Raw) Append(v DateTime64) { c.AppendRaw(v) }
func (c ColDateTime64Raw) Row(i int) DateTime64 { return c.Data[i] }
45 changes: 12 additions & 33 deletions proto/datetime64.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,22 @@ type Precision byte

// Duration returns duration of single tick for precision.
func (p Precision) Duration() time.Duration {
d := time.Nanosecond
for i := PrecisionNano; i > p; i-- {
d *= 10
}
return d
return time.Nanosecond * time.Duration(p.Scale())
}

// Valid reports whether precision is valid.
func (p Precision) Valid() bool {
return p <= PrecisionMax
}

func (p Precision) Scale() int64 {
d := int64(1)
for i := PrecisionNano; i > p; i-- {
d *= 10
}
return d
}

const (
// PrecisionSecond is one second precision.
PrecisionSecond Precision = 0
Expand All @@ -46,38 +50,13 @@ type DateTime64 int64

// ToDateTime64 converts time.Time to DateTime64.
func ToDateTime64(t time.Time, p Precision) DateTime64 {
switch p {
case PrecisionMicro:
return DateTime64(t.UnixMicro())
case PrecisionMilli:
return DateTime64(t.UnixMilli())
case PrecisionNano:
return DateTime64(t.UnixNano())
case PrecisionSecond:
return DateTime64(t.Unix())
default:
// TODO(ernado): support all precisions
panic("precision not supported")
}
return DateTime64(t.UnixNano() / p.Scale())
}

// Time returns DateTime64 as time.Time.
func (d DateTime64) Time(p Precision) time.Time {
switch p {
case PrecisionMicro:
return time.UnixMicro(int64(d))
case PrecisionMilli:
return time.UnixMilli(int64(d))
case PrecisionNano:
nsec := int64(d)
return time.Unix(nsec/1e9, nsec%1e9)
case PrecisionSecond:
sec := int64(d)
return time.Unix(sec, 0)
default:
// TODO(ernado): support all precisions
panic("precision not supported")
}
nsec := int64(d) * p.Scale()
return time.Unix(nsec/1e9, nsec%1e9)
}

// Wrap column with explicit precision.
Expand Down