From cfc725e7993d2173770b08f0ada76699e9af3c0b Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Thu, 1 Apr 2021 14:47:44 -0700 Subject: [PATCH] Add timestamp field type support --- api_test.go | 50 ++ diagnostics.go | 2 +- executor.go | 81 +- executor_test.go | 136 +++- field.go | 127 ++- fragment.go | 4 +- handler.go | 21 +- http/handler.go | 35 +- index.go | 2 +- index_test.go | 21 +- pilosa.go | 5 +- pql/ast.go | 40 + pql/parser.go | 1 + pql/pql.peg | 17 +- pql/pql.peg.go | 1926 ++++++++++++++++++++++++++------------------ pql/pqlpeg_test.go | 16 + sql/extract.go | 81 +- sql/select.go | 2 +- view.go | 2 +- 19 files changed, 1731 insertions(+), 838 deletions(-) diff --git a/api_test.go b/api_test.go index 179d664c1..a03a8dec5 100644 --- a/api_test.go +++ b/api_test.go @@ -444,6 +444,56 @@ func TestAPI_ImportValue(t *testing.T) { } }) + t.Run("ValTimestampField", func(t *testing.T) { + t.Skip("TODO(benbjohnson): timestamp") + + ctx := context.Background() + index := "valts" + field := "fts" + + _, err := m1.API.CreateIndex(ctx, index, pilosa.IndexOptions{}) + if err != nil { + t.Fatalf("creating index: %v", err) + } + _, err = m1.API.CreateField(ctx, index, field, pilosa.OptFieldTypeTimestamp(pilosa.MinTimestamp, pilosa.MaxTimestamp, pilosa.TimeUnitSeconds)) + if err != nil { + t.Fatalf("creating field: %v", err) + } + + // Generate some records. + t0 := time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC) + values := []time.Time{} + colIDs := []uint64{} + for i := 0; i < 10; i++ { + values = append(values, t0.AddDate(0, 1, 0)) + colIDs = append(colIDs, uint64(i)) + } + + // Import data with keys to node1 and verify that it gets translated and + // forwarded to the owner of shard 0 (node0; because of offsetModHasher) + req := &pilosa.ImportValueRequest{ + Index: index, + Field: field, + ColumnIDs: colIDs, + TimestampValues: values, + } + + qcx := m1.API.Txf().NewQcx() + if err := m1.API.ImportValue(ctx, qcx, req); err != nil { + t.Fatal(err) + } + PanicOn(qcx.Finish()) + + query := fmt.Sprintf("Row(%s>6)", field) + + // Query node0. + if res, err := m0.API.Query(ctx, &pilosa.QueryRequest{Index: index, Query: query}); err != nil { + t.Fatal(err) + } else if ids := res.Results[0].(*pilosa.Row).Columns(); !reflect.DeepEqual(ids, colIDs[6:]) { + t.Fatalf("unexpected column keys: observerd %+v; expected '%+v'", ids, colIDs[6:]) + } + }) + t.Run("ValStringField", func(t *testing.T) { ctx := context.Background() index := "valstr" diff --git a/diagnostics.go b/diagnostics.go index 3899eb14d..42ae83338 100644 --- a/diagnostics.go +++ b/diagnostics.go @@ -233,7 +233,7 @@ func (d *diagnosticsCollector) EnrichWithSchemaProperties() { numIndexes++ for _, field := range index.Fields() { numFields++ - if field.Type() == FieldTypeInt || field.Type() == FieldTypeDecimal { + if field.Type() == FieldTypeInt || field.Type() == FieldTypeDecimal || field.Type() == FieldTypeTimestamp { bsiFieldCount++ } if field.TimeQuantum() != "" { diff --git a/executor.go b/executor.go index 1636d07ba..028a3c74b 100644 --- a/executor.go +++ b/executor.go @@ -1029,6 +1029,8 @@ func (e *executor) executeFieldValueCallShard(ctx context.Context, qcx *Qcx, fie Scale: field.Options().Scale} other.FloatVal = 0 other.Val = 0 + } else if field.Type() == FieldTypeTimestamp { + other.TimestampVal = time.Unix(0, value*int64(TimeUnitNano(field.Options().TimeUnit))) } return other, nil @@ -2620,8 +2622,8 @@ func (e *executor) executeTopNShard(ctx context.Context, qcx *Qcx, index string, n, _, err := c.UintArg("n") if err != nil { return nil, fmt.Errorf("executeTopNShard: %v", err) - } else if f := e.Holder.Field(index, fieldName); f != nil && (f.Type() == FieldTypeInt || f.Type() == FieldTypeDecimal) { - return nil, fmt.Errorf("cannot compute TopN() on integer field: %q", fieldName) + } else if f := e.Holder.Field(index, fieldName); f != nil && (f.Type() == FieldTypeInt || f.Type() == FieldTypeDecimal || f.Type() == FieldTypeTimestamp) { + return nil, fmt.Errorf("cannot compute TopN() on integer, decimal, or timestamp field: %q", fieldName) } attrName, _ := c.Args["attrName"].(string) @@ -3001,7 +3003,7 @@ func (e *executor) executeGroupBy(ctx context.Context, qcx *Qcx, index string, c return nil, newNotFoundError(ErrFieldNotFound, fieldName) } switch f.Type() { - case FieldTypeInt: + case FieldTypeInt, FieldTypeTimestamp: bases[i] = f.bsiGroup(f.name).Base } @@ -4420,7 +4422,7 @@ func (e *executor) executeExtractShard(ctx context.Context, qcx *Qcx, index stri } } - case FieldTypeInt, FieldTypeDecimal: + case FieldTypeInt, FieldTypeDecimal, FieldTypeTimestamp: // Handle an int/decimal field by rotating a BSI matrix. // Extract the BSI view fragment. @@ -5128,8 +5130,8 @@ func (e *executor) executeClearBit(ctx context.Context, qcx *Qcx, index string, return false, newNotFoundError(ErrFieldNotFound, fieldName) } - // Int field. - if f.Type() == FieldTypeInt || f.Type() == FieldTypeDecimal { + // BSI field + if f.Type() == FieldTypeInt || f.Type() == FieldTypeDecimal || f.Type() == FieldTypeTimestamp { return e.executeClearValueField(ctx, qcx, index, c, f, colID, opt) } @@ -5457,8 +5459,8 @@ func (e *executor) executeSet(ctx context.Context, qcx *Qcx, index string, c *pq } switch f.Type() { - case FieldTypeInt, FieldTypeDecimal: - // Int or Decimal field. + case FieldTypeInt, FieldTypeDecimal, FieldTypeTimestamp: + // Fetch field v, ok := c.Arg(fieldName) if !ok { return false, fmt.Errorf("Set() row argument '%v' required", rowLabel) @@ -6518,6 +6520,7 @@ func fieldValidateValue(f *Field, val interface{}) error { case int64: case float64: case pql.Decimal: + case time.Time: case []interface{}: for _, v := range v { if err := fieldValidateValue(f, v); err != nil { @@ -6570,6 +6573,12 @@ func fieldValidateValue(f *Field, val interface{}) error { default: return errors.Errorf("invalid value %v for decimal field %q", v, f.Name()) } + case FieldTypeTimestamp: + switch v := val.(type) { + case time.Time: + default: + return errors.Errorf("invalid value %v for timestamp field %q", v, f.Name()) + } default: return errors.Errorf("unsupported type %s of field %q", f.Type(), f.Name()) } @@ -6631,9 +6640,9 @@ func (e *executor) translateCall(c *pql.Call, index string, columnKeys map[strin } if c.Name == "Row" { switch f.Type() { - case FieldTypeInt, FieldTypeDecimal: + case FieldTypeInt, FieldTypeDecimal, FieldTypeTimestamp: if _, ok := arg.(*pql.Condition); !ok { - // This is workaround to support pql.ASSIGN ('=') as condition ('==') for int and decimal fields. + // This is workaround to support pql.ASSIGN ('=') as condition ('==') for BSI fields. arg = &pql.Condition{ Op: pql.EQ, Value: arg, @@ -7384,6 +7393,18 @@ func (e *executor) translateResult(ctx context.Context, index string, idx *Index return nil, errors.Errorf("BSI field %q has too many values: %v", field.Name(), ids) } } + case FieldTypeTimestamp: + datatype = "timestamp" + mapper = func(ids []uint64) (_ interface{}, err error) { + switch len(ids) { + case 0: + return nil, nil + case 1: + return time.Unix(0, int64(ids[0])*int64(TimeUnitNano(field.Options().TimeUnit))).UTC(), nil + default: + return nil, errors.Errorf("BSI field %q has too many values: %v", field.Name(), ids) + } + } default: return nil, errors.Errorf("field type %q not yet supported", typ) } @@ -7655,17 +7676,19 @@ func (sr *SignedRow) union(other SignedRow) SignedRow { // ValCount represents a grouping of sum & count for Sum() and Average() calls. Also Min, Max.... type ValCount struct { - Val int64 `json:"value"` - FloatVal float64 `json:"floatValue"` - DecimalVal *pql.Decimal `json:"decimalValue"` - Count int64 `json:"count"` + Val int64 `json:"value"` + FloatVal float64 `json:"floatValue"` + DecimalVal *pql.Decimal `json:"decimalValue"` + TimestampVal time.Time `json:"timestampValue"` + Count int64 `json:"count"` } func (v *ValCount) Clone() (r *ValCount) { r = &ValCount{ - Val: v.Val, - FloatVal: v.FloatVal, - Count: v.Count, + Val: v.Val, + FloatVal: v.FloatVal, + TimestampVal: v.TimestampVal, + Count: v.Count, } if v.DecimalVal != nil { r.DecimalVal = v.DecimalVal.Clone() @@ -7709,6 +7732,19 @@ func (v ValCount) ToRows(callback func(*proto.RowResponse) error) error { }}); err != nil { return errors.Wrap(err, "calling callback") } + } else if !v.TimestampVal.IsZero() { + ci = []*proto.ColumnInfo{ + {Name: "value", Datatype: "string"}, + {Name: "count", Datatype: "int64"}, + } + if err := callback(&proto.RowResponse{ + Headers: ci, + Columns: []*proto.ColumnResponse{ + &proto.ColumnResponse{ColumnVal: &proto.ColumnResponse_StringVal{StringVal: v.TimestampVal.Format(time.RFC3339Nano)}}, + &proto.ColumnResponse{ColumnVal: &proto.ColumnResponse_Int64Val{Int64Val: v.Count}}, + }}); err != nil { + return errors.Wrap(err, "calling callback") + } } else { ci = []*proto.ColumnInfo{ {Name: "value", Datatype: "int64"}, @@ -7955,12 +7991,12 @@ func newGroupByIterator(executor *executor, qcx *Qcx, rowIDs []RowIDs, children } else { viewName = viewStandard } - case FieldTypeInt: + case FieldTypeInt, FieldTypeTimestamp: viewName = viewBSIGroupPrefix + fieldName default: // FieldTypeDecimal return nil, errors.Errorf("%s call must have field of one of types: %s", - call.Name, strings.Join([]string{FieldTypeSet, FieldTypeTime, FieldTypeMutex, FieldTypeBool, FieldTypeInt}, ",")) + call.Name, strings.Join([]string{FieldTypeSet, FieldTypeTime, FieldTypeMutex, FieldTypeBool, FieldTypeInt, FieldTypeTimestamp}, ",")) } filters := []roaring.BitmapFilter{} @@ -8229,6 +8265,13 @@ func getScaledInt(f *Field, v interface{}) (int64, error) { default: return 0, errors.Errorf("unexpected decimal value type %T, val %v", tv, tv) } + } else if opt.Type == FieldTypeTimestamp { + switch tv := v.(type) { + case time.Time: + value = tv.UnixNano() + default: + return 0, errors.Errorf("unexpected timestamp value type %T, val %v", tv, tv) + } } else { switch tv := v.(type) { case int64: diff --git a/executor_test.go b/executor_test.go index 30b5e152d..005fd6490 100644 --- a/executor_test.go +++ b/executor_test.go @@ -997,6 +997,51 @@ func TestExecutor_Execute_SetValue(t *testing.T) { } }) }) + + t.Run("Timestamp", func(t *testing.T) { + c := test.MustRunCluster(t, 1) + defer c.Close() + hldr := c.GetHolder(0) + + // Create fields. + index := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{}) + if _, err := index.CreateFieldIfNotExists("f", pilosa.OptFieldTypeTimestamp(pilosa.MinTimestamp, pilosa.MaxTimestamp, pilosa.TimeUnitSeconds)); err != nil { + t.Fatal(err) + } else if _, err := index.CreateFieldIfNotExists("xxx", pilosa.OptFieldTypeDefault()); err != nil { + t.Fatal(err) + } + + // Set bsiGroup values. + if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Set(10, f='2000-01-01T00:00:00.000000000Z')`}); err != nil { + t.Fatal(err) + } else if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Set(100, f='2000-01-02T00:00:00Z')`}); err != nil { + t.Fatal(err) + } + + // Obtain transaction. + idx := index.Index + shard := uint64(0) + tx := idx.Txf().NewTx(pilosa.Txo{Write: !writable, Index: idx, Shard: shard}) + defer tx.Rollback() + + f := hldr.Field("i", "f") + if value, exists, err := f.Value(tx, 10); err != nil { + t.Fatal(err) + } else if !exists { + t.Fatal("expected value to exist") + } else if value != time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC).UnixNano() { + t.Fatalf("unexpected value: %v", value) + } + + if value, exists, err := f.Value(tx, 100); err != nil { + t.Fatal(err) + } else if !exists { + t.Fatal("expected value to exist") + } else if value != time.Date(2000, time.January, 2, 0, 0, 0, 0, time.UTC).UnixNano() { + t.Fatalf("unexpected value: %v", value) + } + }) + } // Ensure a SetRowAttrs() query can be executed. @@ -1336,7 +1381,7 @@ func TestExecutor_Execute_TopN(t *testing.T) { t.Fatal(err) } else if _, err := idx.CreateField("f", pilosa.OptFieldTypeInt(0, 100)); err != nil { t.Fatal(err) - } else if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `TopN(f, n=2)`}); err == nil || !strings.Contains(err.Error(), `finding top results: mapping on primary node: cannot compute TopN() on integer field: "f"`) { + } else if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `TopN(f, n=2)`}); err == nil || !strings.Contains(err.Error(), `finding top results: mapping on primary node: cannot compute TopN() on integer, decimal, or timestamp field: "f"`) { t.Fatalf("unexpected error: %v", err) } }) @@ -1741,6 +1786,95 @@ func TestExecutor_Execute_MinMax(t *testing.T) { }) } }) + + t.Run("Timestamp", func(t *testing.T) { + c := test.MustRunCluster(t, 1) + defer c.Close() + hldr := c.GetHolder(0) + + idx, err := hldr.CreateIndex("i", pilosa.IndexOptions{}) + if err != nil { + t.Fatal(err) + } + + tests := []struct { + min time.Time + max time.Time + set time.Time + }{ + { + time.Date(2000, time.January, 10, 0, 0, 0, 0, time.UTC), + time.Date(2000, time.January, 20, 0, 0, 0, 0, time.UTC), + time.Date(2000, time.January, 11, 0, 0, 0, 0, time.UTC), + }, + } + for i, test := range tests { + fld := fmt.Sprintf("f%d", i) + t.Run("MinMaxField_"+fld, func(t *testing.T) { + if _, err := idx.CreateField(fld, pilosa.OptFieldTypeTimestamp(test.min, test.max, pilosa.TimeUnitSeconds)); err != nil { + t.Fatal(err) + } else if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: fmt.Sprintf(`Set(10, %s="%s")`, fld, test.set.Format(time.RFC3339))}); err != nil { + t.Fatal(err) + } + + var pql string + + t.Run("Min", func(t *testing.T) { + pql = fmt.Sprintf(`Min(field=%s)`, fld) + if result, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: pql}); err != nil { + t.Fatal(err) + } else if !reflect.DeepEqual(result.Results[0], pilosa.ValCount{TimestampVal: test.set, Count: 1}) { + t.Fatalf("unexpected min result, test %d: %s", i, spew.Sdump(result)) + } + }) + + t.Run("Max", func(t *testing.T) { + pql = fmt.Sprintf(`Max(field=%s)`, fld) + if result, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: pql}); err != nil { + t.Fatal(err) + } else if !reflect.DeepEqual(result.Results[0], pilosa.ValCount{TimestampVal: test.set, Count: 1}) { + t.Fatalf("unexpected max result, test %d: %s", i, spew.Sdump(result)) + } + }) + + t.Run("Min", func(t *testing.T) { + pql = fmt.Sprintf(`Min(field="%s")`, fld) + if result, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: pql}); err != nil { + t.Fatal(err) + } else if !reflect.DeepEqual(result.Results[0], pilosa.ValCount{TimestampVal: test.set, Count: 1}) { + t.Fatalf("unexpected min result, test %d: %s", i, spew.Sdump(result)) + } + }) + + t.Run("Max", func(t *testing.T) { + pql = fmt.Sprintf(`Max(field="%s")`, fld) + if result, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: pql}); err != nil { + t.Fatal(err) + } else if !reflect.DeepEqual(result.Results[0], pilosa.ValCount{TimestampVal: test.set, Count: 1}) { + t.Fatalf("unexpected max result, test %d: %s", i, spew.Sdump(result)) + } + }) + + t.Run("Min", func(t *testing.T) { + pql = fmt.Sprintf(`Min(%s)`, fld) + if result, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: pql}); err != nil { + t.Fatal(err) + } else if !reflect.DeepEqual(result.Results[0], pilosa.ValCount{TimestampVal: test.set, Count: 1}) { + t.Fatalf("unexpected min result, test %d: %s", i, spew.Sdump(result)) + } + }) + + t.Run("Max", func(t *testing.T) { + pql = fmt.Sprintf(`Max(%s)`, fld) + if result, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: pql}); err != nil { + t.Fatal(err) + } else if !reflect.DeepEqual(result.Results[0], pilosa.ValCount{TimestampVal: test.set, Count: 1}) { + t.Fatalf("unexpected max result, test %d: %s", i, spew.Sdump(result)) + } + }) + }) + } + }) }) t.Run("ColumnID", func(t *testing.T) { diff --git a/field.go b/field.go index b26144289..cce899898 100644 --- a/field.go +++ b/field.go @@ -55,12 +55,13 @@ const ( // Field types. const ( - FieldTypeSet = "set" - FieldTypeInt = "int" - FieldTypeTime = "time" - FieldTypeMutex = "mutex" - FieldTypeBool = "bool" - FieldTypeDecimal = "decimal" + FieldTypeSet = "set" + FieldTypeInt = "int" + FieldTypeTime = "time" + FieldTypeMutex = "mutex" + FieldTypeBool = "bool" + FieldTypeDecimal = "decimal" + FieldTypeTimestamp = "timestamp" ) type protected struct { @@ -204,6 +205,33 @@ func OptFieldTypeInt(min, max int64) FieldOption { } } +// OptFieldTypeTimestamp is a functional option on FieldOptions +// used to specify the field as being type `timestamp` and to +// provide any respective configuration values. +func OptFieldTypeTimestamp(min, max time.Time, timeUnit string) FieldOption { + return func(fo *FieldOptions) error { + minNano := min.UnixNano() + maxNano := max.UnixNano() + if fo.Type != "" { + return errors.Errorf("field type is already set to: %s", fo.Type) + } + if timeUnit == "" { + return errors.Errorf("time unit required for timestamp field") + } else if !IsValidTimeUnit(timeUnit) { + return errors.Errorf("invalid time unit: %q", fo.TimeUnit) + } + if min.After(max) { + return errors.New("timestamp field min cannot be greater than max") + } + fo.Type = FieldTypeTimestamp + fo.TimeUnit = timeUnit + fo.Min = pql.NewDecimal(minNano, 0) + fo.Max = pql.NewDecimal(maxNano, 0) + fo.Base = bsiBase(minNano, maxNano) + return nil + } +} + // OptFieldTypeDecimal is a functional option for creating a `decimal` field. // Unless we decide to expand the range of supported values, `scale` is // restricted to the range [0,19]. This supports anything from: @@ -797,7 +825,7 @@ func (f *Field) applyOptions(opt FieldOptions) error { f.options.TimeQuantum = "" f.options.Keys = opt.Keys f.options.ForeignIndex = opt.ForeignIndex - case FieldTypeInt, FieldTypeDecimal: + case FieldTypeInt, FieldTypeDecimal, FieldTypeTimestamp: f.options.Type = opt.Type f.options.CacheType = CacheTypeNone f.options.CacheSize = 0 @@ -806,6 +834,7 @@ func (f *Field) applyOptions(opt FieldOptions) error { f.options.Base = opt.Base f.options.Scale = opt.Scale f.options.BitDepth = opt.BitDepth + f.options.TimeUnit = opt.TimeUnit f.options.TimeQuantum = "" f.options.Keys = opt.Keys f.options.ForeignIndex = opt.ForeignIndex @@ -818,6 +847,7 @@ func (f *Field) applyOptions(opt FieldOptions) error { Max: opt.Max.ToInt64(opt.Scale), Base: opt.Base, Scale: opt.Scale, + TimeUnit: opt.TimeUnit, BitDepth: opt.BitDepth, } // Validate and create bsiGroup. @@ -1386,6 +1416,8 @@ func (f *Field) MaxForShard(tx Tx, shard uint64, filter *Row) (ValCount, error) if f.Options().Type == FieldTypeDecimal { dec := pql.NewDecimal(max+bsig.Base, bsig.Scale) valCount.DecimalVal = &dec + } else if f.Options().Type == FieldTypeTimestamp { + valCount.TimestampVal = time.Unix(0, (max + bsig.Base)).UTC() } else { valCount.Val = max + bsig.Base } @@ -1430,6 +1462,8 @@ func (f *Field) MinForShard(tx Tx, shard uint64, filter *Row) (ValCount, error) if f.Options().Type == FieldTypeDecimal { dec := pql.NewDecimal(min+bsig.Base, bsig.Scale) valCount.DecimalVal = &dec + } else if f.Options().Type == FieldTypeTimestamp { + valCount.TimestampVal = time.Unix(0, (min + bsig.Base)).UTC() } else { valCount.Val = min + bsig.Base } @@ -1708,10 +1742,10 @@ func (f *Field) importRoaringOverwrite(ctx context.Context, tx Tx, data []byte, return err } - // If field is int or decimal, then we need to update field.options.BitDepth - // and bsiGroup.BitDepth based on the imported data. + // If field is int, decimal, or timestamp, then we need to update + // field.options.BitDepth and bsiGroup.BitDepth based on the imported data. switch f.Options().Type { - case FieldTypeInt, FieldTypeDecimal: + case FieldTypeInt, FieldTypeDecimal, FieldTypeTimestamp: frag.mu.Lock() maxRowID, _, err := frag.maxRow(tx, nil) frag.mu.Unlock() @@ -1772,6 +1806,7 @@ type FieldOptions struct { CacheSize uint32 `json:"cacheSize,omitempty"` CacheType string `json:"cacheType,omitempty"` Type string `json:"type,omitempty"` + TimeUnit string `json:"timeUnit,omitempty"` TimeQuantum TimeQuantum `json:"timeQuantum,omitempty"` ForeignIndex string `json:"foreignIndex"` } @@ -1794,6 +1829,9 @@ func newFieldOptions(opts ...FieldOption) (*FieldOptions, error) { case FieldTypeDecimal: return nil, ErrDecimalFieldWithKeys + + case FieldTypeTimestamp: + return nil, ErrTimestampFieldWithKeys } } @@ -1867,6 +1905,26 @@ func (o *FieldOptions) MarshalJSON() ([]byte, error) { o.Max, o.Keys, }) + case FieldTypeTimestamp: + return json.Marshal(struct { + Type string `json:"type"` + Base int64 `json:"base"` + BitDepth uint64 `json:"bitDepth"` + Min pql.Decimal `json:"min"` + Max pql.Decimal `json:"max"` + Keys bool `json:"keys"` + TimeUnit string `json:"timeUnit"` + ForeignIndex string `json:"foreignIndex"` + }{ + o.Type, + o.Base, + o.BitDepth, + o.Min, + o.Max, + o.Keys, + o.TimeUnit, + o.ForeignIndex, + }) case FieldTypeTime: return json.Marshal(struct { Type string `json:"type"` @@ -1901,6 +1959,16 @@ func (o *FieldOptions) MarshalJSON() ([]byte, error) { return nil, errors.New("invalid field type") } +// MinTimestamp returns the minimum value for a timestamp field. +func (o FieldOptions) MinTimestamp() time.Time { + return time.Unix(0, o.Min.ToInt64(0)*int64(TimeUnitNano(o.TimeUnit))) +} + +// MaxTimestamp returns the maxnimum value for a timestamp field. +func (o FieldOptions) MaxTimestamp() time.Time { + return time.Unix(0, o.Max.ToInt64(0)*int64(TimeUnitNano(o.TimeUnit))) +} + // List of bsiGroup types. const ( bsiGroupTypeInt = "int" @@ -1935,6 +2003,7 @@ type bsiGroup struct { Max int64 `json:"max,omitempty"` Base int64 `json:"base,omitempty"` Scale int64 `json:"scale,omitempty"` + TimeUnit string `json:"timeUnit,omitempty"` BitDepth uint64 `json:"bitDepth,omitempty"` } @@ -2064,3 +2133,41 @@ func (f *Field) persistView(ctx context.Context, cvm *CreateViewMessage) error { return f.schemator.CreateView(ctx, cvm.Index, cvm.Field, cvm.View) } + +// Timestamp field range. +var ( + MinTimestamp = time.Unix(-1<<42, 0).UTC() + MaxTimestamp = time.Unix(1<<42, 0).UTC() +) + +// List of time units. +const ( + TimeUnitSeconds = "s" + TimeUnitMilliseconds = "ms" + TimeUnitMicroseconds = "µs" + TimeUnitNanoseconds = "ns" +) + +// IsValidTimeUnit returns true if unit is valid. +func IsValidTimeUnit(unit string) bool { + switch unit { + case TimeUnitSeconds, TimeUnitMilliseconds, TimeUnitMicroseconds, TimeUnitNanoseconds: + return true + default: + return false + } +} + +// TimeUnitNano returns the number of nanoseconds in unit. +func TimeUnitNano(unit string) int64 { + switch unit { + case TimeUnitSeconds: + return int64(time.Second) + case TimeUnitMilliseconds: + return int64(time.Millisecond) + case TimeUnitMicroseconds: + return int64(time.Microsecond) + default: + return int64(time.Nanosecond) + } +} diff --git a/fragment.go b/fragment.go index e20b0d725..565cc8aac 100644 --- a/fragment.go +++ b/fragment.go @@ -3644,7 +3644,7 @@ func (s *fragmentSyncer) syncFragment() error { // to continue processing int/decimal fields. if nodes[0].ID != s.Node.ID { switch s.FieldType { - case FieldTypeInt, FieldTypeDecimal: + case FieldTypeInt, FieldTypeDecimal, FieldTypeTimestamp: return nil } } @@ -3721,7 +3721,7 @@ func (s *fragmentSyncer) syncFragment() error { s.Fragment.holder.Logger.Debugf("sync block from primary: index='%v' field='%v' view='%v' shard='%v' id=%d", s.Fragment.index(), s.Fragment.field(), s.Fragment.view(), s.Fragment.shard, blockID) switch s.FieldType { - case FieldTypeInt, FieldTypeDecimal: + case FieldTypeInt, FieldTypeDecimal, FieldTypeTimestamp: // Synchronize block from the primary replica. if err := s.syncBlockFromPrimary(blockID); err != nil { return fmt.Errorf("sync block from primary: id=%d, err=%s", blockID, err) diff --git a/handler.go b/handler.go index 3c9fc5d58..8f9f2dbef 100644 --- a/handler.go +++ b/handler.go @@ -16,6 +16,7 @@ package pilosa import ( "encoding/json" + "time" "github.com/pilosa/pilosa/v2/tracing" "github.com/pkg/errors" @@ -128,13 +129,14 @@ type ImportValueRequest struct { FieldCreatedAt int64 // if Shard is MaxUint64 (an impossible shard value), this // indicates that the column IDs may come from multiple shards. - Shard uint64 - ColumnIDs []uint64 // e.g. weather stationID - ColumnKeys []string - Values []int64 // e.g. temperature, humidity, barometric pressure - FloatValues []float64 - StringValues []string - Clear bool + Shard uint64 + ColumnIDs []uint64 // e.g. weather stationID + ColumnKeys []string + Values []int64 // e.g. temperature, humidity, barometric pressure + FloatValues []float64 + TimestampValues []time.Time + StringValues []string + Clear bool } // AtomicRecord applies all its Ivr and Ivr atomically, in a Tx. @@ -158,6 +160,8 @@ func (ivr *ImportValueRequest) Swap(i, j int) { ivr.Values[i], ivr.Values[j] = ivr.Values[j], ivr.Values[i] } else if len(ivr.FloatValues) > 0 { ivr.FloatValues[i], ivr.FloatValues[j] = ivr.FloatValues[j], ivr.FloatValues[i] + } else if len(ivr.TimestampValues) > 0 { + ivr.TimestampValues[i], ivr.TimestampValues[j] = ivr.TimestampValues[j], ivr.TimestampValues[i] } else if len(ivr.StringValues) > 0 { ivr.StringValues[i], ivr.StringValues[j] = ivr.StringValues[j], ivr.StringValues[i] } @@ -183,6 +187,9 @@ func (ivr *ImportValueRequest) ValidateWithTimestamp(indexCreatedAt, fieldCreate if len(ivr.FloatValues) != 0 { valueSetCount++ } + if len(ivr.TimestampValues) != 0 { + valueSetCount++ + } if len(ivr.StringValues) != 0 { valueSetCount++ } diff --git a/http/handler.go b/http/handler.go index 9f1829dc6..426484b3b 100644 --- a/http/handler.go +++ b/http/handler.go @@ -1340,6 +1340,20 @@ func (h *Handler) handlePostField(w http.ResponseWriter, r *http.Request) { } } fos = append(fos, pilosa.OptFieldTypeDecimal(scale, minmax...)) + case pilosa.FieldTypeTimestamp: + if req.Options.Min == nil { + min := pql.NewDecimal(pilosa.MinTimestamp.UnixNano()/pilosa.TimeUnitNano(*req.Options.TimeUnit), 0) + req.Options.Min = &min + } + if req.Options.Max == nil { + max := pql.NewDecimal(pilosa.MaxTimestamp.UnixNano()/pilosa.TimeUnitNano(*req.Options.TimeUnit), 0) + req.Options.Max = &max + } + fos = append(fos, pilosa.OptFieldTypeTimestamp( + time.Unix(0, req.Options.Min.ToInt64(0)*pilosa.TimeUnitNano(*req.Options.TimeUnit)).UTC(), + time.Unix(0, req.Options.Max.ToInt64(0)*pilosa.TimeUnitNano(*req.Options.TimeUnit)).UTC(), + *req.Options.TimeUnit, + )) case pilosa.FieldTypeTime: fos = append(fos, pilosa.OptFieldTypeTime(*req.Options.TimeQuantum, req.Options.NoStandardView)) case pilosa.FieldTypeMutex: @@ -1384,6 +1398,7 @@ type fieldOptions struct { Min *pql.Decimal `json:"min,omitempty"` Max *pql.Decimal `json:"max,omitempty"` Scale *int64 `json:"scale,omitempty"` + TimeUnit *string `json:"timeUnit,omitempty"` TimeQuantum *pilosa.TimeQuantum `json:"timeQuantum,omitempty"` Keys *bool `json:"keys,omitempty"` NoStandardView bool `json:"noStandardView,omitempty"` @@ -1423,8 +1438,8 @@ func (o *fieldOptions) validate() error { return pilosa.NewBadRequestError(errors.New("cacheSize does not apply to field type int")) } else if o.TimeQuantum != nil { return pilosa.NewBadRequestError(errors.New("timeQuantum does not apply to field type int")) - } else if o.ForeignIndex != nil && o.Type == pilosa.FieldTypeDecimal { - return pilosa.NewBadRequestError(errors.New("decimal field cannot be a foreign key")) + } else if o.ForeignIndex != nil { + return pilosa.NewBadRequestError(errors.New("int field cannot be a foreign key")) } case pilosa.FieldTypeDecimal: if o.Scale == nil { @@ -1438,6 +1453,20 @@ func (o *fieldOptions) validate() error { } else if o.ForeignIndex != nil && o.Type == pilosa.FieldTypeDecimal { return pilosa.NewBadRequestError(errors.New("decimal field cannot be a foreign key")) } + case pilosa.FieldTypeTimestamp: + if o.TimeUnit == nil { + return pilosa.NewBadRequestError(errors.New("timestamp field requires a timeUnit argument")) + } else if !pilosa.IsValidTimeUnit(*o.TimeUnit) { + return pilosa.NewBadRequestError(errors.New("invalid timeUnit argument")) + } else if o.CacheType != nil { + return pilosa.NewBadRequestError(errors.New("cacheType does not apply to field type timestamp")) + } else if o.CacheSize != nil { + return pilosa.NewBadRequestError(errors.New("cacheSize does not apply to field type timestamp")) + } else if o.TimeQuantum != nil { + return pilosa.NewBadRequestError(errors.New("timeQuantum does not apply to field type timestamp")) + } else if o.ForeignIndex != nil { + return pilosa.NewBadRequestError(errors.New("timestamp field cannot be a foreign key")) + } case pilosa.FieldTypeTime: if o.CacheType != nil { return pilosa.NewBadRequestError(errors.New("cacheType does not apply to field type time")) @@ -2384,7 +2413,7 @@ func (h *Handler) handlePostImport(w http.ResponseWriter, r *http.Request) { return } // Unmarshal request based on field type. - if field.Type() == pilosa.FieldTypeInt || field.Type() == pilosa.FieldTypeDecimal { + if field.Type() == pilosa.FieldTypeInt || field.Type() == pilosa.FieldTypeDecimal || field.Type() == pilosa.FieldTypeTimestamp { // Field type: Int // Marshal into request object. req := &pilosa.ImportValueRequest{} diff --git a/index.go b/index.go index 9dd06342b..c8c1ebe55 100644 --- a/index.go +++ b/index.go @@ -430,7 +430,7 @@ func (i *Index) openExistenceField() error { func (i *Index) setFieldBitDepths() error { for name, f := range i.fields { switch f.Type() { - case FieldTypeInt, FieldTypeDecimal: + case FieldTypeInt, FieldTypeDecimal, FieldTypeTimestamp: // pass default: continue diff --git a/index_test.go b/index_test.go index 040220f86..da242ca29 100644 --- a/index_test.go +++ b/index_test.go @@ -89,7 +89,7 @@ func TestIndex_CreateField(t *testing.T) { // Ensure field can include range columns. t.Run("BSIFields", func(t *testing.T) { - t.Run("OK", func(t *testing.T) { + t.Run("Int", func(t *testing.T) { index := test.MustOpenIndex(t) defer index.Close() @@ -108,6 +108,25 @@ func TestIndex_CreateField(t *testing.T) { } }) + t.Run("Timestamp", func(t *testing.T) { + index := test.MustOpenIndex(t) + defer index.Close() + + // Create field with schema and verify it exists. + if f, err := index.CreateField("f", pilosa.OptFieldTypeTimestamp(pilosa.MinTimestamp, pilosa.MaxTimestamp, pilosa.TimeUnitSeconds)); err != nil { + t.Fatal(err) + } else if !reflect.DeepEqual(f.Type(), pilosa.FieldTypeTimestamp) { + t.Fatalf("unexpected type: %#v", f.Type()) + } + + // Reopen the index & verify the fields are loaded. + if err := index.Reopen(); err != nil { + t.Fatal(err) + } else if f := index.Field("f"); !reflect.DeepEqual(f.Type(), pilosa.FieldTypeTimestamp) { + t.Fatalf("unexpected type after reopen: %#v", f.Type()) + } + }) + // TODO: These errors don't apply here. Instead, we need these tests // on field creation FieldOptions validation. /* diff --git a/pilosa.go b/pilosa.go index 9109f7447..23dd8ed30 100644 --- a/pilosa.go +++ b/pilosa.go @@ -87,8 +87,9 @@ var ( ErrFieldsArgumentRequired = errors.New("fields argument required") ErrExpectedFieldListArgument = errors.New("expected field list argument") - ErrIntFieldWithKeys = errors.New("int field cannot be created with 'keys=true' option") - ErrDecimalFieldWithKeys = errors.New("decimal field cannot be created with 'keys=true' option") + ErrIntFieldWithKeys = errors.New("int field cannot be created with 'keys=true' option") + ErrDecimalFieldWithKeys = errors.New("decimal field cannot be created with 'keys=true' option") + ErrTimestampFieldWithKeys = errors.New("timestamp field cannot be created with 'keys=true' option") ) // apiMethodNotAllowedError wraps an error value indicating that a particular diff --git a/pql/ast.go b/pql/ast.go index f0c0d38b3..d042bcc08 100644 --- a/pql/ast.go +++ b/pql/ast.go @@ -202,6 +202,38 @@ func (q *Query) addNumVal(val string) { elem.lastCond = ILLEGAL } +func (q *Query) addTimestampVal(val string) { + elem := q.lastCallStackElem() + if elem == nil || elem.lastField == "" { + panic(fmt.Sprintf("addTimestampVal called with '%s' when lastField is empty", val)) + } + tsval := parseTimestamp(val) + if elem.inList { + if elem.lastCond != ILLEGAL { + list := elem.call.Args[elem.lastField].(*Condition).Value.([]interface{}) + elem.call.Args[elem.lastField] = &Condition{ + Op: elem.lastCond, + Value: append(list, tsval), + } + } else { + list := elem.call.Args[elem.lastField].([]interface{}) + elem.call.Args[elem.lastField] = append(list, tsval) + } + return + } else if elem.lastCond != ILLEGAL { + q.validateArgField(elem) // case 3 + elem.call.Args[elem.lastField] = &Condition{ + Op: elem.lastCond, + Value: tsval, + } + } else { + q.validateArgField(elem) // case 4 + elem.call.Args[elem.lastField] = tsval + } + elem.lastField = "" + elem.lastCond = ILLEGAL +} + func (q *Query) startList() { elem := q.lastCallStackElem() q.validateArgField(elem) // case 5 @@ -1109,3 +1141,11 @@ func parseNum(val string) interface{} { } return ival } + +func parseTimestamp(val string) time.Time { + tsval, err := time.Parse(time.RFC3339Nano, val) + if err != nil { + panic(fmt.Sprintf("%s: %s", invalidTimestampError, err)) + } + return tsval +} diff --git a/pql/parser.go b/pql/parser.go index 706cf5a7d..e5f3593c4 100644 --- a/pql/parser.go +++ b/pql/parser.go @@ -31,6 +31,7 @@ const timeFormat = "2006-01-02T15:04" // error strings in the parser const duplicateArgErrorMessage = "duplicate argument provided" const intOutOfRangeError = "integer is not in signed 64-bit range" +const invalidTimestampError = "string is not a timestamp" // parser represents a parser for the PQL language. type parser struct { diff --git a/pql/pql.peg b/pql/pql.peg index 9d7f59478..cb8aec256 100644 --- a/pql/pql.peg +++ b/pql/pql.peg @@ -6,7 +6,7 @@ type PQL Peg { # All input queries consist of a sequence of calls, at the top level. Calls <- sp (Call sp)* !. -Call <- "Set" {p.startCall("Set")} open col comma args (comma timestamp)? close {p.endCall()} +Call <- "Set" {p.startCall("Set")} open col comma args (comma time)? close {p.endCall()} / "SetRowAttrs" {p.startCall("SetRowAttrs")} open posfield comma row comma args close {p.endCall()} / "SetColumnAttrs" {p.startCall("SetColumnAttrs")} open col comma args close {p.endCall()} / "Clear" {p.startCall("Clear")} open col comma args close {p.endCall()} @@ -19,7 +19,7 @@ Call <- "Set" {p.startCall("Set")} open col comma args (comma timestamp)? close / "Min" {p.startCall("Min")} open posfield (comma allargs)? close {p.endCall()} / "Max" {p.startCall("Max")} open posfield (comma allargs)? close {p.endCall()} / "Sum" {p.startCall("Sum")} open posfield (comma allargs)? close {p.endCall()} - / "Range" {p.startCall("Range")} open field eq value comma 'from='? {p.addField("from")} timestampfmt {p.addVal(text)} comma 'to='? sp {p.addField("to")} timestampfmt {p.addVal(text)} close {p.endCall()} + / "Range" {p.startCall("Range")} open field eq value comma 'from='? {p.addField("from")} timefmt {p.addVal(text)} comma 'to='? sp {p.addField("to")} timefmt {p.addVal(text)} close {p.endCall()} / < IDENT > { p.startCall(text) } open allargs comma? close { p.endCall() } allargs <- Call (comma Call)* (comma args)? / args / sp args <- arg (comma args)? sp @@ -45,7 +45,8 @@ items <- item (comma items)? item <- 'null' &(comma / close) { p.addVal(nil) } / 'true' &(comma / close) { p.addVal(true) } / 'false' &(comma / close) { p.addVal(false) } - / timestampfmt { p.addVal(text) } + / timefmt { p.addVal(text) } + / timestampfmt { p.addTimestampVal(text) } / < decimal > { p.addNumVal(text) } / < IDENT > { p.startCall(text) } open allargs comma? close { p.addVal(p.endCall()) } / < ([[A-Z]] / [0-9] / '-' / '_' / ':')+ > { p.addVal(text) } @@ -79,6 +80,12 @@ signedDigits <- '-'? digits decimal <- signedDigits ('.' digits?)? / '-'? '.' digits -timestampbasicfmt <- [0-9][0-9][0-9][0-9]'-'[01][0-9]'-'[0-3][0-9]'T'[0-9][0-9]':'[0-9][0-9] +tz <- 'Z' / '-' [0-9][0-9]':'[0-9][0-9] / '+'[0-9][0-9]':'[0-9][0-9] +iso8601 <- [0-9][0-9][0-9][0-9]'-'[01][0-9]'-'[0-3][0-9]'T'[0-9][0-9]':'[0-9][0-9]':'[0-9][0-9] +iso8601nano <- [0-9][0-9][0-9][0-9]'-'[01][0-9]'-'[0-3][0-9]'T'[0-9][0-9]':'[0-9][0-9]':'[0-9][0-9]'.'[0-9]+ +timestampbasicfmt <- iso8601nano / iso8601 timestampfmt <- '"' '"' / '\'' '\'' / -timestamp <- {p.addPosStr("_timestamp", text)} + +timebasicfmt <- [0-9][0-9][0-9][0-9]'-'[01][0-9]'-'[0-3][0-9]'T'[0-9][0-9]':'[0-9][0-9] +timefmt <- '"' '"' / '\'' '\'' / +time <- {p.addPosStr("_timestamp", text)} diff --git a/pql/pql.peg.go b/pql/pql.peg.go index 327d9cb76..f3b910752 100644 --- a/pql/pql.peg.go +++ b/pql/pql.peg.go @@ -8,6 +8,7 @@ import ( "os" "sort" "strconv" + "strings" ) const endSymbol rune = 1114112 @@ -49,9 +50,14 @@ const ( ruledigits rulesignedDigits ruledecimal + ruletz + ruleiso8601 + ruleiso8601nano ruletimestampbasicfmt ruletimestampfmt - ruletimestamp + ruletimebasicfmt + ruletimefmt + ruletime ruleAction0 ruleAction1 ruleAction2 @@ -120,6 +126,7 @@ const ( ruleAction64 ruleAction65 ruleAction66 + ruleAction67 ) var rul3s = [...]string{ @@ -156,9 +163,14 @@ var rul3s = [...]string{ "digits", "signedDigits", "decimal", + "tz", + "iso8601", + "iso8601nano", "timestampbasicfmt", "timestampfmt", - "timestamp", + "timebasicfmt", + "timefmt", + "time", "Action0", "Action1", "Action2", @@ -227,6 +239,7 @@ var rul3s = [...]string{ "Action64", "Action65", "Action66", + "Action67", } type token32 struct { @@ -255,7 +268,7 @@ func (node *node32) print(w io.Writer, pretty bool, buffer string) { if !pretty { fmt.Fprintf(w, "%v %v\n", rule, quote) } else { - fmt.Fprintf(w, "\x1B[34m%v\x1B[m %v\n", rule, quote) + fmt.Fprintf(w, "\x1B[36m%v\x1B[m %v\n", rule, quote) } if node.up != nil { print(node.up, depth+1) @@ -343,7 +356,7 @@ type PQL struct { Buffer string buffer []rune - rules [104]func() bool + rules [110]func() bool parse func(rule ...int) error reset func() Pretty bool @@ -430,6 +443,12 @@ func (p *PQL) WriteSyntaxTree(w io.Writer) { p.tokens32.WriteSyntaxTree(w, p.Buffer) } +func (p *PQL) SprintSyntaxTree() string { + var bldr strings.Builder + p.WriteSyntaxTree(&bldr) + return bldr.String() +} + func (p *PQL) Execute() { buffer, _buffer, text, begin, end := p.Buffer, p.buffer, "", 0, 0 for _, token := range p.Tokens() { @@ -544,34 +563,36 @@ func (p *PQL) Execute() { case ruleAction51: p.addVal(text) case ruleAction52: - p.addNumVal(text) + p.addTimestampVal(text) case ruleAction53: - p.startCall(text) + p.addNumVal(text) case ruleAction54: - p.addVal(p.endCall()) + p.startCall(text) case ruleAction55: - p.addVal(text) + p.addVal(p.endCall()) case ruleAction56: p.addVal(text) case ruleAction57: p.addVal(text) case ruleAction58: - p.addField(text) + p.addVal(text) case ruleAction59: - p.addPosStr("_field", text) + p.addField(text) case ruleAction60: - p.addPosNum("_col", text) + p.addPosStr("_field", text) case ruleAction61: - p.addPosStr("_col", text) + p.addPosNum("_col", text) case ruleAction62: p.addPosStr("_col", text) case ruleAction63: - p.addPosNum("_row", text) + p.addPosStr("_col", text) case ruleAction64: - p.addPosStr("_row", text) + p.addPosNum("_row", text) case ruleAction65: p.addPosStr("_row", text) case ruleAction66: + p.addPosStr("_row", text) + case ruleAction67: p.addPosStr("_timestamp", text) } @@ -703,7 +724,7 @@ func (p *PQL) Init(options ...func(*PQL) error) error { position, tokenIndex = position0, tokenIndex0 return false }, - /* 1 Call <- <((('s' / 'S') ('e' / 'E') ('t' / 'T') Action0 open col comma args (comma timestamp)? close Action1) / (('s' / 'S') ('e' / 'E') ('t' / 'T') ('r' / 'R') ('o' / 'O') ('w' / 'W') ('a' / 'A') ('t' / 'T') ('t' / 'T') ('r' / 'R') ('s' / 'S') Action2 open posfield comma row comma args close Action3) / (('s' / 'S') ('e' / 'E') ('t' / 'T') ('c' / 'C') ('o' / 'O') ('l' / 'L') ('u' / 'U') ('m' / 'M') ('n' / 'N') ('a' / 'A') ('t' / 'T') ('t' / 'T') ('r' / 'R') ('s' / 'S') Action4 open col comma args close Action5) / (('c' / 'C') ('l' / 'L') ('e' / 'E') ('a' / 'A') ('r' / 'R') Action6 open col comma args close Action7) / (('c' / 'C') ('l' / 'L') ('e' / 'E') ('a' / 'A') ('r' / 'R') ('r' / 'R') ('o' / 'O') ('w' / 'W') Action8 open arg close Action9) / (('s' / 'S') ('t' / 'T') ('o' / 'O') ('r' / 'R') ('e' / 'E') Action10 open Call comma arg close Action11) / (('t' / 'T') ('o' / 'O') ('p' / 'P') ('n' / 'N') Action12 open posfield (comma allargs)? close Action13) / (('t' / 'T') ('o' / 'O') ('p' / 'P') ('k' / 'K') Action14 open posfield (comma allargs)? close Action15) / (('p' / 'P') ('e' / 'E') ('r' / 'R') ('c' / 'C') ('e' / 'E') ('n' / 'N') ('t' / 'T') ('i' / 'I') ('l' / 'L') ('e' / 'E') Action16 open posfield (comma allargs)? close Action17) / (('r' / 'R') ('o' / 'O') ('w' / 'W') ('s' / 'S') Action18 open posfield (comma allargs)? close Action19) / (('m' / 'M') ('i' / 'I') ('n' / 'N') Action20 open posfield (comma allargs)? close Action21) / (('m' / 'M') ('a' / 'A') ('x' / 'X') Action22 open posfield (comma allargs)? close Action23) / (('s' / 'S') ('u' / 'U') ('m' / 'M') Action24 open posfield (comma allargs)? close Action25) / (('r' / 'R') ('a' / 'A') ('n' / 'N') ('g' / 'G') ('e' / 'E') Action26 open field eq value comma ('f' 'r' 'o' 'm' '=')? Action27 timestampfmt Action28 comma ('t' 'o' '=')? sp Action29 timestampfmt Action30 close Action31) / ( Action32 open allargs comma? close Action33))> */ + /* 1 Call <- <((('s' / 'S') ('e' / 'E') ('t' / 'T') Action0 open col comma args (comma time)? close Action1) / (('s' / 'S') ('e' / 'E') ('t' / 'T') ('r' / 'R') ('o' / 'O') ('w' / 'W') ('a' / 'A') ('t' / 'T') ('t' / 'T') ('r' / 'R') ('s' / 'S') Action2 open posfield comma row comma args close Action3) / (('s' / 'S') ('e' / 'E') ('t' / 'T') ('c' / 'C') ('o' / 'O') ('l' / 'L') ('u' / 'U') ('m' / 'M') ('n' / 'N') ('a' / 'A') ('t' / 'T') ('t' / 'T') ('r' / 'R') ('s' / 'S') Action4 open col comma args close Action5) / (('c' / 'C') ('l' / 'L') ('e' / 'E') ('a' / 'A') ('r' / 'R') Action6 open col comma args close Action7) / (('c' / 'C') ('l' / 'L') ('e' / 'E') ('a' / 'A') ('r' / 'R') ('r' / 'R') ('o' / 'O') ('w' / 'W') Action8 open arg close Action9) / (('s' / 'S') ('t' / 'T') ('o' / 'O') ('r' / 'R') ('e' / 'E') Action10 open Call comma arg close Action11) / (('t' / 'T') ('o' / 'O') ('p' / 'P') ('n' / 'N') Action12 open posfield (comma allargs)? close Action13) / (('t' / 'T') ('o' / 'O') ('p' / 'P') ('k' / 'K') Action14 open posfield (comma allargs)? close Action15) / (('p' / 'P') ('e' / 'E') ('r' / 'R') ('c' / 'C') ('e' / 'E') ('n' / 'N') ('t' / 'T') ('i' / 'I') ('l' / 'L') ('e' / 'E') Action16 open posfield (comma allargs)? close Action17) / (('r' / 'R') ('o' / 'O') ('w' / 'W') ('s' / 'S') Action18 open posfield (comma allargs)? close Action19) / (('m' / 'M') ('i' / 'I') ('n' / 'N') Action20 open posfield (comma allargs)? close Action21) / (('m' / 'M') ('a' / 'A') ('x' / 'X') Action22 open posfield (comma allargs)? close Action23) / (('s' / 'S') ('u' / 'U') ('m' / 'M') Action24 open posfield (comma allargs)? close Action25) / (('r' / 'R') ('a' / 'A') ('n' / 'N') ('g' / 'G') ('e' / 'E') Action26 open field eq value comma ('f' 'r' 'o' 'm' '=')? Action27 timefmt Action28 comma ('t' 'o' '=')? sp Action29 timefmt Action30 close Action31) / ( Action32 open allargs comma? close Action33))> */ func() bool { position5, tokenIndex5 := position, tokenIndex { @@ -779,15 +800,15 @@ func (p *PQL) Init(options ...func(*PQL) error) error { position18 := position { position19 := position - if !_rules[ruletimestampfmt]() { + if !_rules[ruletimefmt]() { goto l16 } add(rulePegText, position19) } { - add(ruleAction66, position) + add(ruleAction67, position) } - add(ruletimestamp, position18) + add(ruletime, position18) } goto l17 l16: @@ -992,7 +1013,7 @@ func (p *PQL) Init(options ...func(*PQL) error) error { add(rulePegText, position49) } { - add(ruleAction63, position) + add(ruleAction64, position) } goto l47 l48: @@ -1013,7 +1034,7 @@ func (p *PQL) Init(options ...func(*PQL) error) error { add(rulePegText, position52) } { - add(ruleAction64, position) + add(ruleAction65, position) } goto l47 l51: @@ -1034,7 +1055,7 @@ func (p *PQL) Init(options ...func(*PQL) error) error { add(rulePegText, position54) } { - add(ruleAction65, position) + add(ruleAction66, position) } } l47: @@ -2430,7 +2451,7 @@ func (p *PQL) Init(options ...func(*PQL) error) error { { add(ruleAction27, position) } - if !_rules[ruletimestampfmt]() { + if !_rules[ruletimefmt]() { goto l230 } { @@ -2464,7 +2485,7 @@ func (p *PQL) Init(options ...func(*PQL) error) error { { add(ruleAction29, position) } - if !_rules[ruletimestampfmt]() { + if !_rules[ruletimefmt]() { goto l230 } { @@ -2941,7 +2962,7 @@ func (p *PQL) Init(options ...func(*PQL) error) error { position, tokenIndex = position316, tokenIndex316 return false }, - /* 12 item <- <(('n' 'u' 'l' 'l' &(comma / close) Action48) / ('t' 'r' 'u' 'e' &(comma / close) Action49) / ('f' 'a' 'l' 's' 'e' &(comma / close) Action50) / (timestampfmt Action51) / ( Action52) / ( Action53 open allargs comma? close Action54) / (<([a-z] / [A-Z] / [0-9] / '-' / '_' / ':')+> Action55) / (<('"' doublequotedstring '"')> Action56) / (<('\'' singlequotedstring '\'')> Action57))> */ + /* 12 item <- <(('n' 'u' 'l' 'l' &(comma / close) Action48) / ('t' 'r' 'u' 'e' &(comma / close) Action49) / ('f' 'a' 'l' 's' 'e' &(comma / close) Action50) / (timefmt Action51) / (timestampfmt Action52) / ( Action53) / ( Action54 open allargs comma? close Action55) / (<([a-z] / [A-Z] / [0-9] / '-' / '_' / ':')+> Action56) / (<('"' doublequotedstring '"')> Action57) / (<('\'' singlequotedstring '\'')> Action58))> */ func() bool { position320, tokenIndex320 := position, tokenIndex { @@ -3069,7 +3090,7 @@ func (p *PQL) Init(options ...func(*PQL) error) error { goto l322 l333: position, tokenIndex = position322, tokenIndex322 - if !_rules[ruletimestampfmt]() { + if !_rules[ruletimefmt]() { goto l338 } { @@ -3080,178 +3101,235 @@ func (p *PQL) Init(options ...func(*PQL) error) error { position, tokenIndex = position322, tokenIndex322 { position341 := position - if !_rules[ruledecimal]() { - goto l340 + { + position342, tokenIndex342 := position, tokenIndex + if buffer[position] != rune('"') { + goto l343 + } + position++ + { + position344 := position + if !_rules[ruletimestampbasicfmt]() { + goto l343 + } + add(rulePegText, position344) + } + if buffer[position] != rune('"') { + goto l343 + } + position++ + goto l342 + l343: + position, tokenIndex = position342, tokenIndex342 + if buffer[position] != rune('\'') { + goto l345 + } + position++ + { + position346 := position + if !_rules[ruletimestampbasicfmt]() { + goto l345 + } + add(rulePegText, position346) + } + if buffer[position] != rune('\'') { + goto l345 + } + position++ + goto l342 + l345: + position, tokenIndex = position342, tokenIndex342 + { + position347 := position + if !_rules[ruletimestampbasicfmt]() { + goto l340 + } + add(rulePegText, position347) + } } - add(rulePegText, position341) + l342: + add(ruletimestampfmt, position341) } { add(ruleAction52, position) } goto l322 l340: - position, tokenIndex = position322, tokenIndex322 - { - position344 := position - if !_rules[ruleIDENT]() { - goto l343 - } - add(rulePegText, position344) - } - { - add(ruleAction53, position) - } - if !_rules[ruleopen]() { - goto l343 - } - if !_rules[ruleallargs]() { - goto l343 - } - { - position346, tokenIndex346 := position, tokenIndex - if !_rules[rulecomma]() { - goto l346 - } - goto l347 - l346: - position, tokenIndex = position346, tokenIndex346 - } - l347: - if !_rules[ruleclose]() { - goto l343 - } - { - add(ruleAction54, position) - } - goto l322 - l343: position, tokenIndex = position322, tokenIndex322 { position350 := position - { - position353, tokenIndex353 := position, tokenIndex - if c := buffer[position]; c < rune('a') || c > rune('z') { - goto l354 - } - position++ - goto l353 - l354: - position, tokenIndex = position353, tokenIndex353 - if c := buffer[position]; c < rune('A') || c > rune('Z') { - goto l355 - } - position++ - goto l353 - l355: - position, tokenIndex = position353, tokenIndex353 - if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l356 - } - position++ - goto l353 - l356: - position, tokenIndex = position353, tokenIndex353 - if buffer[position] != rune('-') { - goto l357 - } - position++ - goto l353 - l357: - position, tokenIndex = position353, tokenIndex353 - if buffer[position] != rune('_') { - goto l358 - } - position++ - goto l353 - l358: - position, tokenIndex = position353, tokenIndex353 - if buffer[position] != rune(':') { - goto l349 - } - position++ - } - l353: - l351: - { - position352, tokenIndex352 := position, tokenIndex - { - position359, tokenIndex359 := position, tokenIndex - if c := buffer[position]; c < rune('a') || c > rune('z') { - goto l360 - } - position++ - goto l359 - l360: - position, tokenIndex = position359, tokenIndex359 - if c := buffer[position]; c < rune('A') || c > rune('Z') { - goto l361 - } - position++ - goto l359 - l361: - position, tokenIndex = position359, tokenIndex359 - if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l362 - } - position++ - goto l359 - l362: - position, tokenIndex = position359, tokenIndex359 - if buffer[position] != rune('-') { - goto l363 - } - position++ - goto l359 - l363: - position, tokenIndex = position359, tokenIndex359 - if buffer[position] != rune('_') { - goto l364 - } - position++ - goto l359 - l364: - position, tokenIndex = position359, tokenIndex359 - if buffer[position] != rune(':') { - goto l352 - } - position++ - } - l359: - goto l351 - l352: - position, tokenIndex = position352, tokenIndex352 + if !_rules[ruledecimal]() { + goto l349 } add(rulePegText, position350) } { - add(ruleAction55, position) + add(ruleAction53, position) } goto l322 l349: position, tokenIndex = position322, tokenIndex322 { - position367 := position - if buffer[position] != rune('"') { - goto l366 + position353 := position + if !_rules[ruleIDENT]() { + goto l352 } - position++ - if !_rules[ruledoublequotedstring]() { - goto l366 + add(rulePegText, position353) + } + { + add(ruleAction54, position) + } + if !_rules[ruleopen]() { + goto l352 + } + if !_rules[ruleallargs]() { + goto l352 + } + { + position355, tokenIndex355 := position, tokenIndex + if !_rules[rulecomma]() { + goto l355 } - if buffer[position] != rune('"') { - goto l366 + goto l356 + l355: + position, tokenIndex = position355, tokenIndex355 + } + l356: + if !_rules[ruleclose]() { + goto l352 + } + { + add(ruleAction55, position) + } + goto l322 + l352: + position, tokenIndex = position322, tokenIndex322 + { + position359 := position + { + position362, tokenIndex362 := position, tokenIndex + if c := buffer[position]; c < rune('a') || c > rune('z') { + goto l363 + } + position++ + goto l362 + l363: + position, tokenIndex = position362, tokenIndex362 + if c := buffer[position]; c < rune('A') || c > rune('Z') { + goto l364 + } + position++ + goto l362 + l364: + position, tokenIndex = position362, tokenIndex362 + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l365 + } + position++ + goto l362 + l365: + position, tokenIndex = position362, tokenIndex362 + if buffer[position] != rune('-') { + goto l366 + } + position++ + goto l362 + l366: + position, tokenIndex = position362, tokenIndex362 + if buffer[position] != rune('_') { + goto l367 + } + position++ + goto l362 + l367: + position, tokenIndex = position362, tokenIndex362 + if buffer[position] != rune(':') { + goto l358 + } + position++ } - position++ - add(rulePegText, position367) + l362: + l360: + { + position361, tokenIndex361 := position, tokenIndex + { + position368, tokenIndex368 := position, tokenIndex + if c := buffer[position]; c < rune('a') || c > rune('z') { + goto l369 + } + position++ + goto l368 + l369: + position, tokenIndex = position368, tokenIndex368 + if c := buffer[position]; c < rune('A') || c > rune('Z') { + goto l370 + } + position++ + goto l368 + l370: + position, tokenIndex = position368, tokenIndex368 + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l371 + } + position++ + goto l368 + l371: + position, tokenIndex = position368, tokenIndex368 + if buffer[position] != rune('-') { + goto l372 + } + position++ + goto l368 + l372: + position, tokenIndex = position368, tokenIndex368 + if buffer[position] != rune('_') { + goto l373 + } + position++ + goto l368 + l373: + position, tokenIndex = position368, tokenIndex368 + if buffer[position] != rune(':') { + goto l361 + } + position++ + } + l368: + goto l360 + l361: + position, tokenIndex = position361, tokenIndex361 + } + add(rulePegText, position359) } { add(ruleAction56, position) } goto l322 - l366: + l358: position, tokenIndex = position322, tokenIndex322 { - position369 := position + position376 := position + if buffer[position] != rune('"') { + goto l375 + } + position++ + if !_rules[ruledoublequotedstring]() { + goto l375 + } + if buffer[position] != rune('"') { + goto l375 + } + position++ + add(rulePegText, position376) + } + { + add(ruleAction57, position) + } + goto l322 + l375: + position, tokenIndex = position322, tokenIndex322 + { + position378 := position if buffer[position] != rune('\'') { goto l320 } @@ -3263,10 +3341,10 @@ func (p *PQL) Init(options ...func(*PQL) error) error { goto l320 } position++ - add(rulePegText, position369) + add(rulePegText, position378) } { - add(ruleAction57, position) + add(ruleAction58, position) } } l322: @@ -3280,646 +3358,587 @@ func (p *PQL) Init(options ...func(*PQL) error) error { /* 13 doublequotedstring <- <(('\\' '"') / ('\\' '\\') / ('\\' 'n') / ('\\' 't') / (!('"' / '\\') .))*> */ func() bool { { - position372 := position - l373: + position381 := position + l382: { - position374, tokenIndex374 := position, tokenIndex + position383, tokenIndex383 := position, tokenIndex { - position375, tokenIndex375 := position, tokenIndex + position384, tokenIndex384 := position, tokenIndex if buffer[position] != rune('\\') { - goto l376 + goto l385 } position++ if buffer[position] != rune('"') { - goto l376 + goto l385 } position++ - goto l375 - l376: - position, tokenIndex = position375, tokenIndex375 + goto l384 + l385: + position, tokenIndex = position384, tokenIndex384 if buffer[position] != rune('\\') { - goto l377 + goto l386 } position++ if buffer[position] != rune('\\') { - goto l377 + goto l386 } position++ - goto l375 - l377: - position, tokenIndex = position375, tokenIndex375 + goto l384 + l386: + position, tokenIndex = position384, tokenIndex384 if buffer[position] != rune('\\') { - goto l378 + goto l387 } position++ if buffer[position] != rune('n') { - goto l378 + goto l387 } position++ - goto l375 - l378: - position, tokenIndex = position375, tokenIndex375 + goto l384 + l387: + position, tokenIndex = position384, tokenIndex384 if buffer[position] != rune('\\') { - goto l379 + goto l388 } position++ if buffer[position] != rune('t') { - goto l379 + goto l388 } position++ - goto l375 - l379: - position, tokenIndex = position375, tokenIndex375 + goto l384 + l388: + position, tokenIndex = position384, tokenIndex384 { - position380, tokenIndex380 := position, tokenIndex + position389, tokenIndex389 := position, tokenIndex { - position381, tokenIndex381 := position, tokenIndex + position390, tokenIndex390 := position, tokenIndex if buffer[position] != rune('"') { - goto l382 + goto l391 } position++ - goto l381 - l382: - position, tokenIndex = position381, tokenIndex381 + goto l390 + l391: + position, tokenIndex = position390, tokenIndex390 if buffer[position] != rune('\\') { - goto l380 + goto l389 } position++ } - l381: - goto l374 - l380: - position, tokenIndex = position380, tokenIndex380 + l390: + goto l383 + l389: + position, tokenIndex = position389, tokenIndex389 } if !matchDot() { - goto l374 + goto l383 } } - l375: - goto l373 - l374: - position, tokenIndex = position374, tokenIndex374 + l384: + goto l382 + l383: + position, tokenIndex = position383, tokenIndex383 } - add(ruledoublequotedstring, position372) + add(ruledoublequotedstring, position381) } return true }, /* 14 singlequotedstring <- <(('\\' '\'') / ('\\' '\\') / ('\\' 'n') / ('\\' 't') / (!('\'' / '\\') .))*> */ func() bool { { - position384 := position - l385: + position393 := position + l394: { - position386, tokenIndex386 := position, tokenIndex + position395, tokenIndex395 := position, tokenIndex { - position387, tokenIndex387 := position, tokenIndex + position396, tokenIndex396 := position, tokenIndex if buffer[position] != rune('\\') { - goto l388 + goto l397 } position++ if buffer[position] != rune('\'') { - goto l388 + goto l397 } position++ - goto l387 - l388: - position, tokenIndex = position387, tokenIndex387 + goto l396 + l397: + position, tokenIndex = position396, tokenIndex396 if buffer[position] != rune('\\') { - goto l389 + goto l398 } position++ if buffer[position] != rune('\\') { - goto l389 + goto l398 } position++ - goto l387 - l389: - position, tokenIndex = position387, tokenIndex387 + goto l396 + l398: + position, tokenIndex = position396, tokenIndex396 if buffer[position] != rune('\\') { - goto l390 + goto l399 } position++ if buffer[position] != rune('n') { - goto l390 + goto l399 } position++ - goto l387 - l390: - position, tokenIndex = position387, tokenIndex387 + goto l396 + l399: + position, tokenIndex = position396, tokenIndex396 if buffer[position] != rune('\\') { - goto l391 + goto l400 } position++ if buffer[position] != rune('t') { - goto l391 + goto l400 } position++ - goto l387 - l391: - position, tokenIndex = position387, tokenIndex387 + goto l396 + l400: + position, tokenIndex = position396, tokenIndex396 { - position392, tokenIndex392 := position, tokenIndex + position401, tokenIndex401 := position, tokenIndex { - position393, tokenIndex393 := position, tokenIndex + position402, tokenIndex402 := position, tokenIndex if buffer[position] != rune('\'') { - goto l394 + goto l403 } position++ - goto l393 - l394: - position, tokenIndex = position393, tokenIndex393 + goto l402 + l403: + position, tokenIndex = position402, tokenIndex402 if buffer[position] != rune('\\') { - goto l392 + goto l401 } position++ } - l393: - goto l386 - l392: - position, tokenIndex = position392, tokenIndex392 + l402: + goto l395 + l401: + position, tokenIndex = position401, tokenIndex401 } if !matchDot() { - goto l386 + goto l395 } } - l387: - goto l385 - l386: - position, tokenIndex = position386, tokenIndex386 + l396: + goto l394 + l395: + position, tokenIndex = position395, tokenIndex395 } - add(rulesinglequotedstring, position384) + add(rulesinglequotedstring, position393) } return true }, /* 15 fieldExpr <- <(([a-z] / [A-Z] / '_') ([a-z] / [A-Z] / [0-9] / '_' / '-')*)> */ func() bool { - position395, tokenIndex395 := position, tokenIndex + position404, tokenIndex404 := position, tokenIndex { - position396 := position + position405 := position { - position397, tokenIndex397 := position, tokenIndex + position406, tokenIndex406 := position, tokenIndex if c := buffer[position]; c < rune('a') || c > rune('z') { - goto l398 + goto l407 } position++ - goto l397 - l398: - position, tokenIndex = position397, tokenIndex397 + goto l406 + l407: + position, tokenIndex = position406, tokenIndex406 if c := buffer[position]; c < rune('A') || c > rune('Z') { - goto l399 + goto l408 } position++ - goto l397 - l399: - position, tokenIndex = position397, tokenIndex397 + goto l406 + l408: + position, tokenIndex = position406, tokenIndex406 if buffer[position] != rune('_') { - goto l395 + goto l404 } position++ } - l397: - l400: + l406: + l409: { - position401, tokenIndex401 := position, tokenIndex + position410, tokenIndex410 := position, tokenIndex { - position402, tokenIndex402 := position, tokenIndex + position411, tokenIndex411 := position, tokenIndex if c := buffer[position]; c < rune('a') || c > rune('z') { - goto l403 + goto l412 } position++ - goto l402 - l403: - position, tokenIndex = position402, tokenIndex402 + goto l411 + l412: + position, tokenIndex = position411, tokenIndex411 if c := buffer[position]; c < rune('A') || c > rune('Z') { - goto l404 + goto l413 } position++ - goto l402 - l404: - position, tokenIndex = position402, tokenIndex402 + goto l411 + l413: + position, tokenIndex = position411, tokenIndex411 if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l405 + goto l414 } position++ - goto l402 - l405: - position, tokenIndex = position402, tokenIndex402 + goto l411 + l414: + position, tokenIndex = position411, tokenIndex411 if buffer[position] != rune('_') { - goto l406 + goto l415 } position++ - goto l402 - l406: - position, tokenIndex = position402, tokenIndex402 + goto l411 + l415: + position, tokenIndex = position411, tokenIndex411 if buffer[position] != rune('-') { - goto l401 + goto l410 } position++ } - l402: - goto l400 - l401: - position, tokenIndex = position401, tokenIndex401 + l411: + goto l409 + l410: + position, tokenIndex = position410, tokenIndex410 } - add(rulefieldExpr, position396) + add(rulefieldExpr, position405) } return true - l395: - position, tokenIndex = position395, tokenIndex395 + l404: + position, tokenIndex = position404, tokenIndex404 return false }, - /* 16 field <- <(<(fieldExpr / reserved)> Action58)> */ + /* 16 field <- <(<(fieldExpr / reserved)> Action59)> */ func() bool { - position407, tokenIndex407 := position, tokenIndex + position416, tokenIndex416 := position, tokenIndex { - position408 := position + position417 := position { - position409 := position + position418 := position { - position410, tokenIndex410 := position, tokenIndex + position419, tokenIndex419 := position, tokenIndex if !_rules[rulefieldExpr]() { - goto l411 + goto l420 } - goto l410 - l411: - position, tokenIndex = position410, tokenIndex410 + goto l419 + l420: + position, tokenIndex = position419, tokenIndex419 { - position412 := position + position421 := position { - position413, tokenIndex413 := position, tokenIndex + position422, tokenIndex422 := position, tokenIndex if buffer[position] != rune('_') { - goto l414 + goto l423 } position++ if buffer[position] != rune('r') { - goto l414 + goto l423 } position++ if buffer[position] != rune('o') { - goto l414 + goto l423 } position++ if buffer[position] != rune('w') { - goto l414 + goto l423 } position++ - goto l413 - l414: - position, tokenIndex = position413, tokenIndex413 + goto l422 + l423: + position, tokenIndex = position422, tokenIndex422 if buffer[position] != rune('_') { - goto l415 + goto l424 } position++ if buffer[position] != rune('c') { - goto l415 + goto l424 } position++ if buffer[position] != rune('o') { - goto l415 + goto l424 } position++ if buffer[position] != rune('l') { - goto l415 + goto l424 } position++ - goto l413 - l415: - position, tokenIndex = position413, tokenIndex413 + goto l422 + l424: + position, tokenIndex = position422, tokenIndex422 if buffer[position] != rune('_') { - goto l416 + goto l425 } position++ if buffer[position] != rune('s') { - goto l416 + goto l425 } position++ if buffer[position] != rune('t') { - goto l416 + goto l425 } position++ if buffer[position] != rune('a') { - goto l416 + goto l425 } position++ if buffer[position] != rune('r') { - goto l416 + goto l425 } position++ if buffer[position] != rune('t') { - goto l416 + goto l425 } position++ - goto l413 - l416: - position, tokenIndex = position413, tokenIndex413 + goto l422 + l425: + position, tokenIndex = position422, tokenIndex422 if buffer[position] != rune('_') { - goto l417 + goto l426 } position++ if buffer[position] != rune('e') { - goto l417 + goto l426 } position++ if buffer[position] != rune('n') { - goto l417 + goto l426 } position++ if buffer[position] != rune('d') { - goto l417 + goto l426 } position++ - goto l413 - l417: - position, tokenIndex = position413, tokenIndex413 + goto l422 + l426: + position, tokenIndex = position422, tokenIndex422 if buffer[position] != rune('_') { - goto l418 + goto l427 } position++ if buffer[position] != rune('t') { - goto l418 + goto l427 } position++ if buffer[position] != rune('i') { - goto l418 + goto l427 } position++ if buffer[position] != rune('m') { - goto l418 + goto l427 } position++ if buffer[position] != rune('e') { - goto l418 + goto l427 } position++ if buffer[position] != rune('s') { - goto l418 + goto l427 } position++ if buffer[position] != rune('t') { - goto l418 + goto l427 } position++ if buffer[position] != rune('a') { - goto l418 + goto l427 } position++ if buffer[position] != rune('m') { - goto l418 + goto l427 } position++ if buffer[position] != rune('p') { - goto l418 + goto l427 } position++ - goto l413 - l418: - position, tokenIndex = position413, tokenIndex413 + goto l422 + l427: + position, tokenIndex = position422, tokenIndex422 if buffer[position] != rune('_') { - goto l407 + goto l416 } position++ if buffer[position] != rune('f') { - goto l407 + goto l416 } position++ if buffer[position] != rune('i') { - goto l407 + goto l416 } position++ if buffer[position] != rune('e') { - goto l407 + goto l416 } position++ if buffer[position] != rune('l') { - goto l407 + goto l416 } position++ if buffer[position] != rune('d') { - goto l407 + goto l416 } position++ } - l413: - add(rulereserved, position412) + l422: + add(rulereserved, position421) } } - l410: - add(rulePegText, position409) - } - { - add(ruleAction58, position) - } - add(rulefield, position408) - } - return true - l407: - position, tokenIndex = position407, tokenIndex407 - return false - }, - /* 17 reserved <- <(('_' 'r' 'o' 'w') / ('_' 'c' 'o' 'l') / ('_' 's' 't' 'a' 'r' 't') / ('_' 'e' 'n' 'd') / ('_' 't' 'i' 'm' 'e' 's' 't' 'a' 'm' 'p') / ('_' 'f' 'i' 'e' 'l' 'd'))> */ - nil, - /* 18 posfield <- <(('f' 'i' 'e' 'l' 'd' '=')? Action59)> */ - func() bool { - position421, tokenIndex421 := position, tokenIndex - { - position422 := position - { - position423, tokenIndex423 := position, tokenIndex - if buffer[position] != rune('f') { - goto l423 - } - position++ - if buffer[position] != rune('i') { - goto l423 - } - position++ - if buffer[position] != rune('e') { - goto l423 - } - position++ - if buffer[position] != rune('l') { - goto l423 - } - position++ - if buffer[position] != rune('d') { - goto l423 - } - position++ - if buffer[position] != rune('=') { - goto l423 - } - position++ - goto l424 - l423: - position, tokenIndex = position423, tokenIndex423 - } - l424: - { - position425 := position - if !_rules[rulefieldExpr]() { - goto l421 - } - add(rulePegText, position425) + l419: + add(rulePegText, position418) } { add(ruleAction59, position) } - add(ruleposfield, position422) + add(rulefield, position417) } return true - l421: - position, tokenIndex = position421, tokenIndex421 + l416: + position, tokenIndex = position416, tokenIndex416 return false }, - /* 19 col <- <(( Action60) / (<('\'' singlequotedstring '\'')> Action61) / (<('"' doublequotedstring '"')> Action62))> */ + /* 17 reserved <- <(('_' 'r' 'o' 'w') / ('_' 'c' 'o' 'l') / ('_' 's' 't' 'a' 'r' 't') / ('_' 'e' 'n' 'd') / ('_' 't' 'i' 'm' 'e' 's' 't' 'a' 'm' 'p') / ('_' 'f' 'i' 'e' 'l' 'd'))> */ + nil, + /* 18 posfield <- <(('f' 'i' 'e' 'l' 'd' '=')? Action60)> */ func() bool { - position427, tokenIndex427 := position, tokenIndex + position430, tokenIndex430 := position, tokenIndex { - position428 := position + position431 := position { - position429, tokenIndex429 := position, tokenIndex + position432, tokenIndex432 := position, tokenIndex + if buffer[position] != rune('f') { + goto l432 + } + position++ + if buffer[position] != rune('i') { + goto l432 + } + position++ + if buffer[position] != rune('e') { + goto l432 + } + position++ + if buffer[position] != rune('l') { + goto l432 + } + position++ + if buffer[position] != rune('d') { + goto l432 + } + position++ + if buffer[position] != rune('=') { + goto l432 + } + position++ + goto l433 + l432: + position, tokenIndex = position432, tokenIndex432 + } + l433: + { + position434 := position + if !_rules[rulefieldExpr]() { + goto l430 + } + add(rulePegText, position434) + } + { + add(ruleAction60, position) + } + add(ruleposfield, position431) + } + return true + l430: + position, tokenIndex = position430, tokenIndex430 + return false + }, + /* 19 col <- <(( Action61) / (<('\'' singlequotedstring '\'')> Action62) / (<('"' doublequotedstring '"')> Action63))> */ + func() bool { + position436, tokenIndex436 := position, tokenIndex + { + position437 := position + { + position438, tokenIndex438 := position, tokenIndex { - position431 := position + position440 := position if !_rules[ruledigits]() { - goto l430 + goto l439 } - add(rulePegText, position431) - } - { - add(ruleAction60, position) - } - goto l429 - l430: - position, tokenIndex = position429, tokenIndex429 - { - position434 := position - if buffer[position] != rune('\'') { - goto l433 - } - position++ - if !_rules[rulesinglequotedstring]() { - goto l433 - } - if buffer[position] != rune('\'') { - goto l433 - } - position++ - add(rulePegText, position434) + add(rulePegText, position440) } { add(ruleAction61, position) } - goto l429 - l433: - position, tokenIndex = position429, tokenIndex429 + goto l438 + l439: + position, tokenIndex = position438, tokenIndex438 { - position436 := position - if buffer[position] != rune('"') { - goto l427 + position443 := position + if buffer[position] != rune('\'') { + goto l442 } position++ - if !_rules[ruledoublequotedstring]() { - goto l427 + if !_rules[rulesinglequotedstring]() { + goto l442 } - if buffer[position] != rune('"') { - goto l427 + if buffer[position] != rune('\'') { + goto l442 } position++ - add(rulePegText, position436) + add(rulePegText, position443) } { add(ruleAction62, position) } + goto l438 + l442: + position, tokenIndex = position438, tokenIndex438 + { + position445 := position + if buffer[position] != rune('"') { + goto l436 + } + position++ + if !_rules[ruledoublequotedstring]() { + goto l436 + } + if buffer[position] != rune('"') { + goto l436 + } + position++ + add(rulePegText, position445) + } + { + add(ruleAction63, position) + } } - l429: - add(rulecol, position428) + l438: + add(rulecol, position437) } return true - l427: - position, tokenIndex = position427, tokenIndex427 + l436: + position, tokenIndex = position436, tokenIndex436 return false }, - /* 20 row <- <(( Action63) / (<('\'' singlequotedstring '\'')> Action64) / (<('"' doublequotedstring '"')> Action65))> */ + /* 20 row <- <(( Action64) / (<('\'' singlequotedstring '\'')> Action65) / (<('"' doublequotedstring '"')> Action66))> */ nil, /* 21 open <- <('(' sp)> */ func() bool { - position439, tokenIndex439 := position, tokenIndex + position448, tokenIndex448 := position, tokenIndex { - position440 := position + position449 := position if buffer[position] != rune('(') { - goto l439 + goto l448 } position++ if !_rules[rulesp]() { - goto l439 + goto l448 } - add(ruleopen, position440) + add(ruleopen, position449) } return true - l439: - position, tokenIndex = position439, tokenIndex439 + l448: + position, tokenIndex = position448, tokenIndex448 return false }, /* 22 close <- <(sp ')' sp)> */ - func() bool { - position441, tokenIndex441 := position, tokenIndex - { - position442 := position - if !_rules[rulesp]() { - goto l441 - } - if buffer[position] != rune(')') { - goto l441 - } - position++ - if !_rules[rulesp]() { - goto l441 - } - add(ruleclose, position442) - } - return true - l441: - position, tokenIndex = position441, tokenIndex441 - return false - }, - /* 23 sp <- <(' ' / '\t' / '\n')*> */ - func() bool { - { - position444 := position - l445: - { - position446, tokenIndex446 := position, tokenIndex - { - position447, tokenIndex447 := position, tokenIndex - if buffer[position] != rune(' ') { - goto l448 - } - position++ - goto l447 - l448: - position, tokenIndex = position447, tokenIndex447 - if buffer[position] != rune('\t') { - goto l449 - } - position++ - goto l447 - l449: - position, tokenIndex = position447, tokenIndex447 - if buffer[position] != rune('\n') { - goto l446 - } - position++ - } - l447: - goto l445 - l446: - position, tokenIndex = position446, tokenIndex446 - } - add(rulesp, position444) - } - return true - }, - /* 24 eq <- <(sp '=' sp)> */ func() bool { position450, tokenIndex450 := position, tokenIndex { @@ -3927,40 +3946,99 @@ func (p *PQL) Init(options ...func(*PQL) error) error { if !_rules[rulesp]() { goto l450 } - if buffer[position] != rune('=') { + if buffer[position] != rune(')') { goto l450 } position++ if !_rules[rulesp]() { goto l450 } - add(ruleeq, position451) + add(ruleclose, position451) } return true l450: position, tokenIndex = position450, tokenIndex450 return false }, - /* 25 comma <- <(sp ',' sp)> */ + /* 23 sp <- <(' ' / '\t' / '\n')*> */ func() bool { - position452, tokenIndex452 := position, tokenIndex { position453 := position - if !_rules[rulesp]() { - goto l452 + l454: + { + position455, tokenIndex455 := position, tokenIndex + { + position456, tokenIndex456 := position, tokenIndex + if buffer[position] != rune(' ') { + goto l457 + } + position++ + goto l456 + l457: + position, tokenIndex = position456, tokenIndex456 + if buffer[position] != rune('\t') { + goto l458 + } + position++ + goto l456 + l458: + position, tokenIndex = position456, tokenIndex456 + if buffer[position] != rune('\n') { + goto l455 + } + position++ + } + l456: + goto l454 + l455: + position, tokenIndex = position455, tokenIndex455 } - if buffer[position] != rune(',') { - goto l452 + add(rulesp, position453) + } + return true + }, + /* 24 eq <- <(sp '=' sp)> */ + func() bool { + position459, tokenIndex459 := position, tokenIndex + { + position460 := position + if !_rules[rulesp]() { + goto l459 + } + if buffer[position] != rune('=') { + goto l459 } position++ if !_rules[rulesp]() { - goto l452 + goto l459 } - add(rulecomma, position453) + add(ruleeq, position460) } return true - l452: - position, tokenIndex = position452, tokenIndex452 + l459: + position, tokenIndex = position459, tokenIndex459 + return false + }, + /* 25 comma <- <(sp ',' sp)> */ + func() bool { + position461, tokenIndex461 := position, tokenIndex + { + position462 := position + if !_rules[rulesp]() { + goto l461 + } + if buffer[position] != rune(',') { + goto l461 + } + position++ + if !_rules[rulesp]() { + goto l461 + } + add(rulecomma, position462) + } + return true + l461: + position, tokenIndex = position461, tokenIndex461 return false }, /* 26 lbrack <- <('[' sp)> */ @@ -3968,448 +4046,764 @@ func (p *PQL) Init(options ...func(*PQL) error) error { /* 27 rbrack <- <(sp ']' sp)> */ nil, /* 28 IDENT <- <(([a-z] / [A-Z]) ([a-z] / [A-Z] / [0-9])*)> */ - func() bool { - position456, tokenIndex456 := position, tokenIndex - { - position457 := position - { - position458, tokenIndex458 := position, tokenIndex - if c := buffer[position]; c < rune('a') || c > rune('z') { - goto l459 - } - position++ - goto l458 - l459: - position, tokenIndex = position458, tokenIndex458 - if c := buffer[position]; c < rune('A') || c > rune('Z') { - goto l456 - } - position++ - } - l458: - l460: - { - position461, tokenIndex461 := position, tokenIndex - { - position462, tokenIndex462 := position, tokenIndex - if c := buffer[position]; c < rune('a') || c > rune('z') { - goto l463 - } - position++ - goto l462 - l463: - position, tokenIndex = position462, tokenIndex462 - if c := buffer[position]; c < rune('A') || c > rune('Z') { - goto l464 - } - position++ - goto l462 - l464: - position, tokenIndex = position462, tokenIndex462 - if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l461 - } - position++ - } - l462: - goto l460 - l461: - position, tokenIndex = position461, tokenIndex461 - } - add(ruleIDENT, position457) - } - return true - l456: - position, tokenIndex = position456, tokenIndex456 - return false - }, - /* 29 digits <- <[0-9]+> */ func() bool { position465, tokenIndex465 := position, tokenIndex { position466 := position - if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l465 - } - position++ - l467: { - position468, tokenIndex468 := position, tokenIndex - if c := buffer[position]; c < rune('0') || c > rune('9') { + position467, tokenIndex467 := position, tokenIndex + if c := buffer[position]; c < rune('a') || c > rune('z') { goto l468 } position++ goto l467 l468: - position, tokenIndex = position468, tokenIndex468 + position, tokenIndex = position467, tokenIndex467 + if c := buffer[position]; c < rune('A') || c > rune('Z') { + goto l465 + } + position++ } - add(ruledigits, position466) + l467: + l469: + { + position470, tokenIndex470 := position, tokenIndex + { + position471, tokenIndex471 := position, tokenIndex + if c := buffer[position]; c < rune('a') || c > rune('z') { + goto l472 + } + position++ + goto l471 + l472: + position, tokenIndex = position471, tokenIndex471 + if c := buffer[position]; c < rune('A') || c > rune('Z') { + goto l473 + } + position++ + goto l471 + l473: + position, tokenIndex = position471, tokenIndex471 + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l470 + } + position++ + } + l471: + goto l469 + l470: + position, tokenIndex = position470, tokenIndex470 + } + add(ruleIDENT, position466) } return true l465: position, tokenIndex = position465, tokenIndex465 return false }, + /* 29 digits <- <[0-9]+> */ + func() bool { + position474, tokenIndex474 := position, tokenIndex + { + position475 := position + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l474 + } + position++ + l476: + { + position477, tokenIndex477 := position, tokenIndex + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l477 + } + position++ + goto l476 + l477: + position, tokenIndex = position477, tokenIndex477 + } + add(ruledigits, position475) + } + return true + l474: + position, tokenIndex = position474, tokenIndex474 + return false + }, /* 30 signedDigits <- <('-'? digits)> */ nil, /* 31 decimal <- <((signedDigits ('.' digits?)?) / ('-'? '.' digits))> */ func() bool { - position470, tokenIndex470 := position, tokenIndex + position479, tokenIndex479 := position, tokenIndex { - position471 := position + position480 := position { - position472, tokenIndex472 := position, tokenIndex + position481, tokenIndex481 := position, tokenIndex { - position474 := position + position483 := position { - position475, tokenIndex475 := position, tokenIndex + position484, tokenIndex484 := position, tokenIndex if buffer[position] != rune('-') { - goto l475 + goto l484 } position++ - goto l476 - l475: - position, tokenIndex = position475, tokenIndex475 + goto l485 + l484: + position, tokenIndex = position484, tokenIndex484 } - l476: + l485: if !_rules[ruledigits]() { - goto l473 + goto l482 } - add(rulesignedDigits, position474) + add(rulesignedDigits, position483) } { - position477, tokenIndex477 := position, tokenIndex + position486, tokenIndex486 := position, tokenIndex if buffer[position] != rune('.') { - goto l477 + goto l486 } position++ { - position479, tokenIndex479 := position, tokenIndex + position488, tokenIndex488 := position, tokenIndex if !_rules[ruledigits]() { - goto l479 + goto l488 } - goto l480 - l479: - position, tokenIndex = position479, tokenIndex479 + goto l489 + l488: + position, tokenIndex = position488, tokenIndex488 } - l480: - goto l478 - l477: - position, tokenIndex = position477, tokenIndex477 + l489: + goto l487 + l486: + position, tokenIndex = position486, tokenIndex486 } - l478: - goto l472 - l473: - position, tokenIndex = position472, tokenIndex472 + l487: + goto l481 + l482: + position, tokenIndex = position481, tokenIndex481 { - position481, tokenIndex481 := position, tokenIndex + position490, tokenIndex490 := position, tokenIndex if buffer[position] != rune('-') { - goto l481 + goto l490 } position++ - goto l482 - l481: - position, tokenIndex = position481, tokenIndex481 + goto l491 + l490: + position, tokenIndex = position490, tokenIndex490 } - l482: + l491: if buffer[position] != rune('.') { - goto l470 + goto l479 } position++ if !_rules[ruledigits]() { - goto l470 + goto l479 } } - l472: - add(ruledecimal, position471) + l481: + add(ruledecimal, position480) } return true - l470: - position, tokenIndex = position470, tokenIndex470 + l479: + position, tokenIndex = position479, tokenIndex479 return false }, - /* 32 timestampbasicfmt <- <([0-9] [0-9] [0-9] [0-9] '-' ('0' / '1') [0-9] '-' [0-3] [0-9] 'T' [0-9] [0-9] ':' [0-9] [0-9])> */ + /* 32 tz <- <('Z' / ('-' [0-9] [0-9] ':' [0-9] [0-9]) / ('+' [0-9] [0-9] ':' [0-9] [0-9]))> */ func() bool { - position483, tokenIndex483 := position, tokenIndex + position492, tokenIndex492 := position, tokenIndex { - position484 := position + position493 := position + { + position494, tokenIndex494 := position, tokenIndex + if buffer[position] != rune('Z') { + goto l495 + } + position++ + goto l494 + l495: + position, tokenIndex = position494, tokenIndex494 + if buffer[position] != rune('-') { + goto l496 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l496 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l496 + } + position++ + if buffer[position] != rune(':') { + goto l496 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l496 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l496 + } + position++ + goto l494 + l496: + position, tokenIndex = position494, tokenIndex494 + if buffer[position] != rune('+') { + goto l492 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l492 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l492 + } + position++ + if buffer[position] != rune(':') { + goto l492 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l492 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l492 + } + position++ + } + l494: + add(ruletz, position493) + } + return true + l492: + position, tokenIndex = position492, tokenIndex492 + return false + }, + /* 33 iso8601 <- <([0-9] [0-9] [0-9] [0-9] '-' ('0' / '1') [0-9] '-' [0-3] [0-9] 'T' [0-9] [0-9] ':' [0-9] [0-9] ':' [0-9] [0-9] )> */ + nil, + /* 34 iso8601nano <- <([0-9] [0-9] [0-9] [0-9] '-' ('0' / '1') [0-9] '-' [0-3] [0-9] 'T' [0-9] [0-9] ':' [0-9] [0-9] ':' [0-9] [0-9] '.' [0-9]+ )> */ + nil, + /* 35 timestampbasicfmt <- <(iso8601nano / iso8601)> */ + func() bool { + position499, tokenIndex499 := position, tokenIndex + { + position500 := position + { + position501, tokenIndex501 := position, tokenIndex + { + position503 := position + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l502 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l502 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l502 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l502 + } + position++ + if buffer[position] != rune('-') { + goto l502 + } + position++ + { + position504, tokenIndex504 := position, tokenIndex + if buffer[position] != rune('0') { + goto l505 + } + position++ + goto l504 + l505: + position, tokenIndex = position504, tokenIndex504 + if buffer[position] != rune('1') { + goto l502 + } + position++ + } + l504: + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l502 + } + position++ + if buffer[position] != rune('-') { + goto l502 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('3') { + goto l502 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l502 + } + position++ + if buffer[position] != rune('T') { + goto l502 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l502 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l502 + } + position++ + if buffer[position] != rune(':') { + goto l502 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l502 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l502 + } + position++ + if buffer[position] != rune(':') { + goto l502 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l502 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l502 + } + position++ + if buffer[position] != rune('.') { + goto l502 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l502 + } + position++ + l506: + { + position507, tokenIndex507 := position, tokenIndex + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l507 + } + position++ + goto l506 + l507: + position, tokenIndex = position507, tokenIndex507 + } + { + position508 := position + if !_rules[ruletz]() { + goto l502 + } + add(rulePegText, position508) + } + add(ruleiso8601nano, position503) + } + goto l501 + l502: + position, tokenIndex = position501, tokenIndex501 + { + position509 := position + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l499 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l499 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l499 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l499 + } + position++ + if buffer[position] != rune('-') { + goto l499 + } + position++ + { + position510, tokenIndex510 := position, tokenIndex + if buffer[position] != rune('0') { + goto l511 + } + position++ + goto l510 + l511: + position, tokenIndex = position510, tokenIndex510 + if buffer[position] != rune('1') { + goto l499 + } + position++ + } + l510: + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l499 + } + position++ + if buffer[position] != rune('-') { + goto l499 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('3') { + goto l499 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l499 + } + position++ + if buffer[position] != rune('T') { + goto l499 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l499 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l499 + } + position++ + if buffer[position] != rune(':') { + goto l499 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l499 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l499 + } + position++ + if buffer[position] != rune(':') { + goto l499 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l499 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l499 + } + position++ + { + position512 := position + if !_rules[ruletz]() { + goto l499 + } + add(rulePegText, position512) + } + add(ruleiso8601, position509) + } + } + l501: + add(ruletimestampbasicfmt, position500) + } + return true + l499: + position, tokenIndex = position499, tokenIndex499 + return false + }, + /* 36 timestampfmt <- <(('"' '"') / ('\'' '\'') / )> */ + nil, + /* 37 timebasicfmt <- <([0-9] [0-9] [0-9] [0-9] '-' ('0' / '1') [0-9] '-' [0-3] [0-9] 'T' [0-9] [0-9] ':' [0-9] [0-9])> */ + func() bool { + position514, tokenIndex514 := position, tokenIndex + { + position515 := position if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l483 + goto l514 } position++ if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l483 + goto l514 } position++ if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l483 + goto l514 } position++ if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l483 + goto l514 } position++ if buffer[position] != rune('-') { - goto l483 + goto l514 } position++ { - position485, tokenIndex485 := position, tokenIndex + position516, tokenIndex516 := position, tokenIndex if buffer[position] != rune('0') { - goto l486 + goto l517 } position++ - goto l485 - l486: - position, tokenIndex = position485, tokenIndex485 + goto l516 + l517: + position, tokenIndex = position516, tokenIndex516 if buffer[position] != rune('1') { - goto l483 + goto l514 } position++ } - l485: + l516: if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l483 + goto l514 } position++ if buffer[position] != rune('-') { - goto l483 + goto l514 } position++ if c := buffer[position]; c < rune('0') || c > rune('3') { - goto l483 + goto l514 } position++ if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l483 + goto l514 } position++ if buffer[position] != rune('T') { - goto l483 + goto l514 } position++ if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l483 + goto l514 } position++ if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l483 + goto l514 } position++ if buffer[position] != rune(':') { - goto l483 + goto l514 } position++ if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l483 + goto l514 } position++ if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l483 + goto l514 } position++ - add(ruletimestampbasicfmt, position484) + add(ruletimebasicfmt, position515) } return true - l483: - position, tokenIndex = position483, tokenIndex483 + l514: + position, tokenIndex = position514, tokenIndex514 return false }, - /* 33 timestampfmt <- <(('"' '"') / ('\'' '\'') / )> */ + /* 38 timefmt <- <(('"' '"') / ('\'' '\'') / )> */ func() bool { - position487, tokenIndex487 := position, tokenIndex + position518, tokenIndex518 := position, tokenIndex { - position488 := position + position519 := position { - position489, tokenIndex489 := position, tokenIndex + position520, tokenIndex520 := position, tokenIndex if buffer[position] != rune('"') { - goto l490 + goto l521 } position++ { - position491 := position - if !_rules[ruletimestampbasicfmt]() { - goto l490 + position522 := position + if !_rules[ruletimebasicfmt]() { + goto l521 } - add(rulePegText, position491) + add(rulePegText, position522) } if buffer[position] != rune('"') { - goto l490 + goto l521 } position++ - goto l489 - l490: - position, tokenIndex = position489, tokenIndex489 + goto l520 + l521: + position, tokenIndex = position520, tokenIndex520 if buffer[position] != rune('\'') { - goto l492 + goto l523 } position++ { - position493 := position - if !_rules[ruletimestampbasicfmt]() { - goto l492 + position524 := position + if !_rules[ruletimebasicfmt]() { + goto l523 } - add(rulePegText, position493) + add(rulePegText, position524) } if buffer[position] != rune('\'') { - goto l492 + goto l523 } position++ - goto l489 - l492: - position, tokenIndex = position489, tokenIndex489 + goto l520 + l523: + position, tokenIndex = position520, tokenIndex520 { - position494 := position - if !_rules[ruletimestampbasicfmt]() { - goto l487 + position525 := position + if !_rules[ruletimebasicfmt]() { + goto l518 } - add(rulePegText, position494) + add(rulePegText, position525) } } - l489: - add(ruletimestampfmt, position488) + l520: + add(ruletimefmt, position519) } return true - l487: - position, tokenIndex = position487, tokenIndex487 + l518: + position, tokenIndex = position518, tokenIndex518 return false }, - /* 34 timestamp <- <( Action66)> */ + /* 39 time <- <( Action67)> */ nil, - /* 36 Action0 <- <{p.startCall("Set")}> */ + /* 41 Action0 <- <{p.startCall("Set")}> */ nil, - /* 37 Action1 <- <{p.endCall()}> */ + /* 42 Action1 <- <{p.endCall()}> */ nil, - /* 38 Action2 <- <{p.startCall("SetRowAttrs")}> */ + /* 43 Action2 <- <{p.startCall("SetRowAttrs")}> */ nil, - /* 39 Action3 <- <{p.endCall()}> */ + /* 44 Action3 <- <{p.endCall()}> */ nil, - /* 40 Action4 <- <{p.startCall("SetColumnAttrs")}> */ + /* 45 Action4 <- <{p.startCall("SetColumnAttrs")}> */ nil, - /* 41 Action5 <- <{p.endCall()}> */ + /* 46 Action5 <- <{p.endCall()}> */ nil, - /* 42 Action6 <- <{p.startCall("Clear")}> */ + /* 47 Action6 <- <{p.startCall("Clear")}> */ nil, - /* 43 Action7 <- <{p.endCall()}> */ + /* 48 Action7 <- <{p.endCall()}> */ nil, - /* 44 Action8 <- <{p.startCall("ClearRow")}> */ + /* 49 Action8 <- <{p.startCall("ClearRow")}> */ nil, - /* 45 Action9 <- <{p.endCall()}> */ + /* 50 Action9 <- <{p.endCall()}> */ nil, - /* 46 Action10 <- <{p.startCall("Store")}> */ + /* 51 Action10 <- <{p.startCall("Store")}> */ nil, - /* 47 Action11 <- <{p.endCall()}> */ + /* 52 Action11 <- <{p.endCall()}> */ nil, - /* 48 Action12 <- <{p.startCall("TopN")}> */ + /* 53 Action12 <- <{p.startCall("TopN")}> */ nil, - /* 49 Action13 <- <{p.endCall()}> */ + /* 54 Action13 <- <{p.endCall()}> */ nil, - /* 50 Action14 <- <{p.startCall("TopK")}> */ + /* 55 Action14 <- <{p.startCall("TopK")}> */ nil, - /* 51 Action15 <- <{p.endCall()}> */ + /* 56 Action15 <- <{p.endCall()}> */ nil, - /* 52 Action16 <- <{p.startCall("Percentile")}> */ + /* 57 Action16 <- <{p.startCall("Percentile")}> */ nil, - /* 53 Action17 <- <{p.endCall()}> */ + /* 58 Action17 <- <{p.endCall()}> */ nil, - /* 54 Action18 <- <{p.startCall("Rows")}> */ + /* 59 Action18 <- <{p.startCall("Rows")}> */ nil, - /* 55 Action19 <- <{p.endCall()}> */ + /* 60 Action19 <- <{p.endCall()}> */ nil, - /* 56 Action20 <- <{p.startCall("Min")}> */ + /* 61 Action20 <- <{p.startCall("Min")}> */ nil, - /* 57 Action21 <- <{p.endCall()}> */ + /* 62 Action21 <- <{p.endCall()}> */ nil, - /* 58 Action22 <- <{p.startCall("Max")}> */ + /* 63 Action22 <- <{p.startCall("Max")}> */ nil, - /* 59 Action23 <- <{p.endCall()}> */ + /* 64 Action23 <- <{p.endCall()}> */ nil, - /* 60 Action24 <- <{p.startCall("Sum")}> */ + /* 65 Action24 <- <{p.startCall("Sum")}> */ nil, - /* 61 Action25 <- <{p.endCall()}> */ + /* 66 Action25 <- <{p.endCall()}> */ nil, - /* 62 Action26 <- <{p.startCall("Range")}> */ + /* 67 Action26 <- <{p.startCall("Range")}> */ nil, - /* 63 Action27 <- <{p.addField("from")}> */ + /* 68 Action27 <- <{p.addField("from")}> */ nil, - /* 64 Action28 <- <{p.addVal(text)}> */ + /* 69 Action28 <- <{p.addVal(text)}> */ nil, - /* 65 Action29 <- <{p.addField("to")}> */ + /* 70 Action29 <- <{p.addField("to")}> */ nil, - /* 66 Action30 <- <{p.addVal(text)}> */ + /* 71 Action30 <- <{p.addVal(text)}> */ nil, - /* 67 Action31 <- <{p.endCall()}> */ + /* 72 Action31 <- <{p.endCall()}> */ nil, nil, - /* 69 Action32 <- <{ p.startCall(text) }> */ + /* 74 Action32 <- <{ p.startCall(text) }> */ nil, - /* 70 Action33 <- <{ p.endCall() }> */ + /* 75 Action33 <- <{ p.endCall() }> */ nil, - /* 71 Action34 <- <{ p.addBTWN() }> */ + /* 76 Action34 <- <{ p.addBTWN() }> */ nil, - /* 72 Action35 <- <{ p.addLTE() }> */ + /* 77 Action35 <- <{ p.addLTE() }> */ nil, - /* 73 Action36 <- <{ p.addGTE() }> */ + /* 78 Action36 <- <{ p.addGTE() }> */ nil, - /* 74 Action37 <- <{ p.addEQ() }> */ + /* 79 Action37 <- <{ p.addEQ() }> */ nil, - /* 75 Action38 <- <{ p.addNEQ() }> */ + /* 80 Action38 <- <{ p.addNEQ() }> */ nil, - /* 76 Action39 <- <{ p.addLT() }> */ + /* 81 Action39 <- <{ p.addLT() }> */ nil, - /* 77 Action40 <- <{ p.addGT() }> */ + /* 82 Action40 <- <{ p.addGT() }> */ nil, - /* 78 Action41 <- <{p.startConditional()}> */ + /* 83 Action41 <- <{p.startConditional()}> */ nil, - /* 79 Action42 <- <{p.endConditional()}> */ + /* 84 Action42 <- <{p.endConditional()}> */ nil, - /* 80 Action43 <- <{p.condAdd(text)}> */ + /* 85 Action43 <- <{p.condAdd(text)}> */ nil, - /* 81 Action44 <- <{p.condAdd(text)}> */ + /* 86 Action44 <- <{p.condAdd(text)}> */ nil, - /* 82 Action45 <- <{p.condAdd(text)}> */ + /* 87 Action45 <- <{p.condAdd(text)}> */ nil, - /* 83 Action46 <- <{ p.startList() }> */ + /* 88 Action46 <- <{ p.startList() }> */ nil, - /* 84 Action47 <- <{ p.endList() }> */ + /* 89 Action47 <- <{ p.endList() }> */ nil, - /* 85 Action48 <- <{ p.addVal(nil) }> */ + /* 90 Action48 <- <{ p.addVal(nil) }> */ nil, - /* 86 Action49 <- <{ p.addVal(true) }> */ + /* 91 Action49 <- <{ p.addVal(true) }> */ nil, - /* 87 Action50 <- <{ p.addVal(false) }> */ + /* 92 Action50 <- <{ p.addVal(false) }> */ nil, - /* 88 Action51 <- <{ p.addVal(text) }> */ + /* 93 Action51 <- <{ p.addVal(text) }> */ nil, - /* 89 Action52 <- <{ p.addNumVal(text) }> */ + /* 94 Action52 <- <{ p.addTimestampVal(text) }> */ nil, - /* 90 Action53 <- <{ p.startCall(text) }> */ + /* 95 Action53 <- <{ p.addNumVal(text) }> */ nil, - /* 91 Action54 <- <{ p.addVal(p.endCall()) }> */ + /* 96 Action54 <- <{ p.startCall(text) }> */ nil, - /* 92 Action55 <- <{ p.addVal(text) }> */ + /* 97 Action55 <- <{ p.addVal(p.endCall()) }> */ nil, - /* 93 Action56 <- <{ p.addVal(text) }> */ + /* 98 Action56 <- <{ p.addVal(text) }> */ nil, - /* 94 Action57 <- <{ p.addVal(text) }> */ + /* 99 Action57 <- <{ p.addVal(text) }> */ nil, - /* 95 Action58 <- <{ p.addField(text) }> */ + /* 100 Action58 <- <{ p.addVal(text) }> */ nil, - /* 96 Action59 <- <{ p.addPosStr("_field", text) }> */ + /* 101 Action59 <- <{ p.addField(text) }> */ nil, - /* 97 Action60 <- <{p.addPosNum("_col", text)}> */ + /* 102 Action60 <- <{ p.addPosStr("_field", text) }> */ nil, - /* 98 Action61 <- <{p.addPosStr("_col", text)}> */ + /* 103 Action61 <- <{p.addPosNum("_col", text)}> */ nil, - /* 99 Action62 <- <{p.addPosStr("_col", text)}> */ + /* 104 Action62 <- <{p.addPosStr("_col", text)}> */ nil, - /* 100 Action63 <- <{p.addPosNum("_row", text)}> */ + /* 105 Action63 <- <{p.addPosStr("_col", text)}> */ nil, - /* 101 Action64 <- <{p.addPosStr("_row", text)}> */ + /* 106 Action64 <- <{p.addPosNum("_row", text)}> */ nil, - /* 102 Action65 <- <{p.addPosStr("_row", text)}> */ + /* 107 Action65 <- <{p.addPosStr("_row", text)}> */ nil, - /* 103 Action66 <- <{p.addPosStr("_timestamp", text)}> */ + /* 108 Action66 <- <{p.addPosStr("_row", text)}> */ + nil, + /* 109 Action67 <- <{p.addPosStr("_timestamp", text)}> */ nil, } p.rules = _rules diff --git a/pql/pqlpeg_test.go b/pql/pqlpeg_test.go index a9525ea77..037d6ee42 100644 --- a/pql/pqlpeg_test.go +++ b/pql/pqlpeg_test.go @@ -142,6 +142,22 @@ func TestPEGWorking(t *testing.T) { name: "SetTimestamp", input: "Set(1, a=4, 2017-04-03T19:34)", ncalls: 1}, + { + name: "SetTimestampField", + input: "Set(1, a='2017-04-03T19:34:00Z')", + ncalls: 1}, + { + name: "SetTimestampTZField", + input: "Set(1, a='2017-04-03T19:34:00-07:00')", + ncalls: 1}, + { + name: "SetTimestampTZField", + input: "Set(1, a='2017-04-03T19:34:00+07:00')", + ncalls: 1}, + { + name: "SetTimestampNanoField", + input: "Set(1, a='2017-04-03T19:34:00.000000Z')", + ncalls: 1}, { name: "Union()", input: "Union()", diff --git a/sql/extract.go b/sql/extract.go index 5f927ed41..85c3447ef 100644 --- a/sql/extract.go +++ b/sql/extract.go @@ -19,6 +19,7 @@ import ( "reflect" "strconv" "strings" + "time" "github.com/pilosa/pilosa/v2" "github.com/pilosa/pilosa/v2/pql" @@ -251,7 +252,7 @@ func extractWhere(index *pilosa.Index, expr sqlparser.Expr) (string, error) { } switch field.Type() { - case pilosa.FieldTypeInt, pilosa.FieldTypeDecimal: + case pilosa.FieldTypeInt, pilosa.FieldTypeDecimal, pilosa.FieldTypeTimestamp: switch op { case "=": return Equals(field.Name(), val), nil @@ -365,6 +366,16 @@ func extractWhere(index *pilosa.Index, expr sqlparser.Expr) (string, error) { return "", err } return Between(field.Name(), fromNum, toNum), nil + case pilosa.FieldTypeTimestamp: + fromTime, err := extractTimestamp(e.From) + if err != nil { + return "", err + } + toTime, err := extractTimestamp(e.To) + if err != nil { + return "", err + } + return Between(field.Name(), fromTime, toTime), nil default: return "", errors.New("only int and float64 fields are supported") } @@ -374,13 +385,13 @@ func extractWhere(index *pilosa.Index, expr sqlparser.Expr) (string, error) { return "", errors.New("left operand must be a column name") } field := index.Field(left.Name.String()) - if field.Type() == pilosa.FieldTypeInt { + if field.Type() == pilosa.FieldTypeInt || field.Type() == pilosa.FieldTypeTimestamp { if e.Operator == "is not null" { return NotNull(field.Name()), nil } - return "", errors.New("only `is not null` is supported for int fields") + return "", fmt.Errorf("only `is not null` is supported for %s fields", field.Type()) } - return "", errors.New("`is` expression is supported only for int fields") + return "", fmt.Errorf("`is` expression is supported only for %s fields", field.Type()) } return "", errors.New("cannot extract where") } @@ -523,6 +534,22 @@ func extractFloat(e sqlparser.Expr) (float64, error) { return num, nil } +func extractTimestamp(e sqlparser.Expr) (time.Time, error) { + val, err := extractVal(e) + if err != nil { + return time.Time{}, err + } + s, ok := val.(string) + if !ok { + return time.Time{}, errors.New("value must be an ISO 8601-formated timestamp string") + } + t, err := time.Parse(time.RFC3339Nano, s) + if err != nil { + return time.Time{}, errors.New("value must be an ISO 8601-formated timestamp string") + } + return t, nil +} + func extractStr(e sqlparser.Expr) (string, error) { val, err := extractVal(e) if err != nil { @@ -961,26 +988,32 @@ func extractWheres(indexes []*pilosa.Index, tbls parseTables, expr sqlparser.Exp table: pTable, } - if field.Type() == pilosa.FieldTypeInt { - num, ok := val.(int) - if !ok { - return nil, errors.New("right operand must be a number") + if field.Type() == pilosa.FieldTypeInt || field.Type() == pilosa.FieldTypeTimestamp { + if field.Type() == pilosa.FieldTypeInt { + if _, ok := val.(int); !ok { + return nil, errors.New("right operand must be a number") + } + } else { // timestamp + if _, ok := val.(time.Time); !ok { + return nil, errors.New("right operand must be a timestamp") + } } + switch op { case "=": - tw.where = Equals(field.Name(), num) + tw.where = Equals(field.Name(), val) case "<": - tw.where = LT(field.Name(), num) + tw.where = LT(field.Name(), val) case "<=": - tw.where = LTE(field.Name(), num) + tw.where = LTE(field.Name(), val) case ">": - tw.where = GT(field.Name(), num) + tw.where = GT(field.Name(), val) case ">=": - tw.where = GTE(field.Name(), num) + tw.where = GTE(field.Name(), val) case "<>": fallthrough case "!=": - tw.where = NotEquals(field.Name(), num) + tw.where = NotEquals(field.Name(), val) } return append(wheres, tw), nil } @@ -1148,7 +1181,19 @@ func extractWheres(indexes []*pilosa.Index, tbls parseTables, expr sqlparser.Exp tw.where = Between(field.Name(), fromNum, toNum) return append(wheres, tw), nil } - return nil, errors.New("only int fields are supported") + if field.Type() == pilosa.FieldTypeTimestamp { + fromTime, err := extractTimestamp(e.From) + if err != nil { + return nil, err + } + toTime, err := extractTimestamp(e.To) + if err != nil { + return nil, err + } + tw.where = Between(field.Name(), fromTime, toTime) + return append(wheres, tw), nil + } + return nil, errors.New("only int or timestamp fields are supported") case *sqlparser.IsExpr: left, ok := e.Expr.(*sqlparser.ColName) if !ok { @@ -1172,14 +1217,14 @@ func extractWheres(indexes []*pilosa.Index, tbls parseTables, expr sqlparser.Exp } field := pTable.index.Field(pCol.name) - if field.Type() == pilosa.FieldTypeInt { + if field.Type() == pilosa.FieldTypeInt || field.Type() == pilosa.FieldTypeTimestamp { if e.Operator == "is not null" { tw.where = NotNull(field.Name()) return append(wheres, tw), nil } - return nil, errors.New("only `is not null` is supported for int fields") + return nil, fmt.Errorf("only `is not null` is supported for %s fields", field.Type()) } - return nil, errors.New("`is` expression is supported only for int fields") + return nil, fmt.Errorf("`is` expression is supported only for %s fields", field.Type()) } return nil, errors.New("cannot extract where") } diff --git a/sql/select.go b/sql/select.go index b403f80c9..bf4165c10 100644 --- a/sql/select.go +++ b/sql/select.go @@ -335,7 +335,7 @@ func (h handlerSelectDistinctFromTable) Apply(stmt *sqlparser.Select, qm QueryMa // Otherwise, use Rows() // TODO: ensure this works for all field types (bool, time, etc). var qo string - if fieldCol.Field.Type() == pilosa.FieldTypeInt { + if fieldCol.Field.Type() == pilosa.FieldTypeInt || fieldCol.Field.Type() == pilosa.FieldTypeTimestamp { qo = Distinct(fieldCol.Field.Index(), fieldCol.Field.Name()) } else { if !qm.HasOrderBy() && limit > 0 { diff --git a/view.go b/view.go index c2827a38c..093b0c2e9 100644 --- a/view.go +++ b/view.go @@ -273,7 +273,7 @@ fragLoop: // flags returns a set of flags for the underlying fragments. func (v *view) flags() byte { var flag byte - if v.fieldType == FieldTypeInt || v.fieldType == FieldTypeDecimal { + if v.fieldType == FieldTypeInt || v.fieldType == FieldTypeDecimal || v.fieldType == FieldTypeTimestamp { flag |= roaringFlagBSIv2 } return flag