Skip to content

Commit

Permalink
types: add timestamp type
Browse files Browse the repository at this point in the history
  • Loading branch information
asdine committed Nov 28, 2023
1 parent 84a6c9e commit e444201
Show file tree
Hide file tree
Showing 33 changed files with 601 additions and 42 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ gopls.log
go.work
go.work.sum

lab/
# Local experiments
lab/
1 change: 1 addition & 0 deletions cmd/genji/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/getsentry/sentry-go v0.25.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-module/carbon/v2 v2.2.13 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/klauspost/compress v1.17.2 // indirect
Expand Down
9 changes: 9 additions & 0 deletions cmd/genji/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:Yyn
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
Expand All @@ -47,6 +48,8 @@ github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxI
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-module/carbon/v2 v2.2.13 h1:8BzSrasTFP4sIXA78i4I4LxxlIxetwGOUGu/+WfjdVg=
github.com/golang-module/carbon/v2 v2.2.13/go.mod h1:XDALX7KgqmHk95xyLeaqX9/LJGbfLATyruTziq68SZ8=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
Expand Down Expand Up @@ -104,6 +107,11 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
Expand Down Expand Up @@ -157,5 +165,6 @@ google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
29 changes: 29 additions & 0 deletions document/cast.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"math"
"strconv"
"time"

"github.com/genjidb/genji/types"
)
Expand All @@ -22,6 +23,8 @@ func CastAs(v types.Value, t types.ValueType) (types.Value, error) {
return CastAsInteger(v)
case types.DoubleValue:
return CastAsDouble(v)
case types.TimestampValue:
return CastAsTimestamp(v)
case types.BlobValue:
return CastAsBlob(v)
case types.TextValue:
Expand Down Expand Up @@ -133,6 +136,30 @@ func CastAsDouble(v types.Value) (types.Value, error) {
return nil, fmt.Errorf("cannot cast %s as double", v.Type())
}

// CastAsTimestamp casts according to the following rules:
// Text: uses carbon.Parse to determine the timestamp value
// it fails if the text doesn't contain a valid timestamp.
// Any other type is considered an invalid cast.
func CastAsTimestamp(v types.Value) (types.Value, error) {
// Null values always remain null.
if v.Type() == types.NullValue {
return v, nil
}

switch v.Type() {
case types.TimestampValue:
return v, nil
case types.TextValue:
t, err := types.ParseTimestamp(types.As[string](v))
if err != nil {
return nil, fmt.Errorf(`cannot cast %q as timestamp: %w`, v.V(), err)
}
return types.NewTimestampValue(t), nil
}

return nil, fmt.Errorf("cannot cast %s as timestamp", v.Type())
}

// CastAsText returns a JSON representation of v.
// If the representation is a string, it gets unquoted.
func CastAsText(v types.Value) (types.Value, error) {
Expand All @@ -146,6 +173,8 @@ func CastAsText(v types.Value) (types.Value, error) {
return v, nil
case types.BlobValue:
return types.NewTextValue(base64.StdEncoding.EncodeToString(types.As[[]byte](v))), nil
case types.TimestampValue:
return types.NewTextValue(types.As[time.Time](v).Format(time.RFC3339Nano)), nil
}

d, err := v.MarshalJSON()
Expand Down
15 changes: 15 additions & 0 deletions document/cast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package document
import (
"math"
"testing"
"time"

"github.com/genjidb/genji/internal/testutil/assert"
"github.com/genjidb/genji/types"
Expand All @@ -14,10 +15,12 @@ func TestCastAs(t *testing.T) {
v, want types.Value
fails bool
}
now := time.Now()

boolV := types.NewBoolValue(true)
integerV := types.NewIntegerValue(10)
doubleV := types.NewDoubleValue(10.5)
tsV := types.NewTimestampValue(now)
textV := types.NewTextValue("foo")
blobV := types.NewBlobValue([]byte("asdine"))
arrayV := types.NewArrayValue(NewValueBuffer().
Expand Down Expand Up @@ -90,6 +93,18 @@ func TestCastAs(t *testing.T) {
})
})

t.Run("ts", func(t *testing.T) {
check(t, types.TimestampValue, []test{
{boolV, nil, true},
{integerV, nil, true},
{doubleV, nil, true},
{types.NewTextValue(now.Format(time.RFC3339Nano)), tsV, false},
{blobV, nil, true},
{arrayV, nil, true},
{docV, nil, true},
})
})

t.Run("text", func(t *testing.T) {
check(t, types.TextValue, []test{
{boolV, types.NewTextValue("true"), false},
Expand Down
4 changes: 2 additions & 2 deletions document/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,13 +200,13 @@ func newFromStruct(ref reflect.Value) (types.Document, error) {
}

// NewValue creates a value whose type is infered from x.
func NewValue(x interface{}) (types.Value, error) {
func NewValue(x any) (types.Value, error) {
// Attempt exact matches first:
switch v := x.(type) {
case time.Duration:
return types.NewIntegerValue(v.Nanoseconds()), nil
case time.Time:
return types.NewTextValue(v.Format(time.RFC3339Nano)), nil
return types.NewTimestampValue(v), nil
case nil:
return types.NewNullValue(), nil
case types.Document:
Expand Down
2 changes: 1 addition & 1 deletion document/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func TestNewValue(t *testing.T) {
{"null", nil, nil},
{"document", document.NewFieldBuffer().Add("a", types.NewIntegerValue(10)), document.NewFieldBuffer().Add("a", types.NewIntegerValue(10))},
{"array", document.NewValueBuffer(types.NewIntegerValue(10)), document.NewValueBuffer(types.NewIntegerValue(10))},
{"time", now, now.Format(time.RFC3339Nano)},
{"time", now, now.UTC()},
{"bytes", myBytes("bar"), []byte("bar")},
{"string", myString("bar"), "bar"},
{"myUint", myUint(10), int64(10)},
Expand Down
3 changes: 3 additions & 0 deletions document/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"sort"
"strings"
"time"

"github.com/buger/jsonparser"
"github.com/cockroachdb/errors"
Expand Down Expand Up @@ -323,6 +324,8 @@ func CloneValue(v types.Value) (types.Value, error) {
return types.NewIntegerValue(types.As[int64](v)), nil
case types.DoubleValue:
return types.NewDoubleValue(types.As[float64](v)), nil
case types.TimestampValue:
return types.NewTimestampValue(types.As[time.Time](v)), nil
case types.TextValue:
return types.NewTextValue(strings.Clone(types.As[string](v))), nil
case types.BlobValue:
Expand Down
10 changes: 4 additions & 6 deletions document/document_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ func TestNewFromStruct(t *testing.T) {
case 25:
require.EqualValues(t, types.IntegerValue, v.Type())
case 26:
require.EqualValues(t, types.TextValue, v.Type())
require.EqualValues(t, types.TimestampValue, v.Type())
default:
require.FailNowf(t, "", "unknown field %q", f)
}
Expand Down Expand Up @@ -518,11 +518,9 @@ func TestNewFromStruct(t *testing.T) {

v, err = doc.GetByField("bb")
assert.NoError(t, err)
var timeStr string
assert.NoError(t, document.ScanValue(v, &timeStr))
parsedTime, err := time.Parse(time.RFC3339Nano, timeStr)
assert.NoError(t, err)
require.Equal(t, u.BB, parsedTime)
var tm time.Time
assert.NoError(t, document.ScanValue(v, &tm))
require.Equal(t, u.BB, tm)
})

t.Run("pointers", func(t *testing.T) {
Expand Down
6 changes: 5 additions & 1 deletion document/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,14 +345,18 @@ func scanValue(v types.Value, ref reflect.Value) error {
// test with supported stdlib types
switch ref.Type().String() {
case "time.Time":
if v.Type() == types.TextValue {
switch v.Type() {
case types.TextValue:
parsed, err := time.Parse(time.RFC3339Nano, types.As[string](v))
if err != nil {
return err
}

ref.Set(reflect.ValueOf(parsed))
return nil
case types.TimestampValue:
ref.Set(reflect.ValueOf(types.As[time.Time](v)))
return nil
}
}

Expand Down
11 changes: 7 additions & 4 deletions document/scan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ func TestScan(t *testing.T) {
Add("baz", types.NewTextValue("baz")).
Add("bat", types.NewTextValue("bat")).
Add("-", types.NewTextValue("bat")),
))
)).
Add("z", types.NewTimestampValue(now))

type foo struct {
Foo string
Expand Down Expand Up @@ -153,8 +154,9 @@ func TestScan(t *testing.T) {
Pub string `genji:"bar"`
Bat string
}
var z time.Time

err = document.Scan(doc, &a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &r, &s, &u, &v, &w, &x, &y)
err = document.Scan(doc, &a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &r, &s, &u, &v, &w, &x, &y, &z)
assert.NoError(t, err)
require.Equal(t, a, []byte("foo"))
require.Equal(t, b, "bar")
Expand Down Expand Up @@ -187,6 +189,7 @@ func TestScan(t *testing.T) {
require.Equal(t, []*foo{{Foo: "a", Pub: strPtr("b")}, {Foo: "c", Pub: strPtr("d")}}, v)
require.Equal(t, [4]int{1, 2, 3, 4}, w)
require.Equal(t, [4]uint8{1, 2, 3, 4}, x)
require.Equal(t, now.UTC(), z)

t.Run("DocumentScanner", func(t *testing.T) {
var ds documentScanner
Expand All @@ -202,14 +205,14 @@ func TestScan(t *testing.T) {
m := make(map[string]interface{})
err := document.MapScan(doc, m)
assert.NoError(t, err)
require.Len(t, m, 23)
require.Len(t, m, 24)
})

t.Run("MapPtr", func(t *testing.T) {
var m map[string]interface{}
err := document.MapScan(doc, &m)
assert.NoError(t, err)
require.Len(t, m, 23)
require.Len(t, m, 24)
})

t.Run("Small Slice", func(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/buger/jsonparser v1.1.1
github.com/cockroachdb/errors v1.11.1
github.com/cockroachdb/pebble v0.0.0-20231027194153-ed45a7767175
github.com/golang-module/carbon/v2 v2.2.13
github.com/google/go-cmp v0.6.0
github.com/stretchr/testify v1.8.4
golang.org/x/sync v0.4.0
Expand Down
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZ
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI=
Expand All @@ -27,6 +28,8 @@ github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxI
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-module/carbon/v2 v2.2.13 h1:8BzSrasTFP4sIXA78i4I4LxxlIxetwGOUGu/+WfjdVg=
github.com/golang-module/carbon/v2 v2.2.13/go.mod h1:XDALX7KgqmHk95xyLeaqX9/LJGbfLATyruTziq68SZ8=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
Expand Down Expand Up @@ -61,6 +64,11 @@ github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3c
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
Expand Down Expand Up @@ -104,5 +112,6 @@ google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
6 changes: 6 additions & 0 deletions internal/database/constraint.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ func (f FieldConstraints) ConvertValueAtPath(path document.Path, v types.Value,
// convert the value using field constraints type information.
// if there is a type constraint on a path, apply it.
// if a value is an integer and has no constraint, convert it to double.
// if a value is a timestamp and has no constraint, convert it to text.
func (f FieldConstraints) convertScalarAtPath(path document.Path, v types.Value, conversionFn ConversionFunc) (types.Value, error) {
fc := f.GetFieldConstraintForPath(path)
if fc != nil {
Expand All @@ -163,6 +164,11 @@ func (f FieldConstraints) convertScalarAtPath(path document.Path, v types.Value,
return newV, nil
}

if v.Type() == types.TimestampValue {
newV, _ := document.CastAsText(v)
return newV, nil
}

return v, nil
}

Expand Down
20 changes: 20 additions & 0 deletions internal/database/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ func encodeDocument(tx *Transaction, dst []byte, fcs *FieldConstraints, d types.
if err != nil {
return nil, err
}
} else if v.Type() == types.TimestampValue {
// without a type constraint, timestamp values must
// always be stored as text to avoid mixed representations.
v, err = document.CastAsText(v)
if err != nil {
return nil, err
}
}

// Encode the value only.
Expand Down Expand Up @@ -127,6 +134,15 @@ func encodeExtraFields(dst []byte, fcs *FieldConstraints, d types.Document) ([]b
dst = encoding.EncodeText(dst, field)

// then encode the value
if value.Type() == types.TimestampValue {
// without a type constraint, timestamp values must
// always be stored as text to avoid mixed representations.
value, err = document.CastAsText(value)
if err != nil {
return err
}
}

dst, err = encoding.EncodeValue(dst, value, false)
return err
})
Expand Down Expand Up @@ -182,6 +198,10 @@ func (e *EncodedDocument) decodeValue(fc *FieldConstraint, b []byte) (types.Valu

v, n := encoding.DecodeValue(b, fc.Type == types.AnyValue || fc.Type == types.ArrayValue /* intAsDouble */)

if fc.Type == types.TimestampValue && v.Type() == types.IntegerValue {
v = types.NewTimestampValue(encoding.ConvertToTimestamp(types.As[int64](v)))
}

// ensure the returned value is of the correct type
if fc.Type != types.AnyValue {
var err error
Expand Down
Loading

0 comments on commit e444201

Please sign in to comment.