mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-09-07 09:05:55 +00:00
Merge pull request #172 from travisturner/float-to-decimal
use pql.Decimal instead of float64
This commit is contained in:
commit
22ae1139d1
9 changed files with 2319 additions and 1418 deletions
|
|
@ -2926,7 +2926,7 @@ func (e *executor) executeSet(ctx context.Context, index string, c *pql.Call, op
|
|||
// Read row value.
|
||||
rowVal, err := getScaledInt(f, v)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("reading Set() row: %v", err)
|
||||
return false, fmt.Errorf("reading Set() row (int/decimal): %v", err)
|
||||
}
|
||||
return e.executeSetValueField(ctx, index, c, f, colID, rowVal, opt)
|
||||
|
||||
|
|
@ -4477,6 +4477,8 @@ func getScaledInt(f *Field, v interface{}) (int64, error) {
|
|||
value = int64(float64(tv) * math.Pow10(int(scale)))
|
||||
case uint64:
|
||||
value = int64(float64(tv) * math.Pow10(int(scale)))
|
||||
case pql.Decimal:
|
||||
value = tv.ToInt64(scale)
|
||||
case float64:
|
||||
value = int64(tv * math.Pow10(int(scale)))
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import (
|
|||
"github.com/pilosa/pilosa/v2"
|
||||
"github.com/pilosa/pilosa/v2/boltdb"
|
||||
"github.com/pilosa/pilosa/v2/http"
|
||||
"github.com/pilosa/pilosa/v2/pql"
|
||||
"github.com/pilosa/pilosa/v2/server"
|
||||
"github.com/pilosa/pilosa/v2/test"
|
||||
"github.com/pkg/errors"
|
||||
|
|
@ -1463,6 +1464,63 @@ func TestExecutor_Execute_MinMax(t *testing.T) {
|
|||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Decimal", func(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)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
scale int64
|
||||
min int64
|
||||
max int64
|
||||
set pql.Decimal
|
||||
}{
|
||||
{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)
|
||||
t.Run("MinMaxField_"+fld, func(t *testing.T) {
|
||||
if _, err := idx.CreateField(fld, pilosa.OptFieldTypeDecimal(test.scale, test.min, test.max)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: fmt.Sprintf(`
|
||||
Set(10, %s=%s)
|
||||
`, fld, test.set)}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var pql string
|
||||
|
||||
t.Run("Min", func(t *testing.T) {
|
||||
pql = fmt.Sprintf(`Min(field=%s)`, fld)
|
||||
if result, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: pql}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !reflect.DeepEqual(result.Results[0], pilosa.ValCount{FloatVal: test.set.Float64(), Count: 1}) {
|
||||
t.Fatalf("unexpected min result, test %d: %s", i, spew.Sdump(result))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Max", func(t *testing.T) {
|
||||
pql = fmt.Sprintf(`Max(field=%s)`, fld)
|
||||
if result, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: pql}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !reflect.DeepEqual(result.Results[0], pilosa.ValCount{FloatVal: test.set.Float64(), Count: 1}) {
|
||||
t.Fatalf("unexpected max result, test %d: %s", i, spew.Sdump(result))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("ColumnID", func(t *testing.T) {
|
||||
|
|
|
|||
16
field.go
16
field.go
|
|
@ -194,6 +194,10 @@ func OptFieldTypeDecimal(scale int64, minmax ...int64) FieldOption {
|
|||
fo.Max = math.MaxInt64
|
||||
if len(minmax) == 2 {
|
||||
min, max := minmax[0], minmax[1]
|
||||
if scale != 0 {
|
||||
min = int64(float64(min) * math.Pow10(int(scale)))
|
||||
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)
|
||||
}
|
||||
|
|
@ -202,7 +206,13 @@ func OptFieldTypeDecimal(scale int64, minmax ...int64) FieldOption {
|
|||
} else if len(minmax) > 2 {
|
||||
return errors.Errorf("unknown extra parameters beyond min and max: %v", minmax)
|
||||
} else if len(minmax) == 1 {
|
||||
fo.Min = minmax[0]
|
||||
// It's not necessary to handle the scale==0 case separately,
|
||||
// but it avoids the type conversion.
|
||||
if scale == 0 {
|
||||
fo.Min = minmax[0]
|
||||
} else {
|
||||
fo.Min = int64(float64(minmax[0]) * math.Pow10(int(scale)))
|
||||
}
|
||||
}
|
||||
fo.Type = FieldTypeDecimal
|
||||
fo.Base = bsiBase(fo.Min, fo.Max)
|
||||
|
|
@ -1447,7 +1457,7 @@ func (f *Field) MaxForShard(shard uint64, filter *Row) (ValCount, error) {
|
|||
valCount := ValCount{Count: int64(cnt)}
|
||||
|
||||
if f.Options().Type == FieldTypeDecimal {
|
||||
valCount.FloatVal = float64(max) / math.Pow10(int(bsig.Scale))
|
||||
valCount.FloatVal = float64(max+bsig.Base) / math.Pow10(int(bsig.Scale))
|
||||
} else {
|
||||
valCount.Val = max + bsig.Base
|
||||
}
|
||||
|
|
@ -1482,7 +1492,7 @@ func (f *Field) MinForShard(shard uint64, filter *Row) (ValCount, error) {
|
|||
valCount := ValCount{Count: int64(cnt)}
|
||||
|
||||
if f.Options().Type == FieldTypeDecimal {
|
||||
valCount.FloatVal = float64(min) / math.Pow10(int(bsig.Scale))
|
||||
valCount.FloatVal = float64(min+bsig.Base) / math.Pow10(int(bsig.Scale))
|
||||
} else {
|
||||
valCount.Val = min + bsig.Base
|
||||
}
|
||||
|
|
|
|||
18
pql/ast.go
18
pql/ast.go
|
|
@ -62,7 +62,7 @@ func (q *Query) lastCallStackElem() *callStackElem {
|
|||
|
||||
func (q *Query) addPosNum(key, value string) {
|
||||
q.addField(key)
|
||||
q.addNumVal(value)
|
||||
q.addNumVal(value, false)
|
||||
}
|
||||
|
||||
func (q *Query) addPosStr(key, value string) {
|
||||
|
|
@ -87,9 +87,9 @@ func (q *Query) endConditional() {
|
|||
if len(q.conditional) != 5 {
|
||||
panic(fmt.Sprintf("conditional of wrong length: %#v", q.conditional))
|
||||
}
|
||||
low := parseNum(q.conditional[0])
|
||||
low := parseNum(q.conditional[0], false)
|
||||
field := q.conditional[2]
|
||||
high := parseNum(q.conditional[4])
|
||||
high := parseNum(q.conditional[4], false)
|
||||
|
||||
var op Token
|
||||
switch q.conditional[1] + q.conditional[3] {
|
||||
|
|
@ -157,12 +157,12 @@ func (q *Query) addVal(val interface{}) {
|
|||
elem.lastCond = ILLEGAL
|
||||
}
|
||||
|
||||
func (q *Query) addNumVal(val string) {
|
||||
func (q *Query) addNumVal(val string, asFloat bool) {
|
||||
elem := q.lastCallStackElem()
|
||||
if elem == nil || elem.lastField == "" {
|
||||
panic(fmt.Sprintf("addIntVal called with '%s' when lastField is empty", val))
|
||||
}
|
||||
ival := parseNum(val)
|
||||
ival := parseNum(val, asFloat)
|
||||
if elem.inList {
|
||||
if elem.lastCond != ILLEGAL {
|
||||
list := elem.call.Args[elem.lastField].(*Condition).Value.([]interface{})
|
||||
|
|
@ -968,11 +968,15 @@ func joinUint64Slice(a []uint64) string {
|
|||
return "[" + strings.Join(other, ",") + "]"
|
||||
}
|
||||
|
||||
func parseNum(val string) interface{} {
|
||||
func parseNum(val string, asFloat bool) interface{} {
|
||||
var ival interface{}
|
||||
var err error
|
||||
if strings.Contains(val, ".") {
|
||||
ival, err = strconv.ParseFloat(val, 64)
|
||||
if asFloat {
|
||||
ival, err = strconv.ParseFloat(val, 64)
|
||||
} else {
|
||||
ival, err = ParseDecimal(val)
|
||||
}
|
||||
} else {
|
||||
ival, err = strconv.ParseInt(val, 10, 64)
|
||||
}
|
||||
|
|
|
|||
240
pql/decimal.go
Normal file
240
pql/decimal.go
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
// Copyright 2017 Pilosa Corp.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// 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
|
||||
// values used in query strings.
|
||||
// Scale is the number of digits to the right of the
|
||||
// decimal point.
|
||||
// Precision is currently not considered; precision, for
|
||||
// our purposes is implied to be the complete, known value.
|
||||
type Decimal struct {
|
||||
Value int64
|
||||
Scale int64
|
||||
}
|
||||
|
||||
// ToInt64 returns d as an int64 adjusted to the
|
||||
// provided scale.
|
||||
func (d Decimal) ToInt64(scale int64) int64 {
|
||||
var ret int64
|
||||
scaleDiff := scale - d.Scale
|
||||
if scaleDiff == 0 {
|
||||
ret = d.Value
|
||||
} else {
|
||||
ret = int64(float64(d.Value) * math.Pow10(int(scaleDiff)))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Float64 returns d as a float64.
|
||||
func (d Decimal) Float64() float64 {
|
||||
var ret float64
|
||||
if d.Scale == 0 {
|
||||
ret = float64(d.Value)
|
||||
} else {
|
||||
ret = float64(d.Value) / math.Pow10(int(d.Scale))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// String returns the string representation of the decimal.
|
||||
func (d Decimal) String() string {
|
||||
var s string
|
||||
|
||||
var neg bool
|
||||
sval := fmt.Sprintf("%d", d.Value)
|
||||
|
||||
// Strip the negative sign off for now, and
|
||||
// re-apply it at the end.
|
||||
if sval[0] == '-' {
|
||||
neg = true
|
||||
sval = sval[1:]
|
||||
}
|
||||
|
||||
if d.Scale == 0 {
|
||||
s = sval
|
||||
} else if d.Scale < 0 {
|
||||
s = sval + strings.Repeat("0", int(-1*d.Scale))
|
||||
} else {
|
||||
var bufLen int
|
||||
if int(d.Scale) < len(sval) {
|
||||
bufLen = len(sval) + 1
|
||||
} else {
|
||||
bufLen = int(d.Scale) + 2
|
||||
}
|
||||
|
||||
buf := make([]byte, bufLen)
|
||||
j := 0
|
||||
for i := range buf {
|
||||
z := len(buf) - 1 - i // index into buf from the end
|
||||
if i == int(d.Scale) {
|
||||
buf[z] = '.'
|
||||
continue
|
||||
}
|
||||
if len(sval) > j {
|
||||
buf[z] = sval[len(sval)-1-j]
|
||||
j++
|
||||
} else {
|
||||
buf[z] = '0'
|
||||
}
|
||||
}
|
||||
s = string(buf)
|
||||
}
|
||||
|
||||
if neg {
|
||||
return "-" + s
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
const (
|
||||
stateSign = "sign"
|
||||
stateLeadingZeros = "zeros"
|
||||
stateMantissa = "mantissa"
|
||||
)
|
||||
|
||||
// ParseDecimal parses a string into a Decimal.
|
||||
func ParseDecimal(s string) (Decimal, error) {
|
||||
var sign bool
|
||||
var value int64
|
||||
var scale int64
|
||||
var err error
|
||||
|
||||
// General steps:
|
||||
// - Trim leading whitespace/zeros
|
||||
// - Get the sign value
|
||||
// - Trim leading zeros
|
||||
// - Push characters into a buffer
|
||||
// - Track position of decimal point
|
||||
// - Trim trailing zeros of buffer
|
||||
// - value = buffer -> int
|
||||
// - scale = len(buffer) - tracked position
|
||||
|
||||
var decimalPos int = -1
|
||||
var pos int
|
||||
mantissa := make([]byte, len(s))
|
||||
|
||||
state := stateSign
|
||||
var foundLeadingZero bool
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch state {
|
||||
case stateSign:
|
||||
switch s[i] {
|
||||
case ' ':
|
||||
continue
|
||||
case '-':
|
||||
sign = true
|
||||
fallthrough
|
||||
case '+':
|
||||
state = stateLeadingZeros
|
||||
default:
|
||||
state = stateLeadingZeros
|
||||
i--
|
||||
}
|
||||
case stateLeadingZeros:
|
||||
switch s[i] {
|
||||
case '0':
|
||||
foundLeadingZero = true
|
||||
continue
|
||||
default:
|
||||
state = stateMantissa
|
||||
i--
|
||||
}
|
||||
case stateMantissa:
|
||||
switch s[i] {
|
||||
case '.':
|
||||
if decimalPos == -1 {
|
||||
decimalPos = pos
|
||||
} else {
|
||||
return Decimal{}, errors.Errorf("invalid decimal string: %s", s)
|
||||
}
|
||||
continue
|
||||
default:
|
||||
mantissa[pos] = s[i]
|
||||
pos++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we've gotten here and state is still in stateSign or
|
||||
// it's in stateLeadingZeros without finding any zeros,
|
||||
// it means no value was provided.
|
||||
if state == stateSign || (state == stateLeadingZeros && !foundLeadingZero) {
|
||||
return Decimal{}, errors.New("decimal string is empty")
|
||||
}
|
||||
|
||||
// Trim trailing zeros/spaces of mantissa
|
||||
// for any portion that would have been to the
|
||||
// right of the decimal.
|
||||
trimZeroCnt := 0
|
||||
trimSpaceCnt := 0
|
||||
for i := len(mantissa) - 1; i >= 0; i-- {
|
||||
switch mantissa[i] {
|
||||
case uint8(0), uint8(32): // nil/space
|
||||
trimSpaceCnt++
|
||||
continue
|
||||
case uint8(48): // zero
|
||||
trimZeroCnt++
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
mantissa = mantissa[:len(mantissa)-trimSpaceCnt-trimZeroCnt]
|
||||
|
||||
// Based on where (or if) the decimal was found,
|
||||
// calculate scale.
|
||||
if decimalPos == -1 {
|
||||
scale = -1 * int64(trimZeroCnt)
|
||||
} else {
|
||||
scale = int64(len(mantissa) - decimalPos)
|
||||
}
|
||||
|
||||
// If mantissa is empty, treat it as "0".
|
||||
if len(mantissa) == 0 {
|
||||
mantissa = []byte{'0'}
|
||||
sign = false
|
||||
scale = 0
|
||||
}
|
||||
|
||||
value, err = strconv.ParseInt(string(mantissa), 10, 64)
|
||||
if err != nil {
|
||||
return Decimal{}, errors.Wrap(err, "converting mantissa to uint32")
|
||||
}
|
||||
// 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")
|
||||
}
|
||||
|
||||
if sign {
|
||||
value *= -1
|
||||
}
|
||||
|
||||
return Decimal{
|
||||
Value: value,
|
||||
Scale: scale,
|
||||
}, nil
|
||||
}
|
||||
164
pql/decimal_test.go
Normal file
164
pql/decimal_test.go
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
// Copyright 2017 Pilosa Corp.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pql_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pilosa/pilosa/v2/pql"
|
||||
)
|
||||
|
||||
// Ensure call can be converted into a string.
|
||||
func TestDecimal(t *testing.T) {
|
||||
t.Run("Parse", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
s string
|
||||
exp pql.Decimal
|
||||
expErr string
|
||||
}{
|
||||
{"0", pql.Decimal{0, 0}, ""},
|
||||
{"-0", pql.Decimal{0, 0}, ""},
|
||||
{"0.0", pql.Decimal{0, 0}, ""},
|
||||
{"-0.00", pql.Decimal{0, 0}, ""},
|
||||
{"123.4567", pql.Decimal{1234567, 4}, ""},
|
||||
{" 123.4567", pql.Decimal{1234567, 4}, ""},
|
||||
{" 123.4567 ", pql.Decimal{1234567, 4}, ""},
|
||||
{"123.456700", pql.Decimal{1234567, 4}, ""},
|
||||
{"00123.4567", pql.Decimal{1234567, 4}, ""},
|
||||
{"+123.4567", pql.Decimal{1234567, 4}, ""},
|
||||
{"-123.4567", pql.Decimal{-1234567, 4}, ""},
|
||||
{"-00123.4567", pql.Decimal{-1234567, 4}, ""},
|
||||
{"-12.25", pql.Decimal{-1225, 2}, ""},
|
||||
|
||||
{"123", pql.Decimal{123, 0}, ""},
|
||||
{"-12300", pql.Decimal{-123, -2}, ""},
|
||||
{"+012300", pql.Decimal{123, -2}, ""},
|
||||
{"12300", pql.Decimal{123, -2}, ""},
|
||||
{"12300.", pql.Decimal{123, -2}, ""},
|
||||
{"12300.0", pql.Decimal{123, -2}, ""},
|
||||
{"123.0", pql.Decimal{123, 0}, ""},
|
||||
|
||||
{".123", pql.Decimal{123, 3}, ""},
|
||||
{"0.123", pql.Decimal{123, 3}, ""},
|
||||
{"0.001230", pql.Decimal{123, 5}, ""},
|
||||
{" 0.001230 ", pql.Decimal{123, 5}, ""},
|
||||
{"-0.001230 ", pql.Decimal{-123, 5}, ""},
|
||||
|
||||
// int64 edges.
|
||||
{".000009223372036854775807", pql.Decimal{9223372036854775807, 24}, ""},
|
||||
{"-.000009223372036854775807", pql.Decimal{-9223372036854775807, 24}, ""},
|
||||
{"92233720368547.75807", pql.Decimal{9223372036854775807, 5}, ""},
|
||||
{"-92233720368547.75807", pql.Decimal{-9223372036854775807, 5}, ""},
|
||||
{"9223372036854775807000", pql.Decimal{9223372036854775807, -3}, ""},
|
||||
{"-9223372036854775807000", pql.Decimal{-9223372036854775807, -3}, ""},
|
||||
|
||||
// Error cases.
|
||||
{"", pql.Decimal{}, "decimal string is empty"},
|
||||
{"-", pql.Decimal{}, "decimal string is empty"},
|
||||
{"*0.123", pql.Decimal{}, "invalid syntax"},
|
||||
{"abc", pql.Decimal{}, "invalid syntax"},
|
||||
{"0.12.3", pql.Decimal{}, "invalid decimal string"},
|
||||
{"--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"},
|
||||
{"-9223372036854775808000", pql.Decimal{}, "value out of range"},
|
||||
}
|
||||
for i, test := range tests {
|
||||
dec, err := pql.ParseDecimal(test.s)
|
||||
if test.expErr != "" {
|
||||
if err == nil || !strings.Contains(err.Error(), test.expErr) {
|
||||
t.Fatalf("test %d expected error to contain: %s, but got: %v", i, test.expErr, err)
|
||||
}
|
||||
} else if err != nil {
|
||||
t.Fatalf("test %d parsing string `%s`: %s", i, test.s, err)
|
||||
} else if dec != test.exp {
|
||||
t.Fatalf("test %d expected: %v, but got: %v", i, test.exp, dec)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ToInt64", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
dec pql.Decimal
|
||||
scale int64
|
||||
exp int64
|
||||
}{
|
||||
{pql.Decimal{0, 0}, 0, 0}, // 0 : 0
|
||||
{pql.Decimal{0, 0}, 1, 0}, // 0 : 0.0
|
||||
{pql.Decimal{0, 0}, -1, 0}, // 0 : 0
|
||||
|
||||
{pql.Decimal{1234567, 4}, 5, 12345670}, // 123.4567 : 123.45670
|
||||
{pql.Decimal{1234567, 4}, 4, 1234567}, // 123.4567 : 123.4567
|
||||
{pql.Decimal{1234567, 4}, 3, 123456}, // 123.4567 : 123.456
|
||||
|
||||
{pql.Decimal{-1234567, 4}, 5, -12345670}, // -123.4567 : -123.45670
|
||||
{pql.Decimal{-1234567, 4}, 4, -1234567}, // -123.4567 : -123.4567
|
||||
{pql.Decimal{-1234567, 4}, 3, -123456}, // -123.4567 : -123.456
|
||||
|
||||
{pql.Decimal{123, -2}, 5, 1230000000}, // 12300 : 12300.00000
|
||||
{pql.Decimal{123, -2}, -1, 1230}, // 12300 : 1230
|
||||
{pql.Decimal{123, 1}, -1, 1}, // 12.3 : 1
|
||||
{pql.Decimal{123, 1}, -2, 0}, // 12.3 : 0
|
||||
}
|
||||
for i, test := range tests {
|
||||
v := test.dec.ToInt64(test.scale)
|
||||
if v != test.exp {
|
||||
t.Fatalf("test %d expected: %d, but got: %d", i, test.exp, v)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("String", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
s string
|
||||
exp string
|
||||
}{
|
||||
{"123.4567", "123.4567"},
|
||||
{" 123.4567", "123.4567"},
|
||||
{" 123.4567 ", "123.4567"},
|
||||
{"123.456700", "123.4567"},
|
||||
{"00123.4567", "123.4567"},
|
||||
{"+123.4567", "123.4567"},
|
||||
{"-123.4567", "-123.4567"},
|
||||
{"-00123.4567", "-123.4567"},
|
||||
{"-12.25", "-12.25"},
|
||||
|
||||
{"123", "123"},
|
||||
{"-12300", "-12300"},
|
||||
{"+012300", "12300"},
|
||||
{"12300", "12300"},
|
||||
|
||||
{"12300.", "12300"},
|
||||
{"12300.0", "12300"},
|
||||
{"123.0", "123"},
|
||||
|
||||
{"0.123", "0.123"},
|
||||
{"0.001230", "0.00123"},
|
||||
{" 0.001230 ", "0.00123"},
|
||||
{"-0.001230 ", "-0.00123"},
|
||||
}
|
||||
for i, test := range tests {
|
||||
dec, err := pql.ParseDecimal(test.s)
|
||||
if err != nil {
|
||||
t.Fatalf("parsing string `%s`: %s", test.s, err)
|
||||
}
|
||||
if str := dec.String(); str != test.exp {
|
||||
t.Fatalf("test %d expected: %s, but got: %s", i, test.exp, str)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -96,8 +96,8 @@ func TestParser_Parse(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
// Parse with float arguments.
|
||||
t.Run("WithFloatArgs", func(t *testing.T) {
|
||||
// Parse with decimal arguments.
|
||||
t.Run("WithDecimalArgs", func(t *testing.T) {
|
||||
q, err := pql.ParseString(`Row( key=12.25, foo= 13.167, bar=2., baz=0.9)`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -105,10 +105,10 @@ func TestParser_Parse(t *testing.T) {
|
|||
&pql.Call{
|
||||
Name: "Row",
|
||||
Args: map[string]interface{}{
|
||||
"key": 12.25,
|
||||
"foo": 13.167,
|
||||
"bar": 2.,
|
||||
"baz": 0.9,
|
||||
"key": pql.Decimal{1225, 2},
|
||||
"foo": pql.Decimal{13167, 3},
|
||||
"bar": pql.Decimal{2, 0},
|
||||
"baz": pql.Decimal{9, 1},
|
||||
},
|
||||
},
|
||||
) {
|
||||
|
|
@ -125,7 +125,7 @@ func TestParser_Parse(t *testing.T) {
|
|||
&pql.Call{
|
||||
Name: "Row",
|
||||
Args: map[string]interface{}{
|
||||
"key": -12.25,
|
||||
"key": pql.Decimal{-1225, 2},
|
||||
"foo": int64(-13),
|
||||
},
|
||||
},
|
||||
|
|
@ -181,7 +181,7 @@ func TestParser_Parse(t *testing.T) {
|
|||
Name: "Row",
|
||||
Args: map[string]interface{}{
|
||||
"key": "foo",
|
||||
"x": &pql.Condition{Op: pql.EQ, Value: 12.25},
|
||||
"x": &pql.Condition{Op: pql.EQ, Value: pql.Decimal{1225, 2}},
|
||||
"y": &pql.Condition{Op: pql.GTE, Value: int64(100)},
|
||||
"z": &pql.Condition{Op: pql.BETWEEN, Value: []interface{}{int64(4), int64(8)}},
|
||||
"m": &pql.Condition{Op: pql.NEQ, Value: nil},
|
||||
|
|
@ -191,5 +191,4 @@ func TestParser_Parse(t *testing.T) {
|
|||
t.Fatalf("unexpected call: %#v", q.Calls[0])
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
|||
62
pql/pql.peg
62
pql/pql.peg
|
|
@ -6,22 +6,27 @@ type PQL Peg {
|
|||
|
||||
|
||||
Calls <- sp (Call sp)* !.
|
||||
Call <- 'Set' {p.startCall("Set")} open col comma args (comma timestamp)? close {p.endCall()}
|
||||
/ 'SetRowAttrs' {p.startCall("SetRowAttrs")} open posfield comma row comma args close {p.endCall()}
|
||||
/ 'SetColumnAttrs' {p.startCall("SetColumnAttrs")} open col comma args close {p.endCall()}
|
||||
/ 'Clear' {p.startCall("Clear")} open col comma args close {p.endCall()}
|
||||
/ 'ClearRow' {p.startCall("ClearRow")} open arg close {p.endCall()}
|
||||
/ 'Store' {p.startCall("Store")} open Call comma arg close {p.endCall()}
|
||||
Call <- 'Set' {p.startCall("Set")} open col comma dargs (comma timestamp)? close {p.endCall()}
|
||||
/ 'SetRowAttrs' {p.startCall("SetRowAttrs")} open posfield comma row comma fargs close {p.endCall()}
|
||||
/ 'SetColumnAttrs' {p.startCall("SetColumnAttrs")} open col comma fargs close {p.endCall()}
|
||||
/ 'Clear' {p.startCall("Clear")} open col comma dargs close {p.endCall()}
|
||||
/ 'ClearRow' {p.startCall("ClearRow")} open darg close {p.endCall()}
|
||||
/ 'Store' {p.startCall("Store")} open Call comma darg close {p.endCall()}
|
||||
/ 'TopN' {p.startCall("TopN")} open posfield (comma allargs)? close {p.endCall()}
|
||||
/ 'Rows' {p.startCall("Rows")} open posfield (comma allargs)? close {p.endCall()}
|
||||
/ 'Range' {p.startCall("Range")} open field sp '=' sp value comma 'from='? {p.addField("from")} timestampfmt {p.addVal(buffer[begin:end])} comma 'to='? sp {p.addField("to")} timestampfmt {p.addVal(buffer[begin:end])} close {p.endCall()}
|
||||
/ 'Range' {p.startCall("Range")} open field sp '=' sp fvalue comma 'from='? {p.addField("from")} timestampfmt {p.addVal(buffer[begin:end])} comma 'to='? sp {p.addField("to")} timestampfmt {p.addVal(buffer[begin:end])} close {p.endCall()}
|
||||
/ < IDENT > { p.startCall(buffer[begin:end] ) } open allargs comma? close { p.endCall() }
|
||||
allargs <- Call (comma Call)* (comma args)? / args / sp
|
||||
args <- arg (comma args)? sp
|
||||
arg <- ( field sp '=' sp value
|
||||
/ field sp COND sp value
|
||||
/ conditional
|
||||
)
|
||||
allargs <- Call (comma Call)* (comma dargs)? / dargs / sp
|
||||
fargs <- farg (comma fargs)? sp
|
||||
farg <- ( field sp '=' sp fvalue
|
||||
/ field sp COND sp fvalue
|
||||
/ conditional
|
||||
)
|
||||
dargs <- darg (comma dargs)? sp
|
||||
darg <- ( field sp '=' sp dvalue
|
||||
/ field sp COND sp dvalue
|
||||
/ conditional
|
||||
)
|
||||
COND <- ( '><' { p.addBTWN() }
|
||||
/ '<=' { p.addLTE() }
|
||||
/ '>=' { p.addGTE() }
|
||||
|
|
@ -36,21 +41,38 @@ condint <- < '-'? [0-9]* '.' [0-9]+ / '0' / '-'? [1-9] [0-9]* > sp {p.condAdd(bu
|
|||
condLT <- <('<=' / '<')> sp {p.condAdd(buffer[begin:end])}
|
||||
condfield <- <fieldExpr> sp {p.condAdd(buffer[begin:end])}
|
||||
|
||||
value <- ( item
|
||||
/ lbrack { p.startList() } list rbrack { p.endList() }
|
||||
dvalue <- ( ditem
|
||||
/ lbrack { p.startList() } dlist rbrack { p.endList() }
|
||||
)
|
||||
list <- item (comma list)?
|
||||
item <- ( 'null' &(comma / sp close) { p.addVal(nil) }
|
||||
fvalue <- ( fitem
|
||||
/ lbrack { p.startList() } flist rbrack { p.endList() }
|
||||
)
|
||||
dlist <- ditem (comma dlist)?
|
||||
flist <- fitem (comma flist)?
|
||||
ditem <- ( itema
|
||||
/ decimal
|
||||
/ itemb
|
||||
)
|
||||
fitem <- ( itema
|
||||
/ float
|
||||
/ itemb
|
||||
)
|
||||
itema <- ( 'null' &(comma / sp close) { p.addVal(nil) }
|
||||
/ 'true' &(comma / sp close) { p.addVal(true) }
|
||||
/ 'false' &(comma / sp close) { p.addVal(false) }
|
||||
/ timestampfmt { p.addVal(buffer[begin:end]) }
|
||||
/ < '-'? [0-9]+ ('.'[0-9]*)? > { p.addNumVal(buffer[begin:end]) }
|
||||
/ < '-'? '.'[0-9]+ > { p.addNumVal(buffer[begin:end]) }
|
||||
/ < IDENT > { p.startCall(buffer[begin:end]) } open allargs comma? close { p.addVal(p.endCall()) }
|
||||
)
|
||||
itemb <- ( < IDENT > { p.startCall(buffer[begin:end]) } open allargs comma? close { p.addVal(p.endCall()) }
|
||||
/ < ([[A-Z]] / [0-9] / '-' / '_' / ':')+ > { p.addVal(buffer[begin:end]) }
|
||||
/ < '"' doublequotedstring '"' > { s, _ := strconv.Unquote(buffer[begin:end]); p.addVal(s) }
|
||||
/ '\'' < singlequotedstring > '\'' { p.addVal(buffer[begin:end]) }
|
||||
)
|
||||
float <- ( < '-'? [0-9]+ ('.'[0-9]*)? > { p.addNumVal(buffer[begin:end], true) }
|
||||
/ < '-'? '.'[0-9]+ > { p.addNumVal(buffer[begin:end], true) }
|
||||
)
|
||||
decimal <- ( < '-'? [0-9]+ ('.'[0-9]*)? > { p.addNumVal(buffer[begin:end], false) }
|
||||
/ < '-'? '.'[0-9]+ > { p.addNumVal(buffer[begin:end], false) }
|
||||
)
|
||||
|
||||
doublequotedstring <- ( '\\"' / '\\\\' / [^"] )*
|
||||
singlequotedstring <- ( '\\\'' / '\\\\' / [^'] )*
|
||||
|
|
|
|||
3158
pql/pql.peg.go
3158
pql/pql.peg.go
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue