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.
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.
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_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 |
| ----- | ------- |
| 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) |
| 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.
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)
}
// 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
= "ns"
MicroSecondUnits
= ("us" / "µs" / "μs")
= ("us" / "µs" / "μs") {
return []byte("us"), nil
}
MilliSecondUnits
= "ms"
@ -368,27 +493,11 @@ DayUnits
WeekUnits
= "w"
DurationUnits
= (
NanoSecondUnits
/ MicroSecondUnits
/ MilliSecondUnits
/ SecondUnits
/ MinuteUnits
/ HourUnits
/ DayUnits
/ WeekUnits
)
MonthUnits
= "mo"
SingleDuration
= mag:IntegerLiteral unit:DurationUnits {
return singleDuration(mag, unit, c.text, c.pos)
}
DurationLiteral
= durations:SingleDuration+ {
return durationLiteral(durations, c.text, c.pos)
}
YearUnits
= "y"
StringLiteral
= ( '"' DoubleStringChar* '"' ) {

View File

@ -2,7 +2,7 @@
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 (
"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",
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")`,
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 {
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) {
durs := toIfaceSlice(durations)
literals := make([]*singleDurationLiteral, len(durs))
for i, d := range durs {
literals[i] = d.(*singleDurationLiteral)
literals := durations.([]*singleDurationLiteral)
// The slice built by the parser goes from smallest units to largest (opposite of syntax),
// reverse it for the AST produced.
for i := 0; i < len(literals) / 2; i++ {
j := len(literals) - i - 1
literals[i], literals[j] = literals[j], literals[i]
}
return &ast.DurationLiteral{
BaseNode: base(text, pos),
Values: toDurationSlice(literals),
}, 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 {
magnitude *ast.IntegerLiteral
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) {
t, err := time.Parse(time.RFC3339Nano, string(text))
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
// DO NOT EDIT: This file is auto generated by the pigeon PEG parser generator.
var reservedWords = map[string]bool{}
}

View File

@ -1367,11 +1367,22 @@ func analyzeRegexpLiteral(lit *ast.RegexpLiteral, declarations DeclarationScope)
}, nil
}
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 err error
mag := lit.Magnitude
unit := lit.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":
mag *= 7
unit = "d"
@ -1381,6 +1392,7 @@ func toDuration(lit ast.Duration) (time.Duration, error) {
unit = "h"
fallthrough
default:
// ParseDuration will handle h, m, s, ms, us, ns.
dur, err = time.ParseDuration(strconv.FormatInt(mag, 10) + unit)
}
return dur, err