diff --git a/executor.go b/executor.go index a911eb0a7..0269ba473 100644 --- a/executor.go +++ b/executor.go @@ -2967,6 +2967,16 @@ func (e *executor) executeSet(ctx context.Context, index string, c *pql.Call, op if !ok { return false, fmt.Errorf("Set() row argument '%v' required", rowLabel) } + + // Before we scale a decimal to an integer, we need to make sure the decimal + // is between min/max for the field. If it's not, converting to an integer + // can result in an overflow. + if dec, ok := v.(pql.Decimal); ok && f.Options().Type == FieldTypeDecimal { + if dec.LessThan(f.Options().Min) || dec.GreaterThan(f.Options().Max) { + return false, ErrDecimalOutOfRange + } + } + // Read row value. rowVal, err := getScaledInt(f, v) if err != nil { diff --git a/executor_test.go b/executor_test.go index ad0d1c56e..f894a10ee 100644 --- a/executor_test.go +++ b/executor_test.go @@ -5251,6 +5251,23 @@ func TestExecutor_Execute_MinMaxCountEqual(t *testing.T) { } } }) + + t.Run("MinMaxRangeError", func(t *testing.T) { + // Min + pql := `Set(4, dec=-92233720368547758.08)` + if _, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: pql}); err == nil { + t.Fatalf("expected error but got: nil") + } else if errors.Cause(err) != pilosa.ErrDecimalOutOfRange { + t.Fatalf("expected error: %s, but got: %s", pilosa.ErrDecimalOutOfRange, err) + } + // Max + pql = `Set(4, dec=92233720368547758.07)` + if _, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: pql}); err == nil { + t.Fatalf("expected error but got: nil") + } else if errors.Cause(err) != pilosa.ErrDecimalOutOfRange { + t.Fatalf("expected error: %s, but got: %s", pilosa.ErrDecimalOutOfRange, err) + } + }) } func TestExecutor_Execute_NoIndex(t *testing.T) { diff --git a/field.go b/field.go index 996d6be20..b36db5753 100644 --- a/field.go +++ b/field.go @@ -800,13 +800,13 @@ func (f *Field) loadMeta() error { if pb.Min != nil { min = pql.NewDecimal(pb.Min.Value, pb.Min.Scale) } else { - min = pql.NewDecimal(pb.OldMin, 0) + min = pql.NewDecimal(pb.OldMin, pb.Scale) } var max pql.Decimal if pb.Max != nil { max = pql.NewDecimal(pb.Max.Value, pb.Max.Scale) } else { - max = pql.NewDecimal(pb.OldMax, 0) + max = pql.NewDecimal(pb.OldMax, pb.Scale) } // Initialize "base" to "min" when upgrading from v1 BSI format. diff --git a/pilosa.go b/pilosa.go index 86ecd303a..d7eb2f3c8 100644 --- a/pilosa.go +++ b/pilosa.go @@ -46,6 +46,7 @@ var ( ErrBSIGroupValueTooHigh = errors.New("bsigroup value too high") ErrInvalidRangeOperation = errors.New("invalid range operation") ErrInvalidBetweenValue = errors.New("invalid value for between operation") + ErrDecimalOutOfRange = errors.New("decimal value out of range") ErrInvalidView = errors.New("invalid view") ErrInvalidCacheType = errors.New("invalid cache type") diff --git a/pql/decimal.go b/pql/decimal.go index 186fff037..08a4be5c2 100644 --- a/pql/decimal.go +++ b/pql/decimal.go @@ -425,6 +425,8 @@ func quotient(d Decimal) int64 { return d.Value } else if d.Scale > 0 && d.Scale < 19 { return d.Value / pow10[d.Scale] + } else if d.Scale < 0 && d.Scale > -19 { + return d.Value * pow10[-1*d.Scale] } return 0 }