diff --git a/http_handler.go b/http_handler.go index 9dc5319ae..07385f342 100644 --- a/http_handler.go +++ b/http_handler.go @@ -1563,20 +1563,43 @@ func fieldOptionsToFunctionalOpts(opt fieldOptions) []FieldOption { case FieldTypeSet: fos = append(fos, OptFieldTypeSet(*opt.CacheType, *opt.CacheSize)) case FieldTypeInt: - if opt.Min == nil { - min := pql.NewDecimal(int64(math.MinInt64), 0) - opt.Min = &min + min, max := pql.MinMax(0) + // ensure the provided bounds are valid + if opt.Max != nil && max.LessThan(*opt.Max) { + opt.Max = &max } if opt.Max == nil { - max := pql.NewDecimal(int64(math.MaxInt64), 0) opt.Max = &max } + + if opt.Min != nil && min.GreaterThan(*opt.Min) { + opt.Min = &min + } + if opt.Min == nil { + opt.Min = &min + } fos = append(fos, OptFieldTypeInt(opt.Min.ToInt64(0), opt.Max.ToInt64(0))) case FieldTypeDecimal: scale := int64(0) if opt.Scale != nil { scale = *opt.Scale + min, max := pql.MinMax(scale) + // ensure the provided bounds are valid + if opt.Max != nil && max.LessThan(*opt.Max) { + opt.Max = &max + } + if opt.Max == nil { + opt.Max = &max + } + + if opt.Min != nil && min.GreaterThan(*opt.Min) { + opt.Min = &min + } + if opt.Min == nil { + opt.Min = &min + } } + if opt.Min == nil { min := pql.NewDecimal(int64(math.MinInt64), scale) opt.Min = &min diff --git a/index.go b/index.go index 12e0aee68..21686b04b 100644 --- a/index.go +++ b/index.go @@ -11,6 +11,7 @@ import ( "sync" "github.com/molecula/featurebase/v3/disco" + "github.com/molecula/featurebase/v3/pql" "github.com/molecula/featurebase/v3/roaring" "github.com/molecula/featurebase/v3/stats" "github.com/molecula/featurebase/v3/testhook" @@ -613,6 +614,27 @@ func (i *Index) CreateFieldIfNotExistsWithOptions(name string, opt *FieldOptions if f := i.fields[name]; f != nil { return f, nil } + // added for backward compatablity with old schemas + if opt != nil && opt.Type == FieldTypeDecimal { + min, max := pql.MinMax(opt.Scale) + // ensure the provided bounds are valid + if max.LessThan(opt.Max) { + opt.Max = max + } + if min.GreaterThan(opt.Min) { + opt.Min = min + } + } + if opt != nil && opt.Type == FieldTypeDecimal { + min, max := pql.MinMax(0) + // ensure the provided bounds are valid + if max.LessThan(opt.Max) { + opt.Max = max + } + if min.GreaterThan(opt.Min) { + opt.Min = min + } + } cfm := &CreateFieldMessage{ Index: i.name, diff --git a/server/handler_test.go b/server/handler_test.go index 2b17d7bd3..1f8225bba 100644 --- a/server/handler_test.go +++ b/server/handler_test.go @@ -886,6 +886,37 @@ func TestHandler_Endpoints(t *testing.T) { } } }) + t.Run("Query decimal field scale only", func(t *testing.T) { + w := httptest.NewRecorder() + fieldName := "f-decimal-scale-only" + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", fmt.Sprintf("/index/i0/field/%s", fieldName), + strings.NewReader(`{"options":{"type":"decimal", "scale": 2}}`))) + if w.Code != gohttp.StatusOK { + fmt.Println(w.Body.String()) + t.Fatalf("unexpected status code: %d", w.Code) + } + w = httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/schema", strings.NewReader(""))) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } + rsp := getSchemaResponse{} + if err := json.Unmarshal(w.Body.Bytes(), &rsp); err != nil { + t.Fatalf("json decode: %s", err) + } + field := rsp.findField("i0", fieldName) + if field == nil { + t.Fatalf("field not found: %s", fieldName) + } + if field != nil { // happy linter + if !reflect.DeepEqual(pql.NewDecimal(math.MinInt64, 2), field.Options.Min) { + t.Fatalf("field min %d != %d", pql.NewDecimal(math.MinInt64, 1), field.Options.Min) + } + if !reflect.DeepEqual(pql.NewDecimal(math.MaxInt64, 2), field.Options.Max) { + t.Fatalf("field min %d != %d", pql.NewDecimal(math.MaxInt64, 2), field.Options.Max) + } + } + }) // Ensure that decimal fields error when scale is not provided. t.Run("Query decimal field scale error", func(t *testing.T) {