Parse duration literals

pull/10616/head
Christopher Wolff 2018-08-23 10:00:26 -07:00 committed by Christopher M. Wolff
parent 2717f9396a
commit a58c9a63f8
9 changed files with 3410 additions and 6098 deletions

View File

@ -163,22 +163,23 @@ Examples:
A duration literal is a representation of a length of time. A duration literal is a representation of a length of time.
It has an integer part and a duration unit part. It has an integer part and a duration unit part.
Multiple durations may be specified together and the resulting duration is the sum of each smaller part. Multiple durations may be specified together and the resulting duration is the sum of each smaller part.
When several durations are specified together, larger units must appear before smaller ones, and there can be no repeated units.
duration_lit = { int_lit duration_unit } . duration_lit = { int_lit duration_unit } .
duration_unit = "ns" | "us" | "µs" | "ms" | "s" | "m" | "h" | "d" | "w" | "mo" | "y" . duration_unit = "y" | "mo" | "w" | "d" | "h" | "m" | "s" | "ms" | "us" | "µs" | "ns" .
| Units | Meaning | | Units | Meaning |
| ----- | ------- | | ----- | ------- |
| ns | nanoseconds (1 billionth of a second) |
| us or µs | microseconds (1 millionth of a second) |
| ms | milliseconds (1 thousandth of a second) |
| s | second |
| m | minute (60 seconds) |
| h | hour (60 minutes) |
| d | day |
| w | week (7 days) |
| mo | month |
| y | year (12 months) | | y | year (12 months) |
| mo | month |
| w | week (7 days) |
| d | day |
| h | hour (60 minutes) |
| m | minute (60 seconds) |
| s | second |
| ms | milliseconds (1 thousandth of a second) |
| us or µs | microseconds (1 millionth of a second) |
| ns | nanoseconds (1 billionth of a second) |
Durations represent a length of time. Durations represent a length of time.
Lengths of time are dependent on specific instants in time they occur and as such, durations do not represent a fixed amount of time. Lengths of time are dependent on specific instants in time they occur and as such, durations do not represent a fixed amount of time.

File diff suppressed because it is too large Load Diff

View File

