update codec to reflect changes to timstamp range

also address a few minor feedback comments from review
This commit is contained in:
Samir Patel 2022-07-15 13:01:15 -05:00 committed by Samir Patel
parent 33fce8a05b
commit bd5dc760b7
6 changed files with 98 additions and 56 deletions

6
api.go
View file

@ -2006,11 +2006,7 @@ func (api *API) IngestOperations(ctx context.Context, qcx *Qcx, indexName string
return fmt.Errorf("adding decimal field to codec: %w", err) return fmt.Errorf("adding decimal field to codec: %w", err)
} }
case "timestamp": case "timestamp":
// TODO: codec will likely need to be updated to reflect changes (increase) to timestamp range if err = codec.AddTimestampField(field.name, field.options.TimeUnit, field.options.Base); err != nil {
// i.e. values are not all being converted to Nanos and being stored at the granularity
// specified by user and taking advantage of the range that provides.
nanos := TimeUnitNanos(field.options.TimeUnit)
if err = codec.AddTimestampField(field.name, time.Duration(nanos), field.options.Base); err != nil {
return fmt.Errorf("adding timestamp field to codec: %w", err) return fmt.Errorf("adding timestamp field to codec: %w", err)
} }
default: default:

View file

@ -1590,8 +1590,6 @@ func (e *executor) executeDistinctShard(ctx context.Context, qcx *Qcx, index str
return nil, errors.Wrap(err, "translating value to timestamp") return nil, errors.Wrap(err, "translating value to timestamp")
} }
results[i] = t.Format(time.RFC3339Nano) results[i] = t.Format(time.RFC3339Nano)
// results[i] = FormatTimestampNano(int64(val), bsig.Base, field.options.TimeUnit)
} }
result = DistinctTimestamp{Name: fieldName, Values: results} result = DistinctTimestamp{Name: fieldName, Values: results}
return result, nil return result, nil
@ -7919,7 +7917,7 @@ func (vc *ValCount) smaller(other ValCount) ValCount {
return vc.decimalSmaller(other) return vc.decimalSmaller(other)
} else if vc.FloatVal != 0 || other.FloatVal != 0 { } else if vc.FloatVal != 0 || other.FloatVal != 0 {
return vc.floatSmaller(other) return vc.floatSmaller(other)
} else if !vc.TimestampVal.Equal(time.Time{}) || !other.TimestampVal.Equal(time.Time{}) { } else if !vc.TimestampVal.IsZero() || !other.TimestampVal.IsZero() {
return vc.timestampSmaller(other) return vc.timestampSmaller(other)
} }
if vc.Count == 0 || (other.Val < vc.Val && other.Count > 0) { if vc.Count == 0 || (other.Val < vc.Val && other.Count > 0) {
@ -7938,6 +7936,8 @@ func (vc *ValCount) smaller(other ValCount) ValCount {
} }
} }
// timestampSmaller returns the smaller of the two (vc or other), while merging the count
// if they are equal.
func (vc *ValCount) timestampSmaller(other ValCount) ValCount { func (vc *ValCount) timestampSmaller(other ValCount) ValCount {
if other.TimestampVal.Equal(time.Time{}) { if other.TimestampVal.Equal(time.Time{}) {
return *vc return *vc
@ -7956,6 +7956,8 @@ func (vc *ValCount) timestampSmaller(other ValCount) ValCount {
} }
} }
// decimalSmaller returns the smaller of the two (vc or other), while merging the count
// if they are equal.
func (vc *ValCount) decimalSmaller(other ValCount) ValCount { func (vc *ValCount) decimalSmaller(other ValCount) ValCount {
if other.DecimalVal == nil { if other.DecimalVal == nil {
return *vc return *vc
@ -7973,6 +7975,8 @@ func (vc *ValCount) decimalSmaller(other ValCount) ValCount {
} }
} }
// floatSmaller returns the smaller of the two (vc or other), while merging the count
// if they are equal.
func (vc *ValCount) floatSmaller(other ValCount) ValCount { func (vc *ValCount) floatSmaller(other ValCount) ValCount {
if vc.Count == 0 || (other.FloatVal < vc.FloatVal && other.Count > 0) { if vc.Count == 0 || (other.FloatVal < vc.FloatVal && other.Count > 0) {
return other return other
@ -8012,6 +8016,8 @@ func (vc *ValCount) larger(other ValCount) ValCount {
} }
} }
// timestampLarger returns the larger of the two (vc or other), while merging the count
// if they are equal.
func (vc *ValCount) timestampLarger(other ValCount) ValCount { func (vc *ValCount) timestampLarger(other ValCount) ValCount {
if other.TimestampVal.Equal(time.Time{}) { if other.TimestampVal.Equal(time.Time{}) {
return *vc return *vc
@ -8030,6 +8036,8 @@ func (vc *ValCount) timestampLarger(other ValCount) ValCount {
} }
} }
// decimalLarger returns the larger of the two (vc or other), while merging the count
// if they are equal.
func (vc *ValCount) decimalLarger(other ValCount) ValCount { func (vc *ValCount) decimalLarger(other ValCount) ValCount {
if other.DecimalVal == nil { if other.DecimalVal == nil {
return *vc return *vc
@ -8047,6 +8055,8 @@ func (vc *ValCount) decimalLarger(other ValCount) ValCount {
} }
} }
// floatLarger returns the larger of the two (vc or other), while merging the count
// if they are equal.
func (vc *ValCount) floatLarger(other ValCount) ValCount { func (vc *ValCount) floatLarger(other ValCount) ValCount {
if vc.Count == 0 || (other.FloatVal > vc.FloatVal && other.Count > 0) { if vc.Count == 0 || (other.FloatVal > vc.FloatVal && other.Count > 0) {
return other return other

View file

@ -7493,7 +7493,7 @@ func variousQueriesOnPercentiles(t *testing.T, c *test.Cluster) {
} }
} }
func toCSV(s string) string { func lineBreaker(s string) string {
return strings.Join(strings.Split(s, " "), "\n") + "\n" return strings.Join(strings.Split(s, " "), "\n") + "\n"
} }
@ -7553,44 +7553,44 @@ func variousQueriesOnTimeFields(t *testing.T, c *test.Cluster) {
// Rows // Rows
{ {
query: `Rows(f1, from='2019-08-04T14:36', to='2019-08-04T16:00')`, query: `Rows(f1, from='2019-08-04T14:36', to='2019-08-04T16:00')`,
csvVerifier: toCSV("R4 R5"), csvVerifier: lineBreaker("R4 R5"),
}, },
{ {
query: `Rows(f1, from='2019-08-04T14', to='2019-08-04T17:00')`, query: `Rows(f1, from='2019-08-04T14', to='2019-08-04T17:00')`,
csvVerifier: toCSV("R4 R5 R6"), csvVerifier: lineBreaker("R4 R5 R6"),
}, },
{ {
query: `Rows(f1, from='2019-08-04', to='2019-08-05')`, query: `Rows(f1, from='2019-08-04', to='2019-08-05')`,
csvVerifier: toCSV("R3 R4 R5 R6"), csvVerifier: lineBreaker("R3 R4 R5 R6"),
}, },
{ {
query: `Rows(f1, from='2019-08', to='2019-12')`, query: `Rows(f1, from='2019-08', to='2019-12')`,
csvVerifier: toCSV("R2 R3 R4 R5 R6 R7"), csvVerifier: lineBreaker("R2 R3 R4 R5 R6 R7"),
}, },
{ {
query: `Rows(f1, from='2019', to='2020')`, query: `Rows(f1, from='2019', to='2020')`,
csvVerifier: toCSV("R1 R2 R3 R4 R5 R6 R7 R8"), csvVerifier: lineBreaker("R1 R2 R3 R4 R5 R6 R7 R8"),
}, },
// Row // Row
{ {
query: `Row(f2='R', from='2019-08-04T14:36', to='2019-08-04T16:00')`, query: `Row(f2='R', from='2019-08-04T14:36', to='2019-08-04T16:00')`,
csvVerifier: toCSV("C4 C5"), csvVerifier: lineBreaker("C4 C5"),
}, },
{ {
query: `Row(f2='R', from='2019-08-04T14', to='2019-08-04T17:00')`, query: `Row(f2='R', from='2019-08-04T14', to='2019-08-04T17:00')`,
csvVerifier: toCSV("C4 C5 C6"), csvVerifier: lineBreaker("C4 C5 C6"),
}, },
{ {
query: `Row(f2='R', from='2019-08-04', to='2019-08-05')`, query: `Row(f2='R', from='2019-08-04', to='2019-08-05')`,
csvVerifier: toCSV("C3 C4 C5 C6"), csvVerifier: lineBreaker("C3 C4 C5 C6"),
}, },
{ {
query: `Row(f2='R', from='2019-08', to='2019-12')`, query: `Row(f2='R', from='2019-08', to='2019-12')`,
csvVerifier: toCSV("C2 C3 C4 C5 C6 C7"), csvVerifier: lineBreaker("C2 C3 C4 C5 C6 C7"),
}, },
{ {
query: `Row(f2='R', from='2019', to='2020')`, query: `Row(f2='R', from='2019', to='2020')`,
csvVerifier: toCSV("C1 C2 C3 C4 C5 C6 C7 C8"), csvVerifier: lineBreaker("C1 C2 C3 C4 C5 C6 C7 C8"),
}, },
} }
@ -7862,39 +7862,39 @@ userB
}, },
{ {
query: `Row(unix_sec="0001-01-01T00:00:01Z")`, query: `Row(unix_sec="0001-01-01T00:00:01Z")`,
csvVerifier: toCSV("userA"), csvVerifier: lineBreaker("userA"),
}, },
{ {
query: `Row(unix_sec="9999-12-31T23:59:59Z")`, query: `Row(unix_sec="9999-12-31T23:59:59Z")`,
csvVerifier: toCSV("userB"), csvVerifier: lineBreaker("userB"),
}, },
{ {
query: `Row(unix_milli="0001-01-01T00:00:01Z")`, query: `Row(unix_milli="0001-01-01T00:00:01Z")`,
csvVerifier: toCSV("userA"), csvVerifier: lineBreaker("userA"),
}, },
{ {
query: `Row(unix_milli="9999-12-31T23:59:59Z")`, query: `Row(unix_milli="9999-12-31T23:59:59Z")`,
csvVerifier: toCSV("userB"), csvVerifier: lineBreaker("userB"),
}, },
{ {
query: `Row(unix_micro="0001-01-01T00:00:01Z")`, query: `Row(unix_micro="0001-01-01T00:00:01Z")`,
csvVerifier: toCSV("userA"), csvVerifier: lineBreaker("userA"),
}, },
{ {
query: `Row(unix_micro="9999-12-31T23:59:59Z")`, query: `Row(unix_micro="9999-12-31T23:59:59Z")`,
csvVerifier: toCSV("userB"), csvVerifier: lineBreaker("userB"),
}, },
{ {
query: `Row(unix_nano="1833-11-24T17:31:44Z")`, query: `Row(unix_nano="1833-11-24T17:31:44Z")`,
csvVerifier: toCSV("userA"), csvVerifier: lineBreaker("userA"),
}, },
{ {
query: `Row(unix_nano="2106-02-07T06:28:16Z")`, query: `Row(unix_nano="2106-02-07T06:28:16Z")`,
csvVerifier: toCSV("userB"), csvVerifier: lineBreaker("userB"),
}, },
{ {
query: `Union(Row(unix_nano="2106-02-07T06:28:16Z"), Row(unix_micro="0001-01-01T00:00:01Z"))`, query: `Union(Row(unix_nano="2106-02-07T06:28:16Z"), Row(unix_micro="0001-01-01T00:00:01Z"))`,
csvVerifier: toCSV("userA\nuserB"), csvVerifier: lineBreaker("userA\nuserB"),
}, },
{ {
query: `Set("userA", unix_milli="2000-12-31T23:59:59.999Z")`, query: `Set("userA", unix_milli="2000-12-31T23:59:59.999Z")`,
@ -8155,59 +8155,59 @@ userC
}, },
{ {
query: `Row(unix_sec_min="0001-01-01T00:00:01Z")`, query: `Row(unix_sec_min="0001-01-01T00:00:01Z")`,
csvVerifier: toCSV("userA"), csvVerifier: lineBreaker("userA"),
}, },
{ {
query: `Row(unix_sec_max="0001-01-01T00:00:01Z")`, query: `Row(unix_sec_max="0001-01-01T00:00:01Z")`,
csvVerifier: toCSV("userC"), csvVerifier: lineBreaker("userC"),
}, },
{ {
query: `Row(unix_sec_max="9999-12-31T23:59:59Z")`, query: `Row(unix_sec_max="9999-12-31T23:59:59Z")`,
csvVerifier: toCSV("userA"), csvVerifier: lineBreaker("userA"),
}, },
{ {
query: `Row(unix_milli_min="0001-01-01T00:00:01Z")`, query: `Row(unix_milli_min="0001-01-01T00:00:01Z")`,
csvVerifier: toCSV("userA"), csvVerifier: lineBreaker("userA"),
}, },
{ {
query: `Row(unix_milli_min="9999-12-31T23:59:59Z")`, query: `Row(unix_milli_min="9999-12-31T23:59:59Z")`,
csvVerifier: toCSV("userC"), csvVerifier: lineBreaker("userC"),
}, },
{ {
query: `Row(unix_micro_max="0001-01-01T00:00:01Z")`, query: `Row(unix_micro_max="0001-01-01T00:00:01Z")`,
csvVerifier: toCSV("userC"), csvVerifier: lineBreaker("userC"),
}, },
{ {
query: `Row(unix_micro_max="9999-12-31T23:59:59Z")`, query: `Row(unix_micro_max="9999-12-31T23:59:59Z")`,
csvVerifier: toCSV("userA"), csvVerifier: lineBreaker("userA"),
}, },
{ {
query: `Row(unix_nano_min="1833-11-24T17:31:44Z")`, query: `Row(unix_nano_min="1833-11-24T17:31:44Z")`,
csvVerifier: toCSV("userA"), csvVerifier: lineBreaker("userA"),
}, },
{ {
query: `Row(unix_nano_min="1969-12-31T23:59:59.999999999Z")`, query: `Row(unix_nano_min="1969-12-31T23:59:59.999999999Z")`,
csvVerifier: toCSV("userB"), csvVerifier: lineBreaker("userB"),
}, },
{ {
query: `Row(unix_nano_min="1970-01-01T00:00:00Z")`, query: `Row(unix_nano_min="1970-01-01T00:00:00Z")`,
csvVerifier: toCSV("userC"), csvVerifier: lineBreaker("userC"),
}, },
{ {
query: `Row(unix_nano_max="2106-02-07T06:28:16Z")`, query: `Row(unix_nano_max="2106-02-07T06:28:16Z")`,
csvVerifier: toCSV("userA"), csvVerifier: lineBreaker("userA"),
}, },
{ {
query: `Row(unix_nano_max="1970-01-01T00:00:00.000000001Z")`, query: `Row(unix_nano_max="1970-01-01T00:00:00.000000001Z")`,
csvVerifier: toCSV("userB"), csvVerifier: lineBreaker("userB"),
}, },
{ {
query: `Row(unix_nano_max="1970-01-01T00:00:00Z")`, query: `Row(unix_nano_max="1970-01-01T00:00:00Z")`,
csvVerifier: toCSV("userC"), csvVerifier: lineBreaker("userC"),
}, },
{ {
query: `Union(Row(unix_nano_max="2106-02-07T06:28:16Z"), Row(unix_micro_max="0001-01-01T00:00:01Z"))`, query: `Union(Row(unix_nano_max="2106-02-07T06:28:16Z"), Row(unix_micro_max="0001-01-01T00:00:01Z"))`,
csvVerifier: toCSV("userA\nuserC"), csvVerifier: lineBreaker("userA\nuserC"),
}, },
{ {
query: `Set("userA", unix_sec_min="2000-12-31T23:59:59.999Z")`, query: `Set("userA", unix_sec_min="2000-12-31T23:59:59.999Z")`,

View file

@ -1833,7 +1833,7 @@ func (f *Field) importValue(qcx *Qcx, columnIDs []uint64, values []int64, shard
// of timestamps are already relative to the epoch (base). // of timestamps are already relative to the epoch (base).
// So a user may set an epoch to 2022-03-01 as the start of a race // So a user may set an epoch to 2022-03-01 as the start of a race
// and import finishing times in seconds. // and import finishing times in seconds.
// Timestamps ingested as timestamps are of coure absolute, but by the time // Timestamps ingested as timestamps are of course absolute, but by the time
// we get here it would be a relative integer. // we get here it would be a relative integer.
if f.Type() != FieldTypeTimestamp { if f.Type() != FieldTypeTimestamp {
min -= bsig.Base min -= bsig.Base

View file

@ -44,7 +44,7 @@ type Codec interface {
AddBoolField(name string) error AddBoolField(name string) error
AddIntField(name string, keys KeyTranslator) error AddIntField(name string, keys KeyTranslator) error
AddDecimalField(name string, scale int64) error AddDecimalField(name string, scale int64) error
AddTimestampField(name string, scale time.Duration, epoch int64) error AddTimestampField(name string, scale string, epoch int64) error
// Parse data from a reader into the vectors. // Parse data from a reader into the vectors.
// This must only be called once on a codec. // This must only be called once on a codec.
@ -70,14 +70,14 @@ type fieldCodec struct {
currentOp *FieldOperation currentOp *FieldOperation
decode jsonDecFn decode jsonDecFn
encode jsonEncFn encode jsonEncFn
// For timestamp: scale-in-nanoseconds; for instance, if scaleUnit is
// 1,000,000,000, we are storing numbers-of-seconds since the Unix epoch.
// The actual value recorded in BSI will be offset by the field's
// epoch, but we don't need to know that.
// For decimal: Decimal digits of precision. So for instance, with // For decimal: Decimal digits of precision. So for instance, with
// scale 2, scaleUnit is 100, "1" is stored as 100 and "1.2" is stored as // scale 2, scaleUnit is 100, "1" is stored as 100 and "1.2" is stored as
// 120. // 120.
scaleUnit int64 scaleUnit int64
// For timestamp: we use timeUnit to determine the scale at which
// to store a timestamp. For example if the timeUnit is milliseconds
// we store the number of milliseconds from the given epoch.
timeUnit string
scale int64 scale int64
epoch int64 // used only by Timestamp fields epoch int64 // used only by Timestamp fields
scratch []uint64 // reusable scratch space for sets of values scratch []uint64 // reusable scratch space for sets of values
@ -282,10 +282,10 @@ func (codec *JSONCodec) AddBoolField(name string) error {
// passed to this function should be the offset from the Unix epoch to the // passed to this function should be the offset from the Unix epoch to the
// desired epoch, in the same scale. (So if the scale is milliseconds, // desired epoch, in the same scale. (So if the scale is milliseconds,
// it should be the Unix timestamp in seconds, times 1000.) // it should be the Unix timestamp in seconds, times 1000.)
func (codec *JSONCodec) AddTimestampField(name string, timeScale time.Duration, epoch int64) error { func (codec *JSONCodec) AddTimestampField(name string, timeScale string, epoch int64) error {
fieldCodec := &fieldCodec{ fieldCodec := &fieldCodec{
fieldType: FieldTypeTimeStamp, fieldType: FieldTypeTimeStamp,
scaleUnit: int64(timeScale), timeUnit: timeScale,
epoch: epoch, epoch: epoch,
} }
fieldCodec.decode = fieldCodec.DecodeTimeValue fieldCodec.decode = fieldCodec.DecodeTimeValue
@ -590,7 +590,7 @@ func (j *fieldCodec) DecodeTimeValue(recID uint64, dataType jsonparser.ValueType
if err != nil { if err != nil {
return fmt.Errorf("parsing timestamp: %w", err) return fmt.Errorf("parsing timestamp: %w", err)
} }
j.currentOp.AddSignedPair(recID, (stamp.UnixNano()/j.scaleUnit)-j.epoch) j.currentOp.AddSignedPair(recID, TimestampToVal(j.timeUnit, stamp)-j.epoch)
case jsonparser.Number: case jsonparser.Number:
// We could in theory convert this to a time, then convert it // We could in theory convert this to a time, then convert it
// back, by multiplying by scaleUnit, then dividing. Or... not. // back, by multiplying by scaleUnit, then dividing. Or... not.
@ -609,7 +609,11 @@ func (j *fieldCodec) EncodeTimeValue(dst *jsonBuffer, values []uint64, signed []
if len(signed) == 0 { if len(signed) == 0 {
return errors.New("encoding time value: no value provided") return errors.New("encoding time value: no value provided")
} }
dst.EncodeTime(time.Unix(0, (signed[0]+j.epoch)*j.scaleUnit).UTC()) t, err := ValToTimestamp(j.timeUnit, signed[0]+j.epoch)
if err != nil {
return errors.Wrap(err, "translating value to timestamp")
}
dst.EncodeTime(t)
return nil return nil
} }
@ -1132,3 +1136,35 @@ type errFieldNotFound struct {
func (err errFieldNotFound) Error() string { func (err errFieldNotFound) Error() string {
return fmt.Sprintf("field not found: %q", err.field) return fmt.Sprintf("field not found: %q", err.field)
} }
// TimestampToVal takes a time unit and a time.Time and converts it to an integer value
func TimestampToVal(unit string, ts time.Time) int64 {
switch unit {
case "s":
return ts.Unix()
case "ms":
return ts.UnixMilli()
case "us":
return ts.UnixMicro()
case "ns":
return ts.UnixNano()
}
return 0
}
// ValToTimestamp takes a timeunit and an integer value and converts it to time.Time
func ValToTimestamp(unit string, val int64) (time.Time, error) {
switch unit {
case "s":
return time.Unix(val, 0).UTC(), nil
case "ms":
return time.UnixMilli(val).UTC(), nil
case "us", "μs":
return time.UnixMicro(val).UTC(), nil
case "ns":
return time.Unix(0, val).UTC(), nil
default:
return time.Time{}, errors.Errorf("Unknown time unit: '%v'", unit)
}
}

View file

@ -58,7 +58,7 @@ func TestEncode(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("can't parse sample epoch time: %v", err) t.Fatalf("can't parse sample epoch time: %v", err)
} }
_ = codec.AddTimestampField("ts", time.Millisecond, epoch.Unix()*1000) _ = codec.AddTimestampField("ts", "ms", epoch.Unix()*1000)
_ = codec.AddDecimalField("dec", 2) _ = codec.AddDecimalField("dec", 2)
_ = codec.AddBoolField("bool") _ = codec.AddBoolField("bool")
@ -74,7 +74,7 @@ func TestEncode(t *testing.T) {
_ = codec.AddTimeQuantumField("tqkeys", newStableTranslator()) _ = codec.AddTimeQuantumField("tqkeys", newStableTranslator())
_ = codec.AddIntField("int", nil) _ = codec.AddIntField("int", nil)
_ = codec.AddIntField("intkeys", newStableTranslator()) _ = codec.AddIntField("intkeys", newStableTranslator())
_ = codec.AddTimestampField("ts", time.Millisecond, epoch.Unix()*1000) _ = codec.AddTimestampField("ts", "ms", epoch.Unix()*1000)
_ = codec.AddDecimalField("dec", 2) _ = codec.AddDecimalField("dec", 2)
_ = codec.AddBoolField("bool") _ = codec.AddBoolField("bool")
@ -291,7 +291,7 @@ func TestCodecErrors(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("can't parse sample epoch time: %v", err) t.Fatalf("can't parse sample epoch time: %v", err)
} }
_ = codec.AddTimestampField("ts", time.Millisecond, epoch.Unix()*1000) _ = codec.AddTimestampField("ts", "ms", epoch.Unix()*1000)
_ = codec.AddDecimalField("dec", 2) _ = codec.AddDecimalField("dec", 2)
_ = codec.AddBoolField("bool") _ = codec.AddBoolField("bool")
@ -475,7 +475,7 @@ func TestSimpleCodec(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("can't parse sample epoch time: %v", err) t.Fatalf("can't parse sample epoch time: %v", err)
} }
_ = codec.AddTimestampField("ts", time.Millisecond, epoch.Unix()*1000) _ = codec.AddTimestampField("ts", "ms", epoch.Unix()*1000)
_ = codec.AddDecimalField("dec", 2) _ = codec.AddDecimalField("dec", 2)
_ = codec.AddBoolField("bool") _ = codec.AddBoolField("bool")
var nextShard = uint64(1<<shardwidth.Exponent) + 5 var nextShard = uint64(1<<shardwidth.Exponent) + 5