From a978b4fd3ea0931390c6e883d0cdc703f2f982ce Mon Sep 17 00:00:00 2001 From: "Jonathan A. Sternberg" Date: Thu, 14 Sep 2017 11:16:40 -0500 Subject: [PATCH] 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. --- influxql/ast.go | 12 ++++++++++++ influxql/parser.go | 15 +++++++++++++++ influxql/parser_test.go | 3 +++ 3 files changed, 30 insertions(+) diff --git a/influxql/ast.go b/influxql/ast.go index 4e189365ef..ce743f1bc0 100644 --- a/influxql/ast.go +++ b/influxql/ast.go @@ -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 diff --git a/influxql/parser.go b/influxql/parser.go index d33e5bbaba..9dbf309c46 100644 --- a/influxql/parser.go +++ b/influxql/parser.go @@ -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: diff --git a/influxql/parser_test.go b/influxql/parser_test.go index 240299d163..d294b27328 100644 --- a/influxql/parser_test.go +++ b/influxql/parser_test.go @@ -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}},