@ -344,11 +344,136 @@ DateTimeLiteral
return datetime(c.text, c.pos) return datetime(c.text, c.pos)
} }
// The order of the choices in this rule and those below matters.
// Minutes (m) must appear before milliseconds (ms) and months (mo),
// otherwise "10ms" will be parsed as 10 minutes and an extraneous "s".
DurationLiteral
= durations:(
DayDuration
/ HourDuration
/ MicroSecondDuration
/ MilliSecondDuration
/ MonthDuration
/ MinuteDuration
/ NanoSecondDuration
/ SecondDuration
/ WeekDuration
/ YearDuration
) {
return durationLiteral(durations, c.text, c.pos)
}
YearDuration
= mag:IntegerLiteral unit:YearUnits otherParts:(
DayDuration
/ HourDuration
/ MicroSecondDuration
/ MilliSecondDuration
/ MonthDuration
/ MinuteDuration
/ NanoSecondDuration
/ SecondDuration
/ WeekDuration
)? {
return appendSingleDurations(mag, unit, otherParts, c.text, c.pos)
}
MonthDuration
= mag:IntegerLiteral unit:MonthUnits otherParts:(
DayDuration
/ HourDuration
/ MicroSecondDuration
/ MilliSecondDuration
/ MinuteDuration
/ NanoSecondDuration
/ SecondDuration
/ WeekDuration
)? {
return appendSingleDurations(mag, unit, otherParts, c.text, c.pos)
}
WeekDuration
= mag:IntegerLiteral unit:WeekUnits otherParts:(
DayDuration
/ HourDuration
/ MicroSecondDuration
/ MilliSecondDuration
/ MinuteDuration
/ NanoSecondDuration
/ SecondDuration
)? {
return appendSingleDurations(mag, unit, otherParts, c.text, c.pos)
}
DayDuration
= mag:IntegerLiteral unit:DayUnits otherParts:(
HourDuration
/ MicroSecondDuration
/ MilliSecondDuration
/ MinuteDuration
/ NanoSecondDuration
/ SecondDuration
)? {
return appendSingleDurations(mag, unit, otherParts, c.text, c.pos)
}
HourDuration
= mag:IntegerLiteral unit:HourUnits otherParts:(
MicroSecondDuration
/ MilliSecondDuration
/ MinuteDuration
/ NanoSecondDuration
/ SecondDuration
)? {
return appendSingleDurations(mag, unit, otherParts, c.text, c.pos)
}
MinuteDuration
= mag:IntegerLiteral unit:MinuteUnits otherParts:(
MicroSecondDuration
/ MilliSecondDuration
/ NanoSecondDuration
/ SecondDuration
)? {
return appendSingleDurations(mag, unit, otherParts, c.text, c.pos)
}
SecondDuration
= mag:IntegerLiteral unit:SecondUnits otherParts:(
MicroSecondDuration
/ MilliSecondDuration
/ NanoSecondDuration
)? {
return appendSingleDurations(mag, unit, otherParts, c.text, c.pos)
}
MilliSecondDuration
= mag:IntegerLiteral unit:MilliSecondUnits otherParts:(
MicroSecondDuration
/ NanoSecondDuration
)? {
return appendSingleDurations(mag, unit, otherParts, c.text, c.pos)
}
MicroSecondDuration
= mag:IntegerLiteral unit:MicroSecondUnits otherParts:(
NanoSecondDuration
)? {
return appendSingleDurations(mag, unit, otherParts, c.text, c.pos)
}
NanoSecondDuration
= mag:IntegerLiteral unit:NanoSecondUnits {
return appendSingleDurations(mag, unit, nil, c.text, c.pos)
}
NanoSecondUnits NanoSecondUnits
= "ns" = "ns"
MicroSecondUnits MicroSecondUnits
= ("us" / "µs" / "μs") = ("us" / "µs" / "μs") {
return []byte("us"), nil
}
MilliSecondUnits MilliSecondUnits
= "ms" = "ms"
@ -368,27 +493,11 @@ DayUnits
WeekUnits WeekUnits
= "w" = "w"
DurationUnits MonthUnits
= ( = "mo"
NanoSecondUnits
/ MicroSecondUnits
/ MilliSecondUnits
/ SecondUnits
/ MinuteUnits
/ HourUnits
/ DayUnits
/ WeekUnits
)
SingleDuration YearUnits
= mag:IntegerLiteral unit:DurationUnits { = "y"
return singleDuration(mag, unit, c.text, c.pos)
}
DurationLiteral
= durations:SingleDuration+ {
return durationLiteral(durations, c.text, c.pos)
}
StringLiteral StringLiteral
= ( '"' DoubleStringChar* '"' ) { = ( '"' DoubleStringChar* '"' ) {

View File

@ -2,7 +2,7 @@
package parser package parser
//go:generate pigeon -optimize-parser -optimize-grammar -o flux.go flux.peg //go:generate pigeon -optimize-parser -o flux.go flux.peg
import ( import (
"github.com/influxdata/platform/query/ast" "github.com/influxdata/platform/query/ast"

View File

@ -1718,6 +1718,81 @@ join(tables:[a,b], on:["t1"], fn: (a,b) => (a["_field"] - b["_field"]) / b["_fie
}, },
}, },
}, },
{
name: "duration literal, all units",
raw: `dur = 1y3mo2w1d4h1m30s1ms2µs70ns`,
want: &ast.Program{
Body: []ast.Statement{&ast.VariableDeclaration{
Declarations: []*ast.VariableDeclarator{{
ID: &ast.Identifier{Name: "dur"},
Init: &ast.DurationLiteral{
Values: []ast.Duration{
{Magnitude: 1, Unit: "y"},
{Magnitude: 3, Unit: "mo"},
{Magnitude: 2, Unit: "w"},
{Magnitude: 1, Unit: "d"},
{Magnitude: 4, Unit: "h"},
{Magnitude: 1, Unit: "m"},
{Magnitude: 30, Unit: "s"},
{Magnitude: 1, Unit: "ms"},
{Magnitude: 2, Unit: "us"},
{Magnitude: 70, Unit: "ns"},
},
},
}},
}},
},
},
{
name: "duration literal, months",
raw: `dur = 6mo`,
want: &ast.Program{
Body: []ast.Statement{&ast.VariableDeclaration{
Declarations: []*ast.VariableDeclarator{{
ID: &ast.Identifier{Name: "dur"},
Init: &ast.DurationLiteral{
Values: []ast.Duration{
{Magnitude: 6, Unit: "mo"},
},
},
}},
}},
},
},
{
name: "duration literal, milliseconds",
raw: `dur = 500ms`,
want: &ast.Program{
Body: []ast.Statement{&ast.VariableDeclaration{
Declarations: []*ast.VariableDeclarator{{
ID: &ast.Identifier{Name: "dur"},
Init: &ast.DurationLiteral{
Values: []ast.Duration{
{Magnitude: 500, Unit: "ms"},
},
},
}},
}},
},
},
{
name: "duration literal, months, minutes, milliseconds",
raw: `dur = 6mo30m500ms`,
want: &ast.Program{
Body: []ast.Statement{&ast.VariableDeclaration{
Declarations: []*ast.VariableDeclarator{{
ID: &ast.Identifier{Name: "dur"},
Init: &ast.DurationLiteral{
Values: []ast.Duration{
{Magnitude: 6, Unit: "mo"},
{Magnitude: 30, Unit: "m"},
{Magnitude: 500, Unit: "ms"},
},
},
}},
}},
},
},
{ {
name: "parse error extra gibberish", name: "parse error extra gibberish",
raw: `from(bucket:"Flux/autogen") &^*&H#IUJBN`, raw: `from(bucket:"Flux/autogen") &^*&H#IUJBN`,
@ -1728,6 +1803,21 @@ join(tables:[a,b], on:["t1"], fn: (a,b) => (a["_field"] - b["_field"]) / b["_fie
raw: `from(bucket:"Flux/autogen") &^*&H#IUJBN from(bucket:"other/autogen")`, raw: `from(bucket:"Flux/autogen") &^*&H#IUJBN from(bucket:"other/autogen")`,
wantErr: true, wantErr: true,
}, },
{
name: "parse error from duration literal with repeated units",
raw: `from(bucket:"my_bucket") |> range(start: -1d3h2h1m)`,
wantErr: true,
},
{
name: "parser error from duration literal with smaller unit before larger one",
raw: `from(bucket:"my_bucket") |> range(start: -1s5m)`,
wantErr: true,
},
{
name: "parser error from duration literal with invalid unit",
raw: `from(bucket:"my_bucket") |> range(start: -1s5v)`,
wantErr: true,
},
} }
for _, tt := range tests { for _, tt := range tests {
tt := tt tt := tt

View File

@ -341,30 +341,41 @@ func regexLiteral(chars interface{}, text []byte, pos position) (*ast.RegexpLite
} }
func durationLiteral(durations interface{}, text []byte, pos position) (*ast.DurationLiteral, error) { func durationLiteral(durations interface{}, text []byte, pos position) (*ast.DurationLiteral, error) {
durs := toIfaceSlice(durations) literals := durations.([]*singleDurationLiteral)
literals := make([]*singleDurationLiteral, len(durs)) // The slice built by the parser goes from smallest units to largest (opposite of syntax),
for i, d := range durs { // reverse it for the AST produced.
literals[i] = d.(*singleDurationLiteral) for i := 0; i < len(literals) / 2; i++ {
j := len(literals) - i - 1
literals[i], literals[j] = literals[j], literals[i]
} }
return &ast.DurationLiteral{ return &ast.DurationLiteral{
BaseNode: base(text, pos), BaseNode: base(text, pos),
Values: toDurationSlice(literals), Values: toDurationSlice(literals),
}, nil }, nil
} }
func singleDuration(mag, unit interface{}, text []byte, pos position) (*singleDurationLiteral, error) {
return &singleDurationLiteral{
// Not an AST node
magnitude: mag.(*ast.IntegerLiteral),
unit: string(unit.([]byte)),
}, nil
}
type singleDurationLiteral struct { type singleDurationLiteral struct {
magnitude *ast.IntegerLiteral magnitude *ast.IntegerLiteral
unit string unit string
} }
func appendSingleDurations(mag, unit, otherParts interface{}, text []byte, pos position) ([]*singleDurationLiteral, error) {
sdl := &singleDurationLiteral{
magnitude: mag.(*ast.IntegerLiteral),
unit: string(unit.([]byte)),
}
if otherParts == nil {
slice := make([]*singleDurationLiteral, 1, 10)
slice[0] = sdl
return slice, nil
}
others := otherParts.([]*singleDurationLiteral)
return append(others, sdl), nil
}
func datetime(text []byte, pos position) (*ast.DateTimeLiteral, error) { func datetime(text []byte, pos position) (*ast.DateTimeLiteral, error) {
t, err := time.Parse(time.RFC3339Nano, string(text)) t, err := time.Parse(time.RFC3339Nano, string(text))
if err != nil { if err != nil {

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,9 @@
{ {
// Package promql implements a promql parser to build flux query specifications from promql.
package promql package promql
// DO NOT EDIT: This file is auto generated by the pigeon PEG parser generator.
var reservedWords = map[string]bool{} var reservedWords = map[string]bool{}
} }

View File

@ -1367,11 +1367,22 @@ func analyzeRegexpLiteral(lit *ast.RegexpLiteral, declarations DeclarationScope)
}, nil }, nil
} }
func toDuration(lit ast.Duration) (time.Duration, error) { func toDuration(lit ast.Duration) (time.Duration, error) {
// TODO: This is temporary code until we have proper duration type that takes different months, DST, etc into account
var dur time.Duration var dur time.Duration
var err error var err error
mag := lit.Magnitude mag := lit.Magnitude
unit := lit.Unit unit := lit.Unit
switch unit { switch unit {
case "y":
mag *= 12
unit = "mo"
fallthrough
case "mo":
const weeksPerMonth = 365.25 / 12 / 7
mag = int64(float64(mag) * weeksPerMonth)
unit = "w"
fallthrough
case "w": case "w":
mag *= 7 mag *= 7
unit = "d" unit = "d"
@ -1381,6 +1392,7 @@ func toDuration(lit ast.Duration) (time.Duration, error) {
unit = "h" unit = "h"
fallthrough fallthrough
default: default:
// ParseDuration will handle h, m, s, ms, us, ns.
dur, err = time.ParseDuration(strconv.FormatInt(mag, 10) + unit) dur, err = time.ParseDuration(strconv.FormatInt(mag, 10) + unit)
} }
return dur, err return dur, err