Support uint64 literals in the parser

When an integer cannot be parsed, we attempt to parse it as a uint64. If
that succeeds, we then have an unsigned literal that can then be used to
compare unsigned values.

This method allows us not to introduce new syntax to the language and
continue just doing the right thing at the right time. But, it also
delegates a lot of the heavy lifting to implicit casting in Reduce and
Eval.
pull/8838/head
Jonathan A. Sternberg 2017-09-14 11:16:40 -05:00
parent 253b8c2b35
commit a978b4fd3e
3 changed files with 30 additions and 0 deletions

View File

@ -179,6 +179,7 @@ func (*Dimension) node() {}
func (Dimensions) node() {}
func (*DurationLiteral) node() {}
func (*IntegerLiteral) node() {}
func (*UnsignedLiteral) node() {}
func (*Field) node() {}
func (Fields) node() {}
func (*Measurement) node() {}
@ -312,6 +313,7 @@ func (*Call) expr() {}
func (*Distinct) expr() {}
func (*DurationLiteral) expr() {}
func (*IntegerLiteral) expr() {}
func (*UnsignedLiteral) expr() {}
func (*NilLiteral) expr() {}
func (*NumberLiteral) expr() {}
func (*ParenExpr) expr() {}
@ -333,6 +335,7 @@ type Literal interface {
func (*BooleanLiteral) literal() {}
func (*DurationLiteral) literal() {}
func (*IntegerLiteral) literal() {}
func (*UnsignedLiteral) literal() {}
func (*NilLiteral) literal() {}
func (*NumberLiteral) literal() {}
func (*RegexLiteral) literal() {}
@ -3273,6 +3276,15 @@ type IntegerLiteral struct {
// String returns a string representation of the literal.
func (l *IntegerLiteral) String() string { return fmt.Sprintf("%d", l.Val) }
// UnsignedLiteral represents an unsigned literal. The parser will only use an unsigned literal if the parsed
// integer is greater than math.MaxInt64.
type UnsignedLiteral struct {
Val uint64
}
// String returns a string representation of the literal.
func (l *UnsignedLiteral) String() string { return strconv.FormatUint(l.Val, 10) }
// BooleanLiteral represents a boolean literal.
type BooleanLiteral struct {
Val bool

View File

@ -2601,6 +2601,11 @@ func (p *Parser) parseUnaryExpr() (Expr, error) {
case INTEGER:
v, err := strconv.ParseInt(lit, 10, 64)
if err != nil {
// The literal may be too large to fit into an int64. If it is, use an unsigned integer.
// The check for negative numbers is handled somewhere else so this should always be a positive number.
if v, err := strconv.ParseUint(lit, 10, 64); err == nil {
return &UnsignedLiteral{Val: v}, nil
}
return nil, &ParseError{Message: "unable to parse integer", Pos: pos}
}
return &IntegerLiteral{Val: v}, nil
@ -2677,6 +2682,16 @@ func (p *Parser) parseUnaryExpr() (Expr, error) {
lit.Val *= float64(mul)
case *IntegerLiteral:
lit.Val *= int64(mul)
case *UnsignedLiteral:
if tok == SUB {
// Because of twos-complement integers and the method we parse, math.MinInt64 will be parsed
// as an UnsignedLiteral because it overflows an int64, but it fits into int64 if it were parsed
// as a negative number instead.
if lit.Val == uint64(math.MaxInt64+1) {
return &IntegerLiteral{Val: int64(-lit.Val)}, nil
}
return nil, fmt.Errorf("constant -%d underflows int64", lit.Val)
}
case *DurationLiteral:
lit.Val *= time.Duration(mul)
case *VarRef, *Call, *ParenExpr:

View File

@ -3069,6 +3069,9 @@ func TestParser_ParseExpr(t *testing.T) {
// Primitives
{s: `100.0`, expr: &influxql.NumberLiteral{Val: 100}},
{s: `100`, expr: &influxql.IntegerLiteral{Val: 100}},
{s: `9223372036854775808`, expr: &influxql.UnsignedLiteral{Val: 9223372036854775808}},
{s: `-9223372036854775808`, expr: &influxql.IntegerLiteral{Val: -9223372036854775808}},
{s: `-9223372036854775809`, err: `constant -9223372036854775809 underflows int64`},
{s: `-100.0`, expr: &influxql.NumberLiteral{Val: -100}},
{s: `-100`, expr: &influxql.IntegerLiteral{Val: -100}},
{s: `100.`, expr: &influxql.NumberLiteral{Val: 100}},