Merge pull request #227 from travisturner/backout-decimal

back out the pql.Decimal changes
This commit is contained in:
Matthew Jaffee 2020-04-01 11:32:02 -05:00 committed by GitHub
commit e77bf45643
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 401 additions and 1148 deletions

View file

@ -447,9 +447,39 @@ func TestAPI_ImportValue(t *testing.T) {
t.Fatalf("creating index: %v", err)
}
_, err = m0.API.CreateField(ctx, index, field, pilosa.OptFieldTypeDecimal(-1))
if err == nil {
t.Fatal("expected error creating field")
if err != nil {
t.Fatalf("creating field: %v", err)
}
// Generate some keyed records.
values := []float64{}
colIDs := []uint64{}
for i := 0; i < 10; i++ {
values = append(values, float64(i)*100+10)
colIDs = append(colIDs, uint64(i))
}
// Import data with keys to the coordinator (node0) and verify that it gets
// translated and forwarded to the owner of shard 0 (node1; because of offsetModHasher)
req := &pilosa.ImportValueRequest{
Index: index,
Field: field,
ColumnIDs: colIDs,
FloatValues: values,
}
if err := m1.API.ImportValue(ctx, req); err != nil {
t.Fatal(err)
}
pql := fmt.Sprintf("Row(%s>600)", field)
// Query node0.
if res, err := m0.API.Query(ctx, &pilosa.QueryRequest{Index: index, Query: pql}); err != nil {
t.Fatal(err)
} else if ids := res.Results[0].(*pilosa.Row).Columns(); !reflect.DeepEqual(ids, colIDs[6:]) {
t.Fatalf("unexpected column keys: %+v", ids)
}
})
t.Run("ValStringField", func(t *testing.T) {

View file

@ -54,8 +54,8 @@ omitted. If it is present then its format should be YYYY-MM-DDTHH:MM.
flags.BoolVar(&Importer.IndexOptions.Keys, "index-keys", false, "Specify keys=true when creating an index")
flags.BoolVar(&Importer.FieldOptions.Keys, "field-keys", false, "Specify keys=true when creating a field")
flags.StringVar(&Importer.FieldOptions.Type, "field-type", "", "Specify the field type when creating a field. One of: set, int, decimal, time, bool, mutex")
flags.Int64Var(&Importer.FieldOptions.Min.Value, "field-min", 0, "Specify the minimum for an int field on creation") // TODO: noting that decimal field min/max are not supported here.
flags.Int64Var(&Importer.FieldOptions.Max.Value, "field-max", 0, "Specify the maximum for an int field on creation")
flags.Int64Var(&Importer.FieldOptions.Min, "field-min", 0, "Specify the minimum for an int field on creation")
flags.Int64Var(&Importer.FieldOptions.Max, "field-max", 0, "Specify the maximum for an int field on creation")
flags.StringVar(&Importer.FieldOptions.CacheType, "field-cache-type", pilosa.CacheTypeRanked, "Specify the cache type for a set field on creation. One of: none, lru, ranked")
flags.Uint32Var(&Importer.FieldOptions.CacheSize, "field-cache-size", 50000, "Specify the cache size for a set field on creation")
flags.Var(&Importer.FieldOptions.TimeQuantum, "field-time-quantum", "Specify the time quantum for a time field on creation. One of: D, DH, H, M, MD, MDH, Y, YM, YMD, YMDH")

View file

@ -21,7 +21,6 @@ import (
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/cmd"
"github.com/pilosa/pilosa/v2/pql"
)
func TestImportHelp(t *testing.T) {
@ -59,8 +58,8 @@ field = "f1"
v.Check(cmd.Importer.Field, "f1")
v.Check(cmd.Importer.FieldOptions, pilosa.FieldOptions{
Keys: true,
Max: pql.NewDecimal(100, 0),
Min: pql.NewDecimal(-10, 0),
Max: 100,
Min: -10,
CacheType: pilosa.CacheTypeRanked,
CacheSize: 50000,
})

View file

@ -28,7 +28,6 @@ import (
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/http"
"github.com/pilosa/pilosa/v2/pql"
"github.com/pilosa/pilosa/v2/server"
"github.com/pkg/errors"
)
@ -105,7 +104,7 @@ func (cmd *ImportCommand) Run(ctx context.Context) error {
// set the correct type for the field
if cmd.FieldOptions.TimeQuantum != "" {
cmd.FieldOptions.Type = pilosa.FieldTypeTime
} else if cmd.FieldOptions.Min != pql.NewDecimal(0, 0) || cmd.FieldOptions.Max != pql.NewDecimal(0, 0) {
} else if cmd.FieldOptions.Min != 0 || cmd.FieldOptions.Max != 0 {
cmd.FieldOptions.Type = pilosa.FieldTypeInt
} else {
cmd.FieldOptions.Type = pilosa.FieldTypeSet

View file

@ -21,7 +21,6 @@ import (
"github.com/gogo/protobuf/proto"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/internal"
"github.com/pilosa/pilosa/v2/pql"
"github.com/pilosa/pilosa/v2/roaring"
"github.com/pkg/errors"
)
@ -612,8 +611,8 @@ func encodeFieldOptions(o *pilosa.FieldOptions) *internal.FieldOptions {
Type: o.Type,
CacheType: o.CacheType,
CacheSize: o.CacheSize,
Min: &internal.Decimal{Value: o.Min.Value, Scale: o.Min.Scale},
Max: &internal.Decimal{Value: o.Max.Value, Scale: o.Max.Scale},
Min: o.Min,
Max: o.Max,
Base: o.Base,
Scale: o.Scale,
BitDepth: uint64(o.BitDepth),
@ -916,8 +915,8 @@ func decodeFieldOptions(options *internal.FieldOptions, m *pilosa.FieldOptions)
m.Type = options.Type
m.CacheType = options.CacheType
m.CacheSize = options.CacheSize
decodeDecimal(options.Min, &m.Min)
decodeDecimal(options.Max, &m.Max)
m.Min = options.Min
m.Max = options.Max
m.Base = options.Base
m.Scale = options.Scale
m.BitDepth = uint(options.BitDepth)
@ -926,11 +925,6 @@ func decodeFieldOptions(options *internal.FieldOptions, m *pilosa.FieldOptions)
m.ForeignIndex = options.ForeignIndex
}
func decodeDecimal(d *internal.Decimal, m *pql.Decimal) {
m.Value = d.Value
m.Scale = d.Scale
}
func decodeNodes(a []*internal.Node, m []*pilosa.Node) {
for i := range a {
m[i] = &pilosa.Node{}
@ -1245,7 +1239,7 @@ func decodeQueryResult(pb *internal.QueryResult) interface{} {
panic(fmt.Sprintf("unknown type: %d", pb.Type))
}
// decodeRow converts r from its internal representation.
// DecodeRow converts r from its internal representation.
func decodeRow(pr *internal.Row) *pilosa.Row {
if pr == nil {
return pilosa.NewRow()

View file

@ -1478,14 +1478,14 @@ func TestExecutor_Execute_MinMax(t *testing.T) {
tests := []struct {
scale int64
min pql.Decimal
max pql.Decimal
min int64
max int64
set pql.Decimal
}{
{2, pql.Decimal{Value: 1, Scale: -1}, pql.Decimal{Value: 2, Scale: -1}, pql.Decimal{Value: 115, Scale: 1}},
{2, pql.Decimal{Value: -1, Scale: -1}, pql.Decimal{Value: 2, Scale: -1}, pql.Decimal{Value: 115, Scale: 1}},
{2, pql.Decimal{Value: -1, Scale: -1}, pql.Decimal{Value: 2, Scale: -1}, pql.Decimal{Value: -95, Scale: 1}},
{2, pql.Decimal{Value: -2, Scale: -1}, pql.Decimal{Value: -1, Scale: -1}, pql.Decimal{Value: -115, Scale: 1}},
{2, 10, 20, pql.Decimal{Value: 115, Scale: 1}},
{2, -10, 20, pql.Decimal{Value: 115, Scale: 1}},
{2, -10, 20, pql.Decimal{Value: -95, Scale: 1}},
{2, -20, -10, pql.Decimal{Value: -115, Scale: 1}},
}
for i, test := range tests {
fld := fmt.Sprintf("f%d", i)
@ -2249,37 +2249,6 @@ func TestExecutor_Execute_Range_Deprecated(t *testing.T) {
})
}
// Ensure decimal args are supported for Decimal fields.
func TestExecutor_DecimalArgs(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
hldr := test.Holder{Holder: c[0].Server.Holder()}
idx, err := hldr.CreateIndex("i", pilosa.IndexOptions{})
if err != nil {
t.Fatal(err)
}
min, err := pql.ParseDecimal("-10.5")
if err != nil {
t.Fatal(err)
}
max, err := pql.ParseDecimal("10.5")
if err != nil {
t.Fatal(err)
}
if _, err := idx.CreateField("f", pilosa.OptFieldTypeDecimal(2, min, max)); err != nil {
t.Fatal(err)
}
if _, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `
Set(0, f=0)
`}); err != nil {
t.Fatal(err)
}
}
// Ensure a Row(bsiGroup) query can be executed.
func TestExecutor_Execute_Row_BSIGroup(t *testing.T) {
c := test.MustRunCluster(t, 1)

149
field.go
View file

@ -178,80 +178,55 @@ func OptFieldTypeInt(min, max int64) FieldOption {
return errors.New("int field min cannot be greater than max")
}
fo.Type = FieldTypeInt
fo.Min = pql.NewDecimal(min, 0)
fo.Max = pql.NewDecimal(max, 0)
fo.Min = min
fo.Max = max
fo.Base = bsiBase(min, max)
return nil
}
}
// OptFieldTypeDecimal is a functional option for creating a `decimal` field.
// Unless we decide to expand the range of supported values, `scale` is
// restricted to the range [0,19]. This supports anything from:
//
// scale = 0:
// min: -9223372036854775808.
// max: 9223372036854775807.
//
// to:
//
// scale = 19:
// min: -0.9223372036854775808
// max: 0.9223372036854775807
//
// While it's possible to support scale values outside of this range,
// the coverage for those scales are no longer continuous. For example,
//
// scale = -2:
// min : [-922337203685477580800, -100]
// GAPs: [-99, -1], [-199, -101] ... [-922337203685477580799, -922337203685477580701]
// 0
// max : [100, 922337203685477580700]
// GAPs: [1, 99], [101, 199] ... [922337203685477580601, 922337203685477580699]
//
// An alternative to this gap strategy would be to scale the supported range
// to a continuous 64-bit space (which is not unreasonable using bsiGroup.Base).
// The issue with this approach is that we would need to know which direction
// to favor. For example, there are two possible ranges for `scale = -2`:
//
// min : [-922337203685477580800, -922337203685477580800+(2^64)]
// max : [922337203685477580700-(2^64), 922337203685477580700]
//
func OptFieldTypeDecimal(scale int64, minmax ...pql.Decimal) FieldOption {
func OptFieldTypeDecimal(scale int64, minmax ...int64) FieldOption {
return func(fo *FieldOptions) error {
if fo.Type != "" {
return errors.Errorf("can't set field type to 'decimal', already set to: %s", fo.Type)
}
if scale < 0 || scale > 19 {
return errors.Errorf("scale values outside the range [0,19] are not supported: %d", scale)
}
fo.Min, fo.Max = pql.MinMax(scale)
fo.Min = math.MinInt64
fo.Max = math.MaxInt64
if len(minmax) == 2 {
min := minmax[0]
max := minmax[1]
if !min.IsValid() || !max.IsValid() {
return errors.Errorf("min/max range %s-%s is not supported", min, max)
} else if !min.SupportedByScale(scale) || !max.SupportedByScale(scale) {
return errors.Errorf("min/max range %s-%s is not supported by scale %d", min, max, scale)
} else if min.GreaterThan(max) {
return errors.Errorf("decimal field min cannot be greater than max, got %s, %s", min, max)
min, max := minmax[0], minmax[1]
if scale != 0 {
// If the min/max provided are already on the boundary of int64,
// then we don't want to operate on them and cause overflow.
// There are still overflow scenarios where a user provides a
// min/max which is not on the boundary, but overflow once the
// scale is applied. This does not address those cases, but at
// least it addresses the default case (where a min/max is not
// provided).
if min != math.MinInt64 {
min = int64(float64(min) * math.Pow10(int(scale)))
}
if max != math.MaxInt64 {
max = int64(float64(max) * math.Pow10(int(scale)))
}
}
if min > max {
return errors.Errorf("decimal field min cannot be greater than max, got %d, %d", min, max)
}
fo.Min = min
fo.Max = max
} else if len(minmax) > 2 {
return errors.Errorf("unknown extra parameters beyond min and max: %v", minmax)
} else if len(minmax) == 1 {
min := minmax[0]
if !min.IsValid() {
return errors.Errorf("min %s is not supported", min)
} else if !min.SupportedByScale(scale) {
return errors.Errorf("min %s is not supported by scale %d", min, scale)
// It's not necessary to handle the scale==0 case separately,
// but it avoids the type conversion.
if scale == 0 || minmax[0] == math.MinInt64 {
fo.Min = minmax[0]
} else {
fo.Min = int64(float64(minmax[0]) * math.Pow10(int(scale)))
}
fo.Min = min
}
fo.Type = FieldTypeDecimal
fo.Base = bsiBase(fo.Min.ToInt64(scale), fo.Max.ToInt64(scale))
fo.Base = bsiBase(fo.Min, fo.Max)
fo.Scale = scale
return nil
}
@ -717,14 +692,10 @@ func (f *Field) loadMeta() error {
}
}
min := pql.NewDecimal(pb.Min.Value, pb.Min.Scale)
max := pql.NewDecimal(pb.Max.Value, pb.Max.Scale)
// Initialize "base" to "min" when upgrading from v1 BSI format.
if pb.BitDepth == 0 {
minInt64, maxInt64 := min.ToInt64(0), max.ToInt64(0)
pb.Base = bsiBase(minInt64, maxInt64)
pb.BitDepth = uint64(bitDepthInt64(maxInt64 - minInt64))
pb.Base = bsiBase(pb.Min, pb.Max)
pb.BitDepth = uint64(bitDepthInt64(pb.Max - pb.Min))
if pb.BitDepth == 0 {
pb.BitDepth = 1
}
@ -734,8 +705,8 @@ func (f *Field) loadMeta() error {
f.options.Type = pb.Type
f.options.CacheType = pb.CacheType
f.options.CacheSize = pb.CacheSize
f.options.Min = min
f.options.Max = max
f.options.Min = pb.Min
f.options.Max = pb.Max
f.options.Base = pb.Base
f.options.Scale = pb.Scale
f.options.BitDepth = uint(pb.BitDepth)
@ -795,8 +766,8 @@ func (f *Field) applyOptions(opt FieldOptions) error {
} else if opt.CacheSize != 0 {
f.options.CacheSize = opt.CacheSize
}
f.options.Min = pql.Decimal{}
f.options.Max = pql.Decimal{}
f.options.Min = 0
f.options.Max = 0
f.options.Base = 0
f.options.BitDepth = 0
f.options.TimeQuantum = ""
@ -819,8 +790,8 @@ func (f *Field) applyOptions(opt FieldOptions) error {
bsig := &bsiGroup{
Name: f.name,
Type: bsiGroupTypeInt,
Min: opt.Min.ToInt64(opt.Scale),
Max: opt.Max.ToInt64(opt.Scale),
Min: opt.Min,
Max: opt.Max,
Base: opt.Base,
Scale: opt.Scale,
BitDepth: opt.BitDepth,
@ -836,8 +807,8 @@ func (f *Field) applyOptions(opt FieldOptions) error {
f.options.Type = opt.Type
f.options.CacheType = CacheTypeNone
f.options.CacheSize = 0
f.options.Min = pql.Decimal{}
f.options.Max = pql.Decimal{}
f.options.Min = 0
f.options.Max = 0
f.options.Base = 0
f.options.BitDepth = 0
f.options.Keys = opt.Keys
@ -852,8 +823,8 @@ func (f *Field) applyOptions(opt FieldOptions) error {
f.options.Type = FieldTypeBool
f.options.CacheType = CacheTypeNone
f.options.CacheSize = 0
f.options.Min = pql.Decimal{}
f.options.Max = pql.Decimal{}
f.options.Min = 0
f.options.Max = 0
f.options.Base = 0
f.options.BitDepth = 0
f.options.TimeQuantum = ""
@ -1847,8 +1818,8 @@ 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 pql.Decimal `json:"min,omitempty"`
Max pql.Decimal `json:"max,omitempty"`
Min int64 `json:"min,omitempty"`
Max int64 `json:"max,omitempty"`
Scale int64 `json:"scale,omitempty"`
Keys bool `json:"keys"`
NoStandardView bool `json:"noStandardView,omitempty"`
@ -1910,8 +1881,8 @@ func encodeFieldOptions(o *FieldOptions) *internal.FieldOptions {
Base: o.Base,
Scale: o.Scale,
BitDepth: uint64(o.BitDepth),
Min: &internal.Decimal{Value: o.Min.Value, Scale: o.Min.Scale},
Max: &internal.Decimal{Value: o.Max.Value, Scale: o.Max.Scale},
Min: o.Min,
Max: o.Max,
TimeQuantum: string(o.TimeQuantum),
Keys: o.Keys,
NoStandardView: o.NoStandardView,
@ -1938,13 +1909,13 @@ func (o *FieldOptions) MarshalJSON() ([]byte, error) {
})
case FieldTypeInt:
return json.Marshal(struct {
Type string `json:"type"`
Base int64 `json:"base"`
BitDepth uint `json:"bitDepth"`
Min pql.Decimal `json:"min"`
Max pql.Decimal `json:"max"`
Keys bool `json:"keys"`
ForeignIndex string `json:"foreignIndex"`
Type string `json:"type"`
Base int64 `json:"base"`
BitDepth uint `json:"bitDepth"`
Min int64 `json:"min"`
Max int64 `json:"max"`
Keys bool `json:"keys"`
ForeignIndex string `json:"foreignIndex"`
}{
o.Type,
o.Base,
@ -1956,13 +1927,13 @@ func (o *FieldOptions) MarshalJSON() ([]byte, error) {
})
case FieldTypeDecimal:
return json.Marshal(struct {
Type string `json:"type"`
Base int64 `json:"base"`
Scale int64 `json:"scale"`
BitDepth uint `json:"bitDepth"`
Min pql.Decimal `json:"min"`
Max pql.Decimal `json:"max"`
Keys bool `json:"keys"`
Type string `json:"type"`
Base int64 `json:"base"`
Scale int64 `json:"scale"`
BitDepth uint `json:"bitDepth"`
Min int64 `json:"min"`
Max int64 `json:"max"`
Keys bool `json:"keys"`
}{
o.Type,
o.Base,

View file

@ -22,7 +22,6 @@ import (
"path/filepath"
"reflect"
"strconv"
"strings"
"testing"
"time"
@ -161,7 +160,7 @@ func TestBSIGroup_BaseValue(t *testing.T) {
// Ensure field can open and retrieve a view.
func TestField_DeleteView(t *testing.T) {
f := OpenField(t, OptFieldTypeDefault())
f := MustOpenField(OptFieldTypeDefault())
defer f.Close()
viewName := viewStandard + "_v"
@ -198,23 +197,23 @@ type TestField struct {
}
// NewTestField returns a new instance of TestField d/0.
func NewTestField(t *testing.T, opts FieldOption) *TestField {
func NewTestField(opts FieldOption) *TestField {
path, err := ioutil.TempDir(*TempDir, "pilosa-field-")
if err != nil {
t.Fatal(err)
panic(err)
}
field, err := NewField(path, "i", "f", opts)
if err != nil {
t.Fatal(err)
panic(err)
}
return &TestField{Field: field}
}
// OpenField returns a new, opened field at a temporary path.
func OpenField(t *testing.T, opts FieldOption) *TestField {
f := NewTestField(t, opts)
// MustOpenField returns a new, opened field at a temporary path. Panic on error.
func MustOpenField(opts FieldOption) *TestField {
f := NewTestField(opts)
if err := f.Open(); err != nil {
t.Fatal(err)
panic(err)
}
return f
}
@ -261,7 +260,7 @@ func (f *TestField) MustSetBit(row, col uint64, ts ...time.Time) {
// Ensure field can open and retrieve a view.
func TestField_CreateViewIfNotExists(t *testing.T) {
f := OpenField(t, OptFieldTypeDefault())
f := MustOpenField(OptFieldTypeDefault())
defer f.Close()
// Create view.
@ -286,7 +285,7 @@ func TestField_CreateViewIfNotExists(t *testing.T) {
}
func TestField_SetTimeQuantum(t *testing.T) {
f := OpenField(t, OptFieldTypeTime(TimeQuantum("")))
f := MustOpenField(OptFieldTypeTime(TimeQuantum("")))
defer f.Close()
// Set & retrieve time quantum.
@ -305,7 +304,7 @@ func TestField_SetTimeQuantum(t *testing.T) {
}
func TestField_RowTime(t *testing.T) {
f := OpenField(t, OptFieldTypeTime(TimeQuantum("")))
f := MustOpenField(OptFieldTypeTime(TimeQuantum("")))
defer f.Close()
if err := f.setTimeQuantum(TimeQuantum("YMDH")); err != nil {
@ -351,7 +350,7 @@ func TestField_RowTime(t *testing.T) {
}
func TestField_PersistAvailableShards(t *testing.T) {
f := OpenField(t, OptFieldTypeDefault())
f := MustOpenField(OptFieldTypeDefault())
// bm represents remote available shards.
bm := roaring.NewBitmap(1, 2, 3)
@ -370,7 +369,7 @@ func TestField_PersistAvailableShards(t *testing.T) {
}
func TestField_CorruptAvailableShards(t *testing.T) {
f := OpenField(t, OptFieldTypeDefault())
f := MustOpenField(OptFieldTypeDefault())
// bm represents remote available shards.
bm := roaring.NewBitmap(1, 2, 3)
@ -400,7 +399,7 @@ func TestField_CorruptAvailableShards(t *testing.T) {
}
func TestField_TruncatedAvailableShards(t *testing.T) {
f := OpenField(t, OptFieldTypeDefault())
f := MustOpenField(OptFieldTypeDefault())
// bm represents remote available shards.
bm := roaring.NewBitmap(1, 2, 3)
@ -428,7 +427,7 @@ func TestField_TruncatedAvailableShards(t *testing.T) {
// Ensure that persisting available shards having a smaller footprint (for example,
// when going from a bitmap to a smaller, RLE representation) succeeds.
func TestField_PersistAvailableShardsFootprint(t *testing.T) {
f := OpenField(t, OptFieldTypeDefault())
f := MustOpenField(OptFieldTypeDefault())
// bm represents remote available shards.
bm := roaring.NewBitmap()
@ -539,7 +538,7 @@ func TestField_ApplyOptions(t *testing.T) {
// into consideration. This would cause an import of 1/8/1
// to result in a value of 9 instead of 1.
func TestBSIGroup_importValue(t *testing.T) {
f := OpenField(t, OptFieldTypeInt(-100, 200))
f := MustOpenField(OptFieldTypeInt(-100, 200))
options := &ImportOptions{}
for i, tt := range []struct {
@ -580,7 +579,7 @@ func TestBSIGroup_importValue(t *testing.T) {
}
func TestIntField_MinMaxForShard(t *testing.T) {
f := OpenField(t, OptFieldTypeInt(-100, 200))
f := MustOpenField(OptFieldTypeInt(-100, 200))
options := &ImportOptions{}
for i, test := range []struct {
@ -655,86 +654,33 @@ func TestIntField_MinMaxForShard(t *testing.T) {
}
}
// Ensure we get errors when they are expected.
func TestDecimalField_MinMaxBoundaries(t *testing.T) {
for i, test := range []struct {
min int64
max int64
scale int64
min pql.Decimal
max pql.Decimal
expErr bool
expmin int64
expmax int64
}{
{
scale: 3,
min: pql.NewDecimal(math.MinInt64, 0),
max: pql.NewDecimal(math.MaxInt64, 0),
expErr: true,
},
{
scale: 3,
min: pql.NewDecimal(math.MinInt64, 3),
max: pql.NewDecimal(math.MaxInt64, 3),
expErr: false,
},
{
scale: 3,
min: pql.NewDecimal(44, 0),
max: pql.NewDecimal(88, 0),
expErr: false,
},
{
scale: 3,
min: pql.NewDecimal(-44, 0),
max: pql.NewDecimal(88, 0),
expErr: false,
},
{
scale: 19,
min: pql.NewDecimal(1, 0),
max: pql.NewDecimal(2, 0),
expErr: true,
},
{
scale: 19,
min: pql.NewDecimal(math.MinInt64, 18),
max: pql.NewDecimal(math.MaxInt64, 18),
expErr: true,
},
{
scale: 0,
min: pql.NewDecimal(1, 20),
max: pql.NewDecimal(2, 20),
expErr: true,
},
{
scale: 0,
min: pql.NewDecimal(1, -1),
max: pql.NewDecimal(2, -1),
expErr: false,
},
{
scale: 0,
min: pql.NewDecimal(1, -19),
max: pql.NewDecimal(2, -19),
expErr: true,
},
{min: math.MinInt64, max: math.MaxInt64, scale: 3, expmin: math.MinInt64, expmax: math.MaxInt64},
{min: 44, max: 88, scale: 3, expmin: 44000, expmax: 88000},
{min: -44, max: 88, scale: 3, expmin: -44000, expmax: 88000},
} {
t.Run("minmax"+strconv.Itoa(i), func(t *testing.T) {
_, err := NewField("no-path", "i", "f", OptFieldTypeDecimal(test.scale, test.min, test.max))
if err != nil && test.expErr {
if !strings.Contains(err.Error(), "is not supported") {
t.Fatal(err)
}
} else if err != nil && !test.expErr {
t.Fatalf("did not expect error, but got: %s", err)
} else if err == nil && test.expErr {
t.Fatal("expected error, but got none")
f := MustOpenField(OptFieldTypeDecimal(test.scale, test.min, test.max))
if f.Options().Min != test.expmin {
t.Fatalf("expected min: %v, but got: %v", test.expmin, f.Options().Min)
}
if f.Options().Max != test.expmax {
t.Fatalf("expected max: %v, but got: %v", test.expmax, f.Options().Max)
}
})
}
}
func TestDecimalField_MinMaxForShard(t *testing.T) {
f := OpenField(t, OptFieldTypeDecimal(3))
f := MustOpenField(OptFieldTypeDecimal(3))
options := &ImportOptions{}
for i, test := range []struct {

View file

@ -1147,7 +1147,7 @@ func TestClient_CreateDecimalField(t *testing.T) {
t.Fatalf("creating index: %v", err)
}
field := "dfield"
err = c.CreateFieldWithOptions(context.Background(), index, field, pilosa.FieldOptions{Type: pilosa.FieldTypeDecimal, Scale: 1, Min: pql.NewDecimal(-1000, 0), Max: pql.NewDecimal(1000, 0)})
err = c.CreateFieldWithOptions(context.Background(), index, field, pilosa.FieldOptions{Type: pilosa.FieldTypeDecimal, Scale: 1, Min: -1000, Max: 1000})
if err != nil {
t.Fatalf("creating field: %v", err)
}

View file

@ -37,7 +37,6 @@ import (
"github.com/gorilla/mux"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/logger"
"github.com/pilosa/pilosa/v2/pql"
"github.com/pilosa/pilosa/v2/tracing"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus/promhttp"
@ -798,39 +797,24 @@ func (h *Handler) handlePostField(w http.ResponseWriter, r *http.Request) {
switch req.Options.Type {
case pilosa.FieldTypeSet:
fos = append(fos, pilosa.OptFieldTypeSet(*req.Options.CacheType, *req.Options.CacheSize))
case pilosa.FieldTypeInt:
case pilosa.FieldTypeInt, pilosa.FieldTypeDecimal:
if req.Options.Min == nil {
min := pql.NewDecimal(int64(math.MinInt64), 0)
min := int64(math.MinInt64)
req.Options.Min = &min
}
if req.Options.Max == nil {
max := pql.NewDecimal(int64(math.MaxInt64), 0)
max := int64(math.MaxInt64)
req.Options.Max = &max
}
fos = append(fos, pilosa.OptFieldTypeInt(req.Options.Min.ToInt64(0), req.Options.Max.ToInt64(0)))
case pilosa.FieldTypeDecimal:
scale := int64(0)
if req.Options.Scale != nil {
scale = *req.Options.Scale
}
if req.Options.Min == nil {
min := pql.NewDecimal(int64(math.MinInt64), scale)
req.Options.Min = &min
}
if req.Options.Max == nil {
max := pql.NewDecimal(int64(math.MaxInt64), scale)
req.Options.Max = &max
}
var minmax []pql.Decimal
if req.Options.Min != nil {
minmax = []pql.Decimal{
*req.Options.Min,
}
if req.Options.Max != nil {
minmax = append(minmax, *req.Options.Max)
if req.Options.Type == pilosa.FieldTypeDecimal {
scale := int64(0)
if req.Options.Scale != nil {
scale = *req.Options.Scale
}
fos = append(fos, pilosa.OptFieldTypeDecimal(scale, *req.Options.Min, *req.Options.Max))
} else {
fos = append(fos, pilosa.OptFieldTypeInt(*req.Options.Min, *req.Options.Max))
}
fos = append(fos, pilosa.OptFieldTypeDecimal(scale, minmax...))
case pilosa.FieldTypeTime:
fos = append(fos, pilosa.OptFieldTypeTime(*req.Options.TimeQuantum, req.Options.NoStandardView))
case pilosa.FieldTypeMutex:
@ -865,8 +849,8 @@ type fieldOptions struct {
Type string `json:"type,omitempty"`
CacheType *string `json:"cacheType,omitempty"`
CacheSize *uint32 `json:"cacheSize,omitempty"`
Min *pql.Decimal `json:"min,omitempty"`
Max *pql.Decimal `json:"max,omitempty"`
Min *int64 `json:"min,omitempty"`
Max *int64 `json:"max,omitempty"`
Scale *int64 `json:"scale,omitempty"`
TimeQuantum *pilosa.TimeQuantum `json:"timeQuantum,omitempty"`
Keys *bool `json:"keys,omitempty"`
@ -902,7 +886,7 @@ func (o *fieldOptions) validate() error {
} else if o.ForeignIndex != nil {
return pilosa.NewBadRequestError(errors.New("set field cannot be a foreign key"))
}
case pilosa.FieldTypeInt:
case pilosa.FieldTypeInt, pilosa.FieldTypeDecimal:
if o.CacheType != nil {
return pilosa.NewBadRequestError(errors.New("cacheType does not apply to field type int"))
} else if o.CacheSize != nil {
@ -912,18 +896,6 @@ func (o *fieldOptions) validate() error {
} else if o.ForeignIndex != nil && o.Type == pilosa.FieldTypeDecimal {
return pilosa.NewBadRequestError(errors.New("decimal field cannot be a foreign key"))
}
case pilosa.FieldTypeDecimal:
if o.Scale == nil {
return pilosa.NewBadRequestError(errors.New("decimal field requires a scale argument"))
} else if o.CacheType != nil {
return pilosa.NewBadRequestError(errors.New("cacheType does not apply to field type int"))
} else if o.CacheSize != nil {
return pilosa.NewBadRequestError(errors.New("cacheSize does not apply to field type int"))
} else if o.TimeQuantum != nil {
return pilosa.NewBadRequestError(errors.New("timeQuantum does not apply to field type int"))
} else if o.ForeignIndex != nil && o.Type == pilosa.FieldTypeDecimal {
return pilosa.NewBadRequestError(errors.New("decimal field cannot be a foreign key"))
}
case pilosa.FieldTypeTime:
if o.CacheType != nil {
return pilosa.NewBadRequestError(errors.New("cacheType does not apply to field type time"))

View file

@ -22,7 +22,6 @@ import (
"testing"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/pql"
)
// Test custom UnmarshalJSON for postIndexRequest object
@ -100,8 +99,8 @@ func stringPtr(s string) *string {
return &s
}
func decimalPtr(d pql.Decimal) *pql.Decimal {
return &d
func int64Ptr(i int64) *int64 {
return &i
}
// Test fieldOption validation.
@ -136,10 +135,10 @@ func TestFieldOptionValidation(t *testing.T) {
// FieldType: Int
{json: `{"options": {"type": "int"}}`, err: "min is required for field type int"},
{json: `{"options": {"type": "int", "min": 0}}`, err: "max is required for field type int"},
{json: `{"options": {"type": "int", "min": 0, "max": 1001}}`, expected: postFieldRequest{Options: fieldOptions{
{json: `{"options": {"type": "int", "min": 0, "max": 1000}}`, expected: postFieldRequest{Options: fieldOptions{
Type: pilosa.FieldTypeInt,
Min: decimalPtr(pql.NewDecimal(0, 0)),
Max: decimalPtr(pql.NewDecimal(1001, 0)),
Min: int64Ptr(0),
Max: int64Ptr(1000),
}}},
{json: `{"options": {"type": "int", "min": 0, "max": 1000, "cacheType": "ranked"}}`, err: "cacheType does not apply to field type int"},
{json: `{"options": {"type": "int", "min": 0, "max": 1000, "cacheSize": 1000}}`, err: "cacheSize does not apply to field type int"},

View file

@ -20,7 +20,6 @@ import (
"testing"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/pql"
"github.com/pilosa/pilosa/v2/test"
"github.com/pkg/errors"
)
@ -204,7 +203,7 @@ func TestIndex_CreateField(t *testing.T) {
index := test.MustOpenIndex()
defer index.Close()
_, err := index.CreateField("f", pilosa.OptFieldTypeDecimal(1, pql.Decimal{Value: -1}, pql.Decimal{Value: 1}), pilosa.OptFieldKeys())
_, err := index.CreateField("f", pilosa.OptFieldTypeDecimal(1, -1, 1), pilosa.OptFieldKeys())
if errors.Cause(err) != pilosa.ErrDecimalFieldWithKeys {
t.Fatal("decimal field cannot be created with keys=true")
}

File diff suppressed because it is too large Load diff

View file

@ -12,19 +12,14 @@ message FieldOptions {
string CacheType = 3;
uint32 CacheSize = 4;
string TimeQuantum = 5;
int64 Min = 9;
int64 Max = 10;
bool Keys = 11;
bool NoStandardView = 12;
int64 Base = 13;
uint64 BitDepth = 14;
int64 Scale = 15;
string ForeignIndex = 16;
Decimal Min = 17;
Decimal Max = 18;
}
message Decimal {
int64 Value = 1;
int64 Scale = 2;
}
message ImportResponse {

View file

@ -23,38 +23,6 @@ import (
"github.com/pkg/errors"
)
// pow10 is a map used to avoid the float64 required by math.Pow10()
var pow10 = map[int64]int64{
0: 1,
1: 10,
2: 100,
3: 1000,
4: 10000,
5: 100000,
6: 1000000,
7: 10000000,
8: 100000000,
9: 1000000000,
10: 10000000000,
11: 100000000000,
12: 1000000000000,
13: 10000000000000,
14: 100000000000000,
15: 1000000000000000,
16: 10000000000000000,
17: 100000000000000000,
18: 1000000000000000000,
//19: 10000000000000000000,
}
// Pow10 is a function which can be used in place of math.Pow10()
// to avoid the float64 logic. Note that only powers 0-18 are
// currently supported; anything else will return 0, which is
// probably going to result in incorrect values.
func Pow10(p int64) int64 {
return pow10[p]
}
// Decimal represents a decimal value; the intention
// is to avoid relying on float64, and the primary
// purpose is to have a predictable way to encode such
@ -68,147 +36,6 @@ type Decimal struct {
Scale int64
}
// NewDecimal returns a Decimal based on the provided arguments.
func NewDecimal(value, scale int64) Decimal {
return Decimal{
Value: value,
Scale: scale,
}
}
// MinMax returns the minimum and maximum values
// supported by the provided scale.
func MinMax(scale int64) (Decimal, Decimal) {
min := NewDecimal(math.MinInt64, scale)
max := NewDecimal(math.MaxInt64, scale)
return min, max
}
// LessThan returns true if d < d2.
func (d Decimal) LessThan(d2 Decimal) bool {
return d.lessThan(d2, false)
}
// LessThanOrEqualTo returns true if d <= d2.
func (d Decimal) LessThanOrEqualTo(d2 Decimal) bool {
return d.lessThan(d2, true)
}
// GreaterThan returns true if d > d2.
func (d Decimal) GreaterThan(d2 Decimal) bool {
return d.greaterThan(d2, false)
}
// GreaterThanOrEqualTo returns true if d >= d2.
func (d Decimal) GreaterThanOrEqualTo(d2 Decimal) bool {
return d.greaterThan(d2, true)
}
// EqualTo returns true if d == d2.
func (d Decimal) EqualTo(d2 Decimal) bool {
if d.Scale == d2.Scale {
return d.Value == d2.Value
}
quotientD := quotient(d)
quotientD2 := quotient(d2)
if quotientD != quotientD2 {
return false
}
remainderD, remainderD2 := remainder(d), remainder(d2)
if d.Scale < d2.Scale {
scaleDiff := d2.Scale - d.Scale
return (remainderD * pow10[scaleDiff]) == remainderD2
}
scaleDiff := d.Scale - d2.Scale
return remainderD == (remainderD2 * pow10[scaleDiff])
}
func (d Decimal) lessThan(d2 Decimal, eq bool) bool {
if d.Scale == d2.Scale {
if eq {
return d.Value <= d2.Value
}
return d.Value < d2.Value
}
quotientD, quotientD2 := quotient(d), quotient(d2)
if quotientD < quotientD2 {
return true
} else if quotientD == quotientD2 {
remainderD, remainderD2 := remainder(d), remainder(d2)
if d.Scale < d2.Scale {
scaleDiff := d2.Scale - d.Scale
if eq {
return (remainderD * pow10[scaleDiff]) <= remainderD2
}
return (remainderD * pow10[scaleDiff]) < remainderD2
}
scaleDiff := d.Scale - d2.Scale
if eq {
return remainderD <= (remainderD2 * pow10[scaleDiff])
}
return remainderD < (remainderD2 * pow10[scaleDiff])
}
return false
}
func (d Decimal) greaterThan(d2 Decimal, eq bool) bool {
if d.Scale == d2.Scale {
if eq {
return d.Value >= d2.Value
}
return d.Value > d2.Value
}
quotientD, quotientD2 := quotient(d), quotient(d2)
if quotientD > quotientD2 {
return true
} else if quotientD == quotientD2 {
remainderD, remainderD2 := remainder(d), remainder(d2)
if d.Scale < d2.Scale {
scaleDiff := d2.Scale - d.Scale
if eq {
return (remainderD * pow10[scaleDiff]) >= remainderD2
}
return (remainderD * pow10[scaleDiff]) > remainderD2
}
scaleDiff := d.Scale - d2.Scale
if eq {
return remainderD >= (remainderD2 * pow10[scaleDiff])
}
return remainderD > (remainderD2 * pow10[scaleDiff])
}
return false
}
// SupportedByScale returns true if d can be represented
// by a decimal based on scale.
// For example:
// scale = 2:
// min: -92233720368547758.08
// max: 92233720368547758.07
// would not support: NewDecimal(9223372036854775807, 0)
func (d Decimal) SupportedByScale(scale int64) bool {
min, max := MinMax(scale)
if d.GreaterThanOrEqualTo(min) && d.LessThanOrEqualTo(max) {
return true
}
return false
}
// IsValid returns true if the decimal does not break
// any assumption or resrictions on input.
func (d Decimal) IsValid() bool {
if d.Scale < -18 || d.Scale > 19 {
return false
}
return true
}
// ToInt64 returns d as an int64 adjusted to the
// provided scale.
func (d Decimal) ToInt64(scale int64) int64 {
@ -216,17 +43,13 @@ func (d Decimal) ToInt64(scale int64) int64 {
scaleDiff := scale - d.Scale
if scaleDiff == 0 {
ret = d.Value
} else if scaleDiff < 0 {
ret = d.Value / Pow10(-1*scaleDiff)
} else {
ret = d.Value * Pow10(scaleDiff)
ret = int64(float64(d.Value) * math.Pow10(int(scaleDiff)))
}
return ret
}
// Float64 returns d as a float64.
// TODO: this could potentially lose precision; we should audit
// its use and protect against unexpected results.
func (d Decimal) Float64() float64 {
var ret float64
if d.Scale == 0 {
@ -396,19 +219,15 @@ func ParseDecimal(s string) (Decimal, error) {
scale = 0
}
// We have to use ParseUint here (as opposed to ParseInt) because
// math.MinInt64 is a valid value, but its absolute value is not.
// So this allows us to handle that one value without overflow, and
// then we check for the uint bounds in the next step.
uvalue, err := strconv.ParseUint(string(mantissa), 10, 64)
value, err = strconv.ParseInt(string(mantissa), 10, 64)
if err != nil {
return Decimal{}, errors.Wrap(err, "converting mantissa string to uint64")
return Decimal{}, errors.Wrap(err, "converting mantissa to uint32")
}
if (sign && uvalue > -1*math.MinInt64) || (!sign && uvalue > math.MaxInt64) {
return Decimal{}, errors.New("value out of range")
// Because we pulled the sign off at the beginning, if value is
// negative here, it likely means the string had two "-"" characters.
if value < 0 {
return Decimal{}, errors.New("invalid negative value")
}
value = int64(uvalue)
if sign {
value *= -1
@ -420,22 +239,6 @@ func ParseDecimal(s string) (Decimal, error) {
}, nil
}
func quotient(d Decimal) int64 {
if d.Scale == 0 {
return d.Value
} else if d.Scale > 0 && d.Scale < 19 {
return d.Value / pow10[d.Scale]
}
return 0
}
func remainder(d Decimal) int64 {
if d.Scale >= 0 && d.Scale < 19 {
return d.Value % pow10[d.Scale]
}
return 0
}
// UnmarshalJSON is a custom unmarshaller for the Decimal
// type. The intention is to avoid the use of float64
// anywhere, so this unmarhaller parses the decimal out

View file

@ -15,8 +15,6 @@
package pql_test
import (
"encoding/json"
"reflect"
"strings"
"testing"
@ -61,7 +59,7 @@ func TestDecimal(t *testing.T) {
// int64 edges.
{".000009223372036854775807", pql.Decimal{9223372036854775807, 24}, ""},
{"-.000009223372036854775808", pql.Decimal{-9223372036854775808, 24}, ""},
{"-.000009223372036854775807", pql.Decimal{-9223372036854775807, 24}, ""},
{"92233720368547.75807", pql.Decimal{9223372036854775807, 5}, ""},
{"-92233720368547.75807", pql.Decimal{-9223372036854775807, 5}, ""},
{"9223372036854775807000", pql.Decimal{9223372036854775807, -3}, ""},
@ -73,12 +71,11 @@ func TestDecimal(t *testing.T) {
{"*0.123", pql.Decimal{}, "invalid syntax"},
{"abc", pql.Decimal{}, "invalid syntax"},
{"0.12.3", pql.Decimal{}, "invalid decimal string"},
{"--12300", pql.Decimal{}, "invalid syntax"},
{"922337203685477580.9", pql.Decimal{}, "value out of range"},
{"-922337203685477580.9", pql.Decimal{}, "value out of range"},
{"--12300", pql.Decimal{}, "invalid negative value"},
{"922337203685477580.8", pql.Decimal{}, "value out of range"},
{"-922337203685477580.8", pql.Decimal{}, "value out of range"},
{"9223372036854775808000", pql.Decimal{}, "value out of range"},
{"-9223372036854775809000", pql.Decimal{}, "value out of range"},
{"-9223372036854775808000", pql.Decimal{}, "value out of range"},
}
for i, test := range tests {
dec, err := pql.ParseDecimal(test.s)
@ -164,89 +161,4 @@ func TestDecimal(t *testing.T) {
}
}
})
t.Run("Comparisons", func(t *testing.T) {
tests := []struct {
d1 pql.Decimal
d2 pql.Decimal
expLT bool
expLTE bool
expGT bool
expGTE bool
expEQ bool
}{
{pql.NewDecimal(0, 0), pql.NewDecimal(0, 0), false, true, false, true, true},
{pql.NewDecimal(0, 0), pql.NewDecimal(10, 0), true, true, false, false, false},
{pql.NewDecimal(10, 0), pql.NewDecimal(0, 0), false, false, true, true, false},
{pql.NewDecimal(123456, 3), pql.NewDecimal(123456, 3), false, true, false, true, true},
{pql.NewDecimal(123456, 3), pql.NewDecimal(123456, 4), false, false, true, true, false},
{pql.NewDecimal(123456, 4), pql.NewDecimal(123456, 3), true, true, false, false, false},
{pql.NewDecimal(1233456, 4), pql.NewDecimal(123456, 3), true, true, false, false, false},
{pql.NewDecimal(0, 0), pql.NewDecimal(-10, 0), false, false, true, true, false},
{pql.NewDecimal(-10, 0), pql.NewDecimal(0, 0), true, true, false, false, false},
{pql.NewDecimal(-123456, 3), pql.NewDecimal(-123456, 3), false, true, false, true, true},
{pql.NewDecimal(-123456, 3), pql.NewDecimal(-123456, 4), true, true, false, false, false},
{pql.NewDecimal(-123456, 4), pql.NewDecimal(-123456, 3), false, false, true, true, false},
{pql.NewDecimal(-1233456, 4), pql.NewDecimal(-123456, 3), false, false, true, true, false},
{pql.NewDecimal(10, 0), pql.NewDecimal(-10, 0), false, false, true, true, false},
{pql.NewDecimal(-10, 0), pql.NewDecimal(10, 0), true, true, false, false, false},
{pql.NewDecimal(-123456, 3), pql.NewDecimal(123456, 3), true, true, false, false, false},
{pql.NewDecimal(123456, 3), pql.NewDecimal(-123456, 3), false, false, true, true, false},
{pql.NewDecimal(-123456, 3), pql.NewDecimal(123456, 4), true, true, false, false, false},
{pql.NewDecimal(123456, 3), pql.NewDecimal(-123456, 4), false, false, true, true, false},
{pql.NewDecimal(-123456, 4), pql.NewDecimal(123456, 3), true, true, false, false, false},
{pql.NewDecimal(123456, 4), pql.NewDecimal(-123456, 3), false, false, true, true, false},
{pql.NewDecimal(-1233456, 4), pql.NewDecimal(123456, 3), true, true, false, false, false},
{pql.NewDecimal(1233456, 4), pql.NewDecimal(-123456, 3), false, false, true, true, false},
{pql.NewDecimal(9223372036854775807, 0), pql.NewDecimal(9223372036854775807, 0), false, true, false, true, true},
{pql.NewDecimal(9223372036854775807, 2), pql.NewDecimal(9223372036854775807, 0), true, true, false, false, false},
{pql.NewDecimal(9223372036854775807, 19), pql.NewDecimal(9223372036854775807, 0), true, true, false, false, false},
{pql.NewDecimal(-9223372036854775808, 0), pql.NewDecimal(-9223372036854775808, 0), false, true, false, true, true},
{pql.NewDecimal(-9223372036854775808, 0), pql.NewDecimal(-9223372036854775807, 0), true, true, false, false, false},
{pql.NewDecimal(-9223372036854775808, 2), pql.NewDecimal(-9223372036854775808, 0), false, false, true, true, false},
{pql.NewDecimal(-9223372036854775808, 19), pql.NewDecimal(-9223372036854775807, 0), false, false, true, true, false},
}
for i, test := range tests {
if got := test.d1.LessThan(test.d2); got != test.expLT {
t.Fatalf("test LT %d expected %s < %s to be %v, but got: %v", i, test.d1, test.d2, test.expLT, got)
}
if got := test.d1.LessThanOrEqualTo(test.d2); got != test.expLTE {
t.Fatalf("test LTE %d expected %s <= %s to be %v, but got: %v", i, test.d1, test.d2, test.expLTE, got)
}
if got := test.d1.GreaterThan(test.d2); got != test.expGT {
t.Fatalf("test GT %d expected %s > %s to be %v, but got: %v", i, test.d1, test.d2, test.expGT, got)
}
if got := test.d1.GreaterThanOrEqualTo(test.d2); got != test.expGTE {
t.Fatalf("test GTE %d expected %s >= %s to be %v, but got: %v", i, test.d1, test.d2, test.expGTE, got)
}
if got := test.d1.EqualTo(test.d2); got != test.expEQ {
t.Fatalf("test EQ %d expected %s == %s to be %v, but got: %v", i, test.d1, test.d2, test.expEQ, got)
}
}
})
t.Run("JSON", func(t *testing.T) {
t.Run("Unmarshal", func(t *testing.T) {
tests := []struct {
json string
exp pql.Decimal
}{
{"1234.56", pql.NewDecimal(123456, 2)},
}
for i, test := range tests {
b := []byte(test.json)
dec := &pql.Decimal{}
if err := json.Unmarshal(b, &dec); err != nil {
panic(err)
}
if !reflect.DeepEqual(*dec, test.exp) {
t.Fatalf("test %d expected: %T, but got: %T", i, test.exp, dec)
}
}
})
})
}

View file

@ -34,7 +34,6 @@ import (
"github.com/pilosa/pilosa/v2/boltdb"
"github.com/pilosa/pilosa/v2/encoding/proto"
"github.com/pilosa/pilosa/v2/http"
"github.com/pilosa/pilosa/v2/pql"
"github.com/pilosa/pilosa/v2/server"
"github.com/pilosa/pilosa/v2/test"
)
@ -575,10 +574,10 @@ func TestHandler_Endpoints(t *testing.T) {
if field == nil {
t.Fatalf("field not found: %s", fieldName)
}
if !reflect.DeepEqual(pql.NewDecimal(math.MinInt64, 0), field.Options.Min) {
if math.MinInt64 != field.Options.Min {
t.Fatalf("field min %d != %d", int64(math.MinInt64), field.Options.Min)
}
if !reflect.DeepEqual(pql.NewDecimal(math.MaxInt64, 0), field.Options.Max) {
if math.MaxInt64 != field.Options.Max {
t.Fatalf("field max %d != %d", int64(math.MaxInt64), field.Options.Max)
}
})
@ -604,10 +603,10 @@ func TestHandler_Endpoints(t *testing.T) {
if field == nil {
t.Fatalf("field not found: %s", fieldName)
}
if !reflect.DeepEqual(pql.NewDecimal(math.MinInt64, 0), field.Options.Min) {
if math.MinInt64 != field.Options.Min {
t.Fatalf("field min %d != %d", int64(math.MinInt64), field.Options.Min)
}
if !reflect.DeepEqual(pql.NewDecimal(1, -1), field.Options.Max) {
if 10 != field.Options.Max {
t.Fatalf("field max %d != %d", 10, field.Options.Max)
}
})
@ -633,10 +632,10 @@ func TestHandler_Endpoints(t *testing.T) {
if field == nil {
t.Fatalf("field not found: %s", fieldName)
}
if !reflect.DeepEqual(pql.NewDecimal(-1, -1), field.Options.Min) {
if -10 != field.Options.Min {
t.Fatalf("field min %d != %d", 10, field.Options.Min)
}
if !reflect.DeepEqual(pql.NewDecimal(math.MaxInt64, 0), field.Options.Max) {
if math.MaxInt64 != field.Options.Max {
t.Fatalf("field max %d != %d", int64(math.MaxInt64), field.Options.Max)
}
})
@ -651,79 +650,6 @@ func TestHandler_Endpoints(t *testing.T) {
}
})
t.Run("Query decimal field unbounded", func(t *testing.T) {
w := httptest.NewRecorder()
fieldName := "f-decimal-ubound"
h.ServeHTTP(w, test.MustNewHTTPRequest("POST", fmt.Sprintf("/index/i0/field/%s", fieldName),
strings.NewReader(`{"options":{"type":"decimal", "scale": 0}}`)))
if w.Code != gohttp.StatusOK {
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 !reflect.DeepEqual(pql.NewDecimal(math.MinInt64, 0), field.Options.Min) {
t.Fatalf("field min %d != %d", int64(math.MinInt64), field.Options.Min)
}
if !reflect.DeepEqual(pql.NewDecimal(math.MaxInt64, 0), field.Options.Max) {
t.Fatalf("field max %d != %d", int64(math.MaxInt64), field.Options.Max)
}
})
t.Run("Query decimal field unbounded min", func(t *testing.T) {
w := httptest.NewRecorder()
fieldName := "f-decimal-ubound-min"
h.ServeHTTP(w, test.MustNewHTTPRequest("POST", fmt.Sprintf("/index/i0/field/%s", fieldName),
strings.NewReader(`{"options":{"type":"decimal", "scale": 1, "max": 10.5}}`)))
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 !reflect.DeepEqual(pql.NewDecimal(math.MinInt64, 1), field.Options.Min) {
t.Fatalf("field min %d != %d", pql.NewDecimal(math.MinInt64, 1), field.Options.Min)
}
if !reflect.DeepEqual(pql.NewDecimal(105, 1), field.Options.Max) {
t.Fatalf("field max %s != %d", pql.NewDecimal(105, 1), field.Options.Max)
}
})
// Ensure that decimal fields error when scale is not provided.
t.Run("Query decimal field scale error", func(t *testing.T) {
w := httptest.NewRecorder()
fieldName := "f-decimal-ubound"
h.ServeHTTP(w, test.MustNewHTTPRequest("POST", fmt.Sprintf("/index/i0/field/%s", fieldName),
strings.NewReader(`{"options":{"type":"decimal"}}`)))
expErr := "decimal field requires a scale argument"
if w.Code != gohttp.StatusBadRequest {
t.Fatalf("unexpected status code: %d", w.Code)
} else if !strings.Contains(w.Body.String(), expErr) {
t.Fatalf("expected error to contain: %s, but got: %s", expErr, w.Body.String())
}
})
t.Run("Method not allowed", func(t *testing.T) {
w := httptest.NewRecorder()
h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/index/i0/query", nil))

View file

@ -32,7 +32,6 @@ import (
"github.com/pelletier/go-toml"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/http"
"github.com/pilosa/pilosa/v2/pql"
"github.com/pilosa/pilosa/v2/roaring"
"github.com/pilosa/pilosa/v2/server"
"github.com/pilosa/pilosa/v2/test"
@ -308,7 +307,7 @@ func TestMain_MinMaxFloat(t *testing.T) {
if err := client.CreateIndex(context.Background(), "i", pilosa.IndexOptions{}); err != nil && err != pilosa.ErrIndexExists {
t.Fatal(err)
}
if err := client.CreateFieldWithOptions(context.Background(), "i", "dec", pilosa.FieldOptions{Type: pilosa.FieldTypeDecimal, Scale: 3, Max: pql.NewDecimal(100000, 0)}); err != nil {
if err := client.CreateFieldWithOptions(context.Background(), "i", "dec", pilosa.FieldOptions{Type: pilosa.FieldTypeDecimal, Scale: 3, Max: 100000}); err != nil {
t.Fatal(err)
}