diff --git a/api_test.go b/api_test.go index a4db231f6..893d6f1b8 100644 --- a/api_test.go +++ b/api_test.go @@ -454,7 +454,7 @@ func TestAPI_ImportValue(t *testing.T) { if err != nil { t.Fatalf("creating index: %v", err) } - _, err = m1.API.CreateField(ctx, index, field, pilosa.OptFieldTypeTimestamp(pilosa.MinTimestamp, pilosa.MaxTimestamp, pilosa.TimeUnitSeconds)) + _, err = m1.API.CreateField(ctx, index, field, pilosa.OptFieldTypeTimestamp(pilosa.DefaultEpoch, pilosa.TimeUnitSeconds)) if err != nil { t.Fatalf("creating field: %v", err) } diff --git a/executor_test.go b/executor_test.go index 425a028a7..fa3510ff0 100644 --- a/executor_test.go +++ b/executor_test.go @@ -1005,7 +1005,7 @@ func TestExecutor_Execute_SetValue(t *testing.T) { // Create fields. index := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{}) - if _, err := index.CreateFieldIfNotExists("f", pilosa.OptFieldTypeTimestamp(pilosa.MinTimestamp, pilosa.MaxTimestamp, pilosa.TimeUnitSeconds)); err != nil { + if _, err := index.CreateFieldIfNotExists("f", pilosa.OptFieldTypeTimestamp(pilosa.DefaultEpoch, pilosa.TimeUnitSeconds)); err != nil { t.Fatal(err) } else if _, err := index.CreateFieldIfNotExists("xxx", pilosa.OptFieldTypeDefault()); err != nil { t.Fatal(err) @@ -1798,20 +1798,18 @@ func TestExecutor_Execute_MinMax(t *testing.T) { } tests := []struct { - min time.Time - max time.Time - set time.Time + epoch time.Time + set time.Time }{ { time.Date(2000, time.January, 10, 0, 0, 0, 0, time.UTC), - time.Date(2000, time.January, 20, 0, 0, 0, 0, time.UTC), time.Date(2000, time.January, 11, 0, 0, 0, 0, time.UTC), }, } for i, test := range tests { fld := fmt.Sprintf("f%d", i) t.Run("MinMaxField_"+fld, func(t *testing.T) { - if _, err := idx.CreateField(fld, pilosa.OptFieldTypeTimestamp(test.min, test.max, pilosa.TimeUnitSeconds)); err != nil { + if _, err := idx.CreateField(fld, pilosa.OptFieldTypeTimestamp(test.epoch, pilosa.TimeUnitSeconds)); err != nil { t.Fatal(err) } else if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: fmt.Sprintf(`Set(10, %s="%s")`, fld, test.set.Format(time.RFC3339))}); err != nil { t.Fatal(err) @@ -4883,7 +4881,7 @@ func TestExecutor_Execute_Extract(t *testing.T) { Set(3, bsidecimal=-1.01) `) - c.CreateField(t, "i", pilosa.IndexOptions{TrackExistence: true}, "timestamp", pilosa.OptFieldTypeTimestamp(pilosa.MinTimestamp, pilosa.MaxTimestamp, pilosa.TimeUnitSeconds)) + c.CreateField(t, "i", pilosa.IndexOptions{TrackExistence: true}, "timestamp", pilosa.OptFieldTypeTimestamp(pilosa.DefaultEpoch, pilosa.TimeUnitSeconds)) c.Query(t, "i", ` Set(0, timestamp='2000-01-01T00:00:00Z') Set(1, timestamp='2000-01-01T00:00:01Z') diff --git a/field.go b/field.go index 59570465f..9dc8ac7a4 100644 --- a/field.go +++ b/field.go @@ -208,10 +208,9 @@ func OptFieldTypeInt(min, max int64) FieldOption { // OptFieldTypeTimestamp is a functional option on FieldOptions // used to specify the field as being type `timestamp` and to // provide any respective configuration values. -func OptFieldTypeTimestamp(min, max time.Time, timeUnit string) FieldOption { +func OptFieldTypeTimestamp(epoch time.Time, timeUnit string) FieldOption { return func(fo *FieldOptions) error { - minValue := min.UnixNano() / TimeUnitNanos(timeUnit) - maxValue := max.UnixNano() / TimeUnitNanos(timeUnit) + epochValue := epoch.UnixNano() / TimeUnitNanos(timeUnit) if fo.Type != "" { return errors.Errorf("field type is already set to: %s", fo.Type) } @@ -220,18 +219,11 @@ func OptFieldTypeTimestamp(min, max time.Time, timeUnit string) FieldOption { } else if !IsValidTimeUnit(timeUnit) { return errors.Errorf("invalid time unit: %q", fo.TimeUnit) } - if min.Before(MinTimestamp) { - return errors.New("timestamp field min is too low") - } else if max.After(MaxTimestamp) { - return errors.New("timestamp field max is too high") - } else if min.After(max) { - return errors.New("timestamp field min cannot be greater than max") - } fo.Type = FieldTypeTimestamp fo.TimeUnit = timeUnit - fo.Min = pql.NewDecimal(minValue, 0) - fo.Max = pql.NewDecimal(maxValue, 0) - fo.Base = bsiBase(minValue, maxValue) + fo.Min = pql.NewDecimal(MinTimestamp.UnixNano()/TimeUnitNanos(timeUnit), 0) + fo.Max = pql.NewDecimal(MaxTimestamp.UnixNano()/TimeUnitNanos(timeUnit), 0) + fo.Base = epochValue return nil } } @@ -1925,23 +1917,15 @@ func (o *FieldOptions) MarshalJSON() ([]byte, error) { }) case FieldTypeTimestamp: return json.Marshal(struct { - Type string `json:"type"` - BaseTimestamp time.Time `json:"baseTimestamp"` - BitDepth uint64 `json:"bitDepth"` - MinTimestamp time.Time `json:"minTimestamp"` - MaxTimestamp time.Time `json:"maxTimestamp"` - Keys bool `json:"keys"` - TimeUnit string `json:"timeUnit"` - ForeignIndex string `json:"foreignIndex"` + Type string `json:"type"` + Epoch time.Time `json:"epoch"` + BitDepth uint64 `json:"bitDepth"` + TimeUnit string `json:"timeUnit"` }{ o.Type, time.Unix(0, o.Base*TimeUnitNanos(o.TimeUnit)).UTC(), o.BitDepth, - time.Unix(0, o.Min.Value*TimeUnitNanos(o.TimeUnit)).UTC(), - time.Unix(0, o.Max.Value*TimeUnitNanos(o.TimeUnit)).UTC(), - o.Keys, o.TimeUnit, - o.ForeignIndex, }) case FieldTypeTime: return json.Marshal(struct { @@ -2154,6 +2138,8 @@ func (f *Field) persistView(ctx context.Context, cvm *CreateViewMessage) error { // Timestamp field range. var ( + DefaultEpoch = time.Unix(0, 0).UTC() // 1970-01-01T00:00:00Z + MinTimestamp = time.Unix(-1<<32, 0).UTC() // 1833-11-24T17:31:44Z MaxTimestamp = time.Unix(1<<32, 0).UTC() // 2106-02-07T06:28:16Z ) diff --git a/http/handler.go b/http/handler.go index e515e3a50..ec976420d 100644 --- a/http/handler.go +++ b/http/handler.go @@ -1341,19 +1341,11 @@ func (h *Handler) handlePostField(w http.ResponseWriter, r *http.Request) { } fos = append(fos, pilosa.OptFieldTypeDecimal(scale, minmax...)) case pilosa.FieldTypeTimestamp: - if req.Options.Min == nil { - min := pql.NewDecimal(pilosa.MinTimestamp.UnixNano()/pilosa.TimeUnitNanos(*req.Options.TimeUnit), 0) - req.Options.Min = &min + if req.Options.Epoch == nil { + epoch := pilosa.DefaultEpoch + req.Options.Epoch = &epoch } - if req.Options.Max == nil { - max := pql.NewDecimal(pilosa.MaxTimestamp.UnixNano()/pilosa.TimeUnitNanos(*req.Options.TimeUnit), 0) - req.Options.Max = &max - } - fos = append(fos, pilosa.OptFieldTypeTimestamp( - time.Unix(0, req.Options.Min.ToInt64(0)*pilosa.TimeUnitNanos(*req.Options.TimeUnit)).UTC(), - time.Unix(0, req.Options.Max.ToInt64(0)*pilosa.TimeUnitNanos(*req.Options.TimeUnit)).UTC(), - *req.Options.TimeUnit, - )) + fos = append(fos, pilosa.OptFieldTypeTimestamp(req.Options.Epoch.UTC(), *req.Options.TimeUnit)) case pilosa.FieldTypeTime: fos = append(fos, pilosa.OptFieldTypeTime(*req.Options.TimeQuantum, req.Options.NoStandardView)) case pilosa.FieldTypeMutex: @@ -1398,6 +1390,7 @@ type fieldOptions struct { Min *pql.Decimal `json:"min,omitempty"` Max *pql.Decimal `json:"max,omitempty"` Scale *int64 `json:"scale,omitempty"` + Epoch *time.Time `json:"epoch,omitempty"` TimeUnit *string `json:"timeUnit,omitempty"` TimeQuantum *pilosa.TimeQuantum `json:"timeQuantum,omitempty"` Keys *bool `json:"keys,omitempty"` diff --git a/index_test.go b/index_test.go index da242ca29..bad61c4dc 100644 --- a/index_test.go +++ b/index_test.go @@ -113,7 +113,7 @@ func TestIndex_CreateField(t *testing.T) { defer index.Close() // Create field with schema and verify it exists. - if f, err := index.CreateField("f", pilosa.OptFieldTypeTimestamp(pilosa.MinTimestamp, pilosa.MaxTimestamp, pilosa.TimeUnitSeconds)); err != nil { + if f, err := index.CreateField("f", pilosa.OptFieldTypeTimestamp(pilosa.DefaultEpoch, pilosa.TimeUnitSeconds)); err != nil { t.Fatal(err) } else if !reflect.DeepEqual(f.Type(), pilosa.FieldTypeTimestamp) { t.Fatalf("unexpected type: %#v", f.Type())