Add min/max constraints; fix tests

This commit is contained in:
Ben Johnson 2019-05-19 16:05:22 -06:00
parent d4de122549
commit 40803372dd
No known key found for this signature in database
GPG key ID: 81741CD251883081
11 changed files with 92 additions and 79 deletions

View file

@ -199,7 +199,7 @@ func TestAPI_ImportValue(t *testing.T) {
if err != nil {
t.Fatalf("creating index: %v", err)
}
_, err = m0.API.CreateField(ctx, index, field, pilosa.OptFieldTypeInt(0, math.MinInt64, math.MaxInt64))
_, err = m0.API.CreateField(ctx, index, field, pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64))
if err != nil {
t.Fatalf("creating field: %v", err)
}

View file

@ -530,6 +530,8 @@ func encodeFieldOptions(o *pilosa.FieldOptions) *internal.FieldOptions {
Type: o.Type,
CacheType: o.CacheType,
CacheSize: o.CacheSize,
Min: o.Min,
Max: o.Max,
Base: o.Base,
BitDepth: uint64(o.BitDepth),
TimeQuantum: string(o.TimeQuantum),

View file

@ -770,7 +770,7 @@ func TestExecutor_Execute_SetValue(t *testing.T) {
// Create fields.
index := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{})
if _, err := index.CreateFieldIfNotExists("f", pilosa.OptFieldTypeInt(0, math.MinInt64, math.MaxInt64)); err != nil {
if _, err := index.CreateFieldIfNotExists("f", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64)); err != nil {
t.Fatal(err)
} else if _, err := index.CreateFieldIfNotExists("xxx", pilosa.OptFieldTypeDefault()); err != nil {
t.Fatal(err)
@ -807,7 +807,7 @@ func TestExecutor_Execute_SetValue(t *testing.T) {
hldr := test.Holder{Holder: c[0].Server.Holder()}
index := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{})
if _, err := index.CreateFieldIfNotExists("f", pilosa.OptFieldTypeInt(0, math.MinInt64, math.MaxInt64)); err != nil {
if _, err := index.CreateFieldIfNotExists("f", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64)); err != nil {
t.Fatal(err)
}
@ -1215,7 +1215,7 @@ func TestExecutor_Execute_MinMax(t *testing.T) {
t.Fatal(err)
}
if _, err := idx.CreateField("f", pilosa.OptFieldTypeInt(-10, math.MinInt64, math.MaxInt64)); err != nil {
if _, err := idx.CreateField("f", pilosa.OptFieldTypeInt(-1100, 1000)); err != nil {
t.Fatal(err)
}
@ -1279,7 +1279,7 @@ func TestExecutor_Execute_MinMax(t *testing.T) {
t.Fatal(err)
}
if _, err := idx.CreateField("f", pilosa.OptFieldTypeInt(-10, math.MinInt64, math.MaxInt64)); err != nil {
if _, err := idx.CreateField("f", pilosa.OptFieldTypeInt(-1110, 1000)); err != nil {
t.Fatal(err)
}
@ -1372,15 +1372,15 @@ func TestExecutor_Execute_Sum(t *testing.T) {
t.Fatal(err)
}
if _, err := idx.CreateField("foo", pilosa.OptFieldTypeInt(10, math.MinInt64, math.MaxInt64)); err != nil {
if _, err := idx.CreateField("foo", pilosa.OptFieldTypeInt(-990, 1000)); err != nil {
t.Fatal(err)
}
if _, err := idx.CreateField("bar", pilosa.OptFieldTypeInt(0, math.MinInt64, math.MaxInt64)); err != nil {
if _, err := idx.CreateField("bar", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64)); err != nil {
t.Fatal(err)
}
if _, err := idx.CreateField("other", pilosa.OptFieldTypeInt(0, math.MinInt64, math.MaxInt64)); err != nil {
if _, err := idx.CreateField("other", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64)); err != nil {
t.Fatal(err)
}
@ -1430,15 +1430,15 @@ func TestExecutor_Execute_Sum(t *testing.T) {
t.Fatal(err)
}
if _, err := idx.CreateField("foo", pilosa.OptFieldTypeInt(10, math.MinInt64, math.MaxInt64)); err != nil {
if _, err := idx.CreateField("foo", pilosa.OptFieldTypeInt(-990, 1000)); err != nil {
t.Fatal(err)
}
if _, err := idx.CreateField("bar", pilosa.OptFieldTypeInt(0, math.MinInt64, math.MaxInt64)); err != nil {
if _, err := idx.CreateField("bar", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64)); err != nil {
t.Fatal(err)
}
if _, err := idx.CreateField("other", pilosa.OptFieldTypeInt(0, math.MinInt64, math.MaxInt64)); err != nil {
if _, err := idx.CreateField("other", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64)); err != nil {
t.Fatal(err)
}
@ -1832,19 +1832,19 @@ func TestExecutor_Execute_Row_BSIGroup(t *testing.T) {
t.Fatal(err)
}
if _, err := idx.CreateField("foo", pilosa.OptFieldTypeInt(10, math.MinInt64, math.MaxInt64)); err != nil {
if _, err := idx.CreateField("foo", pilosa.OptFieldTypeInt(-990, 1000)); err != nil {
t.Fatal(err)
}
if _, err := idx.CreateField("bar", pilosa.OptFieldTypeInt(0, math.MinInt64, math.MaxInt64)); err != nil {
if _, err := idx.CreateField("bar", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64)); err != nil {
t.Fatal(err)
}
if _, err := idx.CreateField("other", pilosa.OptFieldTypeInt(0, math.MinInt64, math.MaxInt64)); err != nil {
if _, err := idx.CreateField("other", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64)); err != nil {
t.Fatal(err)
}
if _, err := idx.CreateField("edge", pilosa.OptFieldTypeInt(-100, math.MinInt64, math.MaxInt64)); err != nil {
if _, err := idx.CreateField("edge", pilosa.OptFieldTypeInt(-900, 1000)); err != nil {
t.Fatal(err)
}
@ -1997,7 +1997,7 @@ func TestExecutor_Execute_Row_BSIGroup(t *testing.T) {
})
t.Run("GTBelowMin", func(t *testing.T) {
if result, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Row(edge > -200)`}); err != nil {
if result, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Row(edge > -1000)`}); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual([]uint64{0, 1}, result.Results[0].(*pilosa.Row).Columns()) {
t.Fatalf("unexpected result: %s", spew.Sdump(result.Results[0].(*pilosa.Row).Columns()))
@ -2026,19 +2026,19 @@ func TestExecutor_Execute_Range_BSIGroup_Deprecated(t *testing.T) {
t.Fatal(err)
}
if _, err := idx.CreateField("foo", pilosa.OptFieldTypeInt(10, math.MinInt64, math.MaxInt64)); err != nil {
if _, err := idx.CreateField("foo", pilosa.OptFieldTypeInt(-990, 1000)); err != nil {
t.Fatal(err)
}
if _, err := idx.CreateField("bar", pilosa.OptFieldTypeInt(0, math.MinInt64, math.MaxInt64)); err != nil {
if _, err := idx.CreateField("bar", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64)); err != nil {
t.Fatal(err)
}
if _, err := idx.CreateField("other", pilosa.OptFieldTypeInt(0, math.MinInt64, math.MaxInt64)); err != nil {
if _, err := idx.CreateField("other", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64)); err != nil {
t.Fatal(err)
}
if _, err := idx.CreateField("edge", pilosa.OptFieldTypeInt(-100, math.MinInt64, math.MaxInt64)); err != nil {
if _, err := idx.CreateField("edge", pilosa.OptFieldTypeInt(-1100, 1000)); err != nil {
t.Fatal(err)
}
@ -2163,7 +2163,7 @@ func TestExecutor_Execute_Range_BSIGroup_Deprecated(t *testing.T) {
})
t.Run("GTBelowMin", func(t *testing.T) {
if result, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Range(edge > -200)`}); err != nil {
if result, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Range(edge > -1200)`}); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual([]uint64{0, 1}, result.Results[0].(*pilosa.Row).Columns()) {
t.Fatalf("unexpected result: %s", spew.Sdump(result.Results[0].(*pilosa.Row).Columns()))
@ -2796,7 +2796,7 @@ func TestExecutor_Execute_ClearRow(t *testing.T) {
defer c.Close()
hldr := test.Holder{Holder: c[0].Server.Holder()}
index := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{TrackExistence: true})
_, err := index.CreateField("f", pilosa.OptFieldTypeInt(0, math.MinInt64, math.MaxInt64))
_, err := index.CreateField("f", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64))
if err != nil {
t.Fatal(err)
}

View file

@ -130,16 +130,14 @@ func OptFieldTypeSet(cacheType string, cacheSize uint32) FieldOption {
// OptFieldTypeInt is a functional option on FieldOptions
// used to specify the field as being type `int` and to
// provide any respective configuration values.
func OptFieldTypeInt(base, min, max int64) FieldOption {
func OptFieldTypeInt(min, max int64) FieldOption {
return func(fo *FieldOptions) error {
if fo.Type != "" {
return errors.Errorf("field type is already set to: %s", fo.Type)
}
fo.Type = FieldTypeInt
fo.Base = base
fo.Min = min
fo.Max = max
fo.BitDepth = 1
return nil
}
}
@ -1016,6 +1014,10 @@ func (f *Field) SetValue(columnID uint64, value int64) (changed bool, err error)
bsig := f.bsiGroup(f.name)
if bsig == nil {
return false, ErrBSIGroupNotFound
} else if value < bsig.Min {
return false, ErrBSIGroupValueTooLow
} else if value > bsig.Max {
return false, ErrBSIGroupValueTooHigh
}
// Determine base value to store.
@ -1259,6 +1261,11 @@ func (f *Field) importValue(columnIDs []uint64, values []int64, options *ImportO
dataByFragment := make(map[importKey]importValueData)
for i := range columnIDs {
columnID, value := columnIDs[i], values[i]
if value > bsig.Max {
return fmt.Errorf("%v, columnID=%v, value=%v", ErrBSIGroupValueTooHigh, columnID, value)
} else if value < bsig.Min {
return fmt.Errorf("%v, columnID=%v, value=%v", ErrBSIGroupValueTooLow, columnID, value)
}
// Attach value to each bsiGroup view.
for _, name := range []string{viewName} {
@ -1345,16 +1352,14 @@ func (p fieldInfoSlice) Less(i, j int) bool { return p[i].Name < p[j].Name }
type FieldOptions struct {
Base int64 `json:"base,omitempty"`
BitDepth uint `json:"bitDepth,omitempty"`
Min int64 `json:"min,omitempty"`
Max int64 `json:"max,omitempty"`
Keys bool `json:"keys"`
NoStandardView bool `json:"noStandardView,omitempty"`
CacheSize uint32 `json:"cacheSize,omitempty"`
CacheType string `json:"cacheType,omitempty"`
Type string `json:"type,omitempty"`
TimeQuantum TimeQuantum `json:"timeQuantum,omitempty"`
// Deprecated. Use base/bit depth.
Min int64 `json:"min,omitempty"`
Max int64 `json:"max,omitempty"`
}
// applyDefaultOptions returns a new FieldOptions object
@ -1383,10 +1388,10 @@ func encodeFieldOptions(o *FieldOptions) *internal.FieldOptions {
Type: o.Type,
CacheType: o.CacheType,
CacheSize: o.CacheSize,
Min: o.Min,
Max: o.Max,
Base: o.Base,
BitDepth: uint64(o.BitDepth),
Min: o.Min,
Max: o.Max,
TimeQuantum: string(o.TimeQuantum),
Keys: o.Keys,
NoStandardView: o.NoStandardView,
@ -1415,11 +1420,15 @@ func (o *FieldOptions) MarshalJSON() ([]byte, error) {
Type string `json:"type"`
Base int64 `json:"base"`
BitDepth uint `json:"bitDepth"`
Min int64 `json:"min"`
Max int64 `json:"max"`
Keys bool `json:"keys"`
}{
o.Type,
o.Base,
o.BitDepth,
o.Min,
o.Max,
o.Keys,
})
case FieldTypeTime:

View file

@ -31,7 +31,7 @@ func TestField_SetValue(t *testing.T) {
idx := test.MustOpenIndex()
defer idx.Close()
f, err := idx.CreateField("f", pilosa.OptFieldTypeInt(0, math.MinInt64, math.MaxInt64))
f, err := idx.CreateField("f", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64))
if err != nil {
t.Fatal(err)
}
@ -64,7 +64,7 @@ func TestField_SetValue(t *testing.T) {
idx := test.MustOpenIndex()
defer idx.Close()
f, err := idx.CreateField("f", pilosa.OptFieldTypeInt(0, math.MinInt64, math.MaxInt64))
f, err := idx.CreateField("f", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64))
if err != nil {
t.Fatal(err)
}
@ -107,6 +107,36 @@ func TestField_SetValue(t *testing.T) {
t.Fatalf("unexpected error: %s", err)
}
})
t.Run("ErrBSIGroupValueTooLow", func(t *testing.T) {
idx := test.MustOpenIndex()
defer idx.Close()
f, err := idx.CreateField("f", pilosa.OptFieldTypeInt(20, 30))
if err != nil {
t.Fatal(err)
}
// Set value.
if _, err := f.SetValue(100, 15); err != pilosa.ErrBSIGroupValueTooLow {
t.Fatalf("unexpected error: %s", err)
}
})
t.Run("ErrBSIGroupValueTooHigh", func(t *testing.T) {
idx := test.MustOpenIndex()
defer idx.Close()
f, err := idx.CreateField("f", pilosa.OptFieldTypeInt(20, 30))
if err != nil {
t.Fatal(err)
}
// Set value.
if _, err := f.SetValue(100, 31); err != pilosa.ErrBSIGroupValueTooHigh {
t.Fatalf("unexpected error: %s", err)
}
})
}
func TestField_NameRestriction(t *testing.T) {

View file

@ -799,11 +799,8 @@ func (c *InternalClient) CreateFieldWithOptions(ctx context.Context, index, fiel
fieldOpt.CacheType = &opt.CacheType
fieldOpt.CacheSize = &opt.CacheSize
} else if fieldOpt.Type == "int" {
if opt.Base == 0 && opt.Min != 0 {
opt.Base = opt.Min
}
fieldOpt.Base = &opt.Base
fieldOpt.BitDepth = &opt.BitDepth
fieldOpt.Min = &opt.Min
fieldOpt.Max = &opt.Max
} else if fieldOpt.Type == "time" {
fieldOpt.TimeQuantum = &opt.TimeQuantum
}

View file

@ -705,7 +705,7 @@ func TestClient_ImportKeys(t *testing.T) {
// Load bitmap into cache to ensure cache gets updated.
index := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{Keys: true})
field, err := index.CreateFieldIfNotExists(fldName, pilosa.OptFieldTypeInt(0, -100, 100))
field, err := index.CreateFieldIfNotExists(fldName, pilosa.OptFieldTypeInt(-100, 100))
if err != nil {
t.Fatal(err)
}
@ -790,7 +790,7 @@ func TestClient_ImportValue(t *testing.T) {
// Load bitmap into cache to ensure cache gets updated.
index := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{})
field, err := index.CreateFieldIfNotExists(fldName, pilosa.OptFieldTypeInt(0, -100, 100))
field, err := index.CreateFieldIfNotExists(fldName, pilosa.OptFieldTypeInt(-100, 100))
if err != nil {
t.Fatal(err)
}
@ -832,8 +832,8 @@ func TestClient_ImportValue(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if min != -100 || cnt != 0 {
t.Fatalf("unexpected values: got min=%v, count=%v; expected min=-100, cnt=0", min, cnt)
if min != 0 || cnt != 0 {
t.Fatalf("unexpected values: got min=%v, count=%v; expected min=0, cnt=0", min, cnt)
}
// Verify Max.
@ -871,8 +871,8 @@ func TestClient_ImportValue(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if min != -100 || cnt != 0 {
t.Fatalf("unexpected values: got min=%v, count=%v; expected min=-100, cnt=0", min, cnt)
if min != 0 || cnt != 0 {
t.Fatalf("unexpected values: got min=%v, count=%v; expected min=0, cnt=0", min, cnt)
}
// Verify Max.
@ -933,7 +933,7 @@ func TestClient_ImportExistence(t *testing.T) {
fldName := "fint"
index := hldr.MustCreateIndexIfNotExists(idxName, pilosa.IndexOptions{TrackExistence: true})
field, err := index.CreateFieldIfNotExists(fldName, pilosa.OptFieldTypeInt(0, -100, 100))
field, err := index.CreateFieldIfNotExists(fldName, pilosa.OptFieldTypeInt(-100, 100))
if err != nil {
t.Fatal(err)
}

View file

@ -759,13 +759,7 @@ func (h *Handler) handlePostField(w http.ResponseWriter, r *http.Request) {
case pilosa.FieldTypeSet:
fos = append(fos, pilosa.OptFieldTypeSet(*req.Options.CacheType, *req.Options.CacheSize))
case pilosa.FieldTypeInt:
var base int64
if v := req.Options.Base; v != nil {
base = *v
} else if v := req.Options.Min; v != nil {
base = *v
}
fos = append(fos, pilosa.OptFieldTypeInt(base, math.MinInt64, math.MaxInt64))
fos = append(fos, pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64))
case pilosa.FieldTypeTime:
fos = append(fos, pilosa.OptFieldTypeTime(*req.Options.TimeQuantum, req.Options.NoStandardView))
case pilosa.FieldTypeMutex:
@ -793,15 +787,11 @@ type fieldOptions struct {
Type string `json:"type,omitempty"`
CacheType *string `json:"cacheType,omitempty"`
CacheSize *uint32 `json:"cacheSize,omitempty"`
Base *int64 `json:"base,omitempty"`
BitDepth *uint `json:"bitDepth,omitempty"`
Min *int64 `json:"min,omitempty"`
Max *int64 `json:"max,omitempty"`
TimeQuantum *pilosa.TimeQuantum `json:"timeQuantum,omitempty"`
Keys *bool `json:"keys,omitempty"`
NoStandardView bool `json:"noStandardView,omitempty"`
// Deprecated. Use base/bit depth.
Min *int64 `json:"min,omitempty"`
Max *int64 `json:"max,omitempty"`
}
func (o *fieldOptions) validate() error {
@ -823,11 +813,7 @@ func (o *fieldOptions) validate() error {
if o.CacheSize == nil {
o.CacheSize = &defaultCacheSize
}
if o.Base != nil {
return pilosa.NewBadRequestError(errors.New("base does not apply to field type set"))
} else if o.BitDepth != nil {
return pilosa.NewBadRequestError(errors.New("bit depth does not apply to field type set"))
} else if o.Min != nil {
if o.Min != nil {
return pilosa.NewBadRequestError(errors.New("min does not apply to field type set"))
} else if o.Max != nil {
return pilosa.NewBadRequestError(errors.New("max does not apply to field type set"))
@ -847,10 +833,6 @@ func (o *fieldOptions) validate() error {
return pilosa.NewBadRequestError(errors.New("cacheType does not apply to field type time"))
} else if o.CacheSize != nil {
return pilosa.NewBadRequestError(errors.New("cacheSize does not apply to field type time"))
} else if o.Base != nil {
return pilosa.NewBadRequestError(errors.New("base does not apply to field type time"))
} else if o.BitDepth != nil {
return pilosa.NewBadRequestError(errors.New("bitDepth does not apply to field type time"))
} else if o.Min != nil {
return pilosa.NewBadRequestError(errors.New("min does not apply to field type time"))
} else if o.Max != nil {
@ -865,11 +847,7 @@ func (o *fieldOptions) validate() error {
if o.CacheSize == nil {
o.CacheSize = &defaultCacheSize
}
if o.Base != nil {
return pilosa.NewBadRequestError(errors.New("base does not apply to field type mutex"))
} else if o.BitDepth != nil {
return pilosa.NewBadRequestError(errors.New("bitDepth does not apply to field type mutex"))
} else if o.Min != nil {
if o.Min != nil {
return pilosa.NewBadRequestError(errors.New("min does not apply to field type mutex"))
} else if o.Max != nil {
return pilosa.NewBadRequestError(errors.New("max does not apply to field type mutex"))
@ -881,10 +859,6 @@ func (o *fieldOptions) validate() error {
return pilosa.NewBadRequestError(errors.New("cacheType does not apply to field type bool"))
} else if o.CacheSize != nil {
return pilosa.NewBadRequestError(errors.New("cacheSize does not apply to field type bool"))
} else if o.Base != nil {
return pilosa.NewBadRequestError(errors.New("base does not apply to field type bool"))
} else if o.BitDepth != nil {
return pilosa.NewBadRequestError(errors.New("bitDepth does not apply to field type bool"))
} else if o.Min != nil {
return pilosa.NewBadRequestError(errors.New("min does not apply to field type bool"))
} else if o.Max != nil {

View file

@ -16,7 +16,6 @@ package pilosa_test
import (
"io/ioutil"
"math"
"reflect"
"testing"
@ -94,7 +93,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.OptFieldTypeInt(10, math.MinInt64, math.MaxInt64)); err != nil {
if f, err := index.CreateField("f", pilosa.OptFieldTypeInt(-990, 1000)); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(f.Type(), pilosa.FieldTypeInt) {
t.Fatalf("unexpected type: %#v", f.Type())

View file

@ -40,6 +40,8 @@ var (
ErrInvalidBSIGroupType = errors.New("invalid bsigroup type")
ErrInvalidBSIGroupRange = errors.New("invalid bsigroup range")
ErrInvalidBSIGroupValueType = errors.New("invalid bsigroup value type")
ErrBSIGroupValueTooLow = errors.New("bsigroup value too low")
ErrBSIGroupValueTooHigh = errors.New("bsigroup value too high")
ErrInvalidRangeOperation = errors.New("invalid range operation")
ErrInvalidBetweenValue = errors.New("invalid value for between operation")

View file

@ -230,7 +230,7 @@ func TestHandler_Endpoints(t *testing.T) {
t.Run("ImportRoaringFieldTypeFail", func(t *testing.T) {
// Roaring import into a non-set field should fail.
if _, err := i0.CreateFieldIfNotExists("int-field", pilosa.OptFieldTypeInt(0, 0, 1)); err != nil {
if _, err := i0.CreateFieldIfNotExists("int-field", pilosa.OptFieldTypeInt(0, 1)); err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()