From 83932041a5d8d8fbdbc805a7dc5d0a3f15e5a8e2 Mon Sep 17 00:00:00 2001 From: Samir Date: Thu, 8 Apr 2021 13:21:49 -0500 Subject: [PATCH] Replicate timefield functionality in pilosaclient --- client/orm.go | 56 +++++++++++++++++++++++++++++++++++++++++++++- client/orm_test.go | 54 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 107 insertions(+), 3 deletions(-) diff --git a/client/orm.go b/client/orm.go index f48c8e2dc..65367a1d8 100644 --- a/client/orm.go +++ b/client/orm.go @@ -793,6 +793,7 @@ type FieldOptions struct { keys bool noStandardView bool foreignIndex string + timeUnit string } // Type returns the type of the field. Currently "set", "int", or "time". @@ -841,6 +842,10 @@ func (fo FieldOptions) ForeignIndex() string { return fo.foreignIndex } +func (fo FieldOptions) TimeUnit() string { + return fo.timeUnit +} + // NoStandardView suppresses creating the standard view for supported field types (currently, time) func (fo FieldOptions) NoStandardView() bool { return fo.noStandardView @@ -877,6 +882,10 @@ func (fo FieldOptions) String() string { case FieldTypeTime: mopt["timeQuantum"] = string(fo.timeQuantum) mopt["noStandardView"] = fo.noStandardView + case FieldTypeTimestamp: + mopt["min"] = fo.min + mopt["max"] = fo.max + mopt["timeUnit"] = fo.timeUnit } if fo.fieldType != FieldTypeDefault { @@ -900,6 +909,16 @@ func (fo *FieldOptions) addOptions(options ...FieldOption) { } } +// MinTimestamp returns the minimum value for a timestamp field. +func (o FieldOptions) MinTimestamp() time.Time { + return time.Unix(0, o.min.ToInt64(0)*int64(TimeUnitNano(o.TimeUnit()))) +} + +// MaxTimestamp returns the maxnimum value for a timestamp field. +func (o FieldOptions) MaxTimestamp() time.Time { + return time.Unix(0, o.max.ToInt64(0)*int64(TimeUnitNano(o.TimeUnit()))) +} + // FieldOption is used to pass an option to index.Field function. type FieldOption func(options *FieldOptions) @@ -950,6 +969,18 @@ func OptFieldTypeTime(quantum TimeQuantum, opts ...bool) FieldOption { } } +func OptFieldTypeTimestamp(min, max time.Time, timeUnit string) FieldOption { + return func(fo *FieldOptions) { + minNano := min.UnixNano() + maxNano := max.UnixNano() + fo.fieldType = FieldTypeTimestamp + fo.timeUnit = timeUnit + fo.min = pql.NewDecimal(minNano, 0) + fo.max = pql.NewDecimal(maxNano, 0) + // fo.Base = bsiBase(minNano, maxNano) + } +} + // OptFieldTypeMutex adds a mutex field. func OptFieldTypeMutex(cacheType CacheType, cacheSize int) FieldOption { return func(options *FieldOptions) { @@ -1287,7 +1318,8 @@ const ( // FieldTypeDecimal can store floating point numbers as integers // with a scale factor. This field type is only available in // Molecula's Pilosa with enterprise extensions. - FieldTypeDecimal FieldType = "decimal" + FieldTypeDecimal FieldType = "decimal" + FieldTypeTimestamp FieldType = "timestamp" ) // TimeQuantum type represents valid time quantum values time fields. @@ -1308,6 +1340,28 @@ const ( TimeQuantumYearMonthDayHour TimeQuantum = "YMDH" ) +// List of time units. +const ( + TimeUnitSeconds = "s" + TimeUnitMilliseconds = "ms" + TimeUnitMicroseconds = "µs" + TimeUnitNanoseconds = "ns" +) + +// TimeUnitNano returns the number of nanoseconds in unit. +func TimeUnitNano(unit string) int64 { + switch unit { + case TimeUnitSeconds: + return int64(time.Second) + case TimeUnitMilliseconds: + return int64(time.Millisecond) + case TimeUnitMicroseconds: + return int64(time.Microsecond) + default: + return int64(time.Nanosecond) + } +} + // CacheType represents cache type for a field type CacheType string diff --git a/client/orm_test.go b/client/orm_test.go index 57a7d128f..a731e69b7 100644 --- a/client/orm_test.go +++ b/client/orm_test.go @@ -1022,6 +1022,7 @@ func TestORM(t *testing.T) { 9999, pql.NewDecimal(0, 0), pql.NewDecimal(0, 0), + "", "") }) @@ -1040,6 +1041,7 @@ func TestORM(t *testing.T) { 0, pql.NewDecimal(-10, 0), pql.NewDecimal(100, 0), + "", "") field = sampleIndex.Field("int-field2", OptFieldTypeInt(-10)) @@ -1057,6 +1059,7 @@ func TestORM(t *testing.T) { 0, pql.NewDecimal(-10, 0), pql.NewDecimal(math.MaxInt64, 0), + "", "") field = sampleIndex.Field("int-field3", OptFieldTypeInt()) jsonString = field.options.String() @@ -1072,6 +1075,7 @@ func TestORM(t *testing.T) { 0, pql.NewDecimal(math.MinInt64, 0), pql.NewDecimal(math.MaxInt64, 0), + "", "") field = sampleIndex.Field("int-field4", OptFieldTypeInt(), OptFieldForeignIndex("blerg")) @@ -1088,7 +1092,8 @@ func TestORM(t *testing.T) { 0, pql.NewDecimal(math.MinInt64, 0), pql.NewDecimal(math.MaxInt64, 0), - "blerg") + "blerg", + "") }) t.Run("TimeFieldOptions", func(t *testing.T) { @@ -1109,6 +1114,7 @@ func TestORM(t *testing.T) { 0, pql.NewDecimal(0, 0), pql.NewDecimal(0, 0), + "", "") }) @@ -1127,6 +1133,7 @@ func TestORM(t *testing.T) { 9999, pql.NewDecimal(0, 0), pql.NewDecimal(0, 0), + "", "") }) @@ -1145,6 +1152,7 @@ func TestORM(t *testing.T) { 0, pql.NewDecimal(0, 0), pql.NewDecimal(0, 0), + "", "") }) @@ -1163,9 +1171,48 @@ func TestORM(t *testing.T) { 0, pql.NewDecimal(7, 3), pql.NewDecimal(999, 3), + "", "") }) + t.Run("DecimalFieldOptions", func(t *testing.T) { + field := sampleIndex.Field("decimal-field", OptFieldTypeDecimal(3, pql.NewDecimal(7, 3), pql.NewDecimal(999, 3))) + jsonString := field.options.String() + targetString := `{"options":{"type":"decimal","scale":3,"max":0.999,"min":0.007}}` + if sortedString(targetString) != sortedString(jsonString) { + t.Fatalf("`%s` != `%s`", targetString, jsonString) + } + compareFieldOptions(t, + field.Options(), + FieldTypeDecimal, + TimeQuantumNone, + CacheTypeDefault, + 0, + pql.NewDecimal(7, 3), + pql.NewDecimal(999, 3), + "", + "") + }) + + t.Run("TimestampFieldOptions", func(t *testing.T) { + field := sampleIndex.Field("timestamp-field", OptFieldTypeTimestamp(time.Unix(0, 2), time.Unix(0, 5000), "s")) + jsonString := field.options.String() + targetString := `{"options":{"type":"timestamp","timeUnit":"s","max":5000,"min":2}}` + if sortedString(targetString) != sortedString(jsonString) { + t.Fatalf("`%s` != `%s`", targetString, jsonString) + } + compareFieldOptions(t, + field.Options(), + FieldTypeTimestamp, + TimeQuantumNone, + CacheTypeDefault, + 0, + pql.NewDecimal(2, 0), + pql.NewDecimal(5000, 0), + "", + "s") + }) + t.Run("EncodeMapPanicsOnMarshalFailure", func(t *testing.T) { defer func() { _ = recover() @@ -1211,7 +1258,7 @@ func comparePQL(t *testing.T, target string, q PQLQuery) { } } -func compareFieldOptions(t *testing.T, opts *FieldOptions, fieldType FieldType, timeQuantum TimeQuantum, cacheType CacheType, cacheSize int, min pql.Decimal, max pql.Decimal, foreignIndex string) { +func compareFieldOptions(t *testing.T, opts *FieldOptions, fieldType FieldType, timeQuantum TimeQuantum, cacheType CacheType, cacheSize int, min pql.Decimal, max pql.Decimal, foreignIndex string, timeUnit string) { if fieldType != opts.Type() { t.Fatalf("%s != %s", fieldType, opts.Type()) } @@ -1233,6 +1280,9 @@ func compareFieldOptions(t *testing.T, opts *FieldOptions, fieldType FieldType, if foreignIndex != opts.ForeignIndex() { t.Fatalf("%s != %s", foreignIndex, opts.ForeignIndex()) } + if timeUnit != opts.TimeUnit() { + t.Fatalf("%s != %s", timeUnit, opts.TimeUnit()) + } } func sortedString(s string) string {