Parse duration literals
parent
2717f9396a
commit
a58c9a63f8
|
@ -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.
|
||||
|
|
7824
query/parser/flux.go
7824
query/parser/flux.go
File diff suppressed because it is too large
Load Diff
|
@ -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* '"' ) {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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{}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue