func (e *StatementExecutor) executeCreateContinuousQueryStatement(q *influxql.CreateContinuousQueryStatement) error { // Verify that retention policies exist. var err error verifyRPFn := func(n influxql.Node) { if err != nil { return } switch m := n.(type) { case *influxql.Measurement: var rp *meta.RetentionPolicyInfo if rp, err = e.MetaClient.RetentionPolicy(m.Database, m.RetentionPolicy); err != nil { return } else if rp == nil { err = fmt.Errorf("%s: %s.%s", meta.ErrRetentionPolicyNotFound, m.Database, m.RetentionPolicy) } default: return } } influxql.WalkFunc(q, verifyRPFn) if err != nil { return err } return e.MetaClient.CreateContinuousQuery(q.Database, q.Name, q.String()) }
// NewSeriesIterator returns a new instance of SeriesIterator. func NewSeriesIterator(sh *Shard, opt influxql.IteratorOptions) (influxql.Iterator, error) { // Only equality operators are allowed. var err error influxql.WalkFunc(opt.Condition, func(n influxql.Node) { switch n := n.(type) { case *influxql.BinaryExpr: switch n.Op { case influxql.EQ, influxql.NEQ, influxql.EQREGEX, influxql.NEQREGEX, influxql.OR, influxql.AND: default: err = errors.New("invalid tag comparison operator") } } }) if err != nil { return nil, err } // Read and sort all measurements. mms := sh.index.Measurements() sort.Sort(mms) return &seriesIterator{ mms: mms, point: influxql.FloatPoint{ Aux: make([]interface{}, len(opt.Aux)), }, opt: opt, }, nil }
// NormalizeStatement adds a default database and policy to the measurements in statement. func (e *StatementExecutor) NormalizeStatement(stmt influxql.Statement, defaultDatabase string) (err error) { influxql.WalkFunc(stmt, func(node influxql.Node) { if err != nil { return } switch node := node.(type) { case *influxql.ShowRetentionPoliciesStatement: if node.Database == "" { node.Database = defaultDatabase } case *influxql.ShowMeasurementsStatement: if node.Database == "" { node.Database = defaultDatabase } case *influxql.ShowTagValuesStatement: if node.Database == "" { node.Database = defaultDatabase } case *influxql.Measurement: switch stmt.(type) { case *influxql.DropSeriesStatement, *influxql.DeleteSeriesStatement: // DB and RP not supported by these statements so don't rewrite into invalid // statements default: err = e.normalizeMeasurement(node, defaultDatabase) } } }) return }
// NormalizeStatement adds a default database and policy to the measurements in statement. func (e *StatementExecutor) NormalizeStatement(stmt influxql.Statement, defaultDatabase string) (err error) { influxql.WalkFunc(stmt, func(node influxql.Node) { if err != nil { return } switch node := node.(type) { case *influxql.Measurement: err = e.normalizeMeasurement(node, defaultDatabase) } }) return }
// uniqueTagValues returns a list of unique tag values used in an expression. func (m *Measurement) uniqueTagValues(expr influxql.Expr) map[string][]string { // Track unique value per tag. tags := make(map[string]map[string]struct{}) // Find all tag values referenced in the expression. influxql.WalkFunc(expr, func(n influxql.Node) { switch n := n.(type) { case *influxql.BinaryExpr: // Ignore operators that are not equality. if n.Op != influxql.EQ { return } // Extract ref and string literal. var key, value string switch lhs := n.LHS.(type) { case *influxql.VarRef: if rhs, ok := n.RHS.(*influxql.StringLiteral); ok { key, value = lhs.Val, rhs.Val } case *influxql.StringLiteral: if rhs, ok := n.RHS.(*influxql.VarRef); ok { key, value = rhs.Val, lhs.Val } } if key == "" { return } // Add value to set. if tags[key] == nil { tags[key] = make(map[string]struct{}) } tags[key][value] = struct{}{} } }) // Convert to map of slices. out := make(map[string][]string) for k, values := range tags { out[k] = make([]string, 0, len(values)) for v := range values { out[k] = append(out[k], v) } sort.Strings(out[k]) } return out }
// Deep clone this query func (q *Query) Clone() (*Query, error) { n := &Query{ stmt: q.stmt.Clone(), isGroupedByTime: q.isGroupedByTime, } // Find the start/stop time literals var err error influxql.WalkFunc(n.stmt.Condition, func(qlNode influxql.Node) { if bn, ok := qlNode.(*influxql.BinaryExpr); ok { switch bn.Op { case influxql.GTE: if vf, ok := bn.LHS.(*influxql.VarRef); !ok || vf.Val != "time" { return } if tl, ok := bn.RHS.(*influxql.TimeLiteral); ok { // We have a "time" >= 'time literal' if n.startTL == nil { n.startTL = tl } else { err = errors.New("invalid query, found multiple start time conditions") } } case influxql.LT: if vf, ok := bn.LHS.(*influxql.VarRef); !ok || vf.Val != "time" { return } if tl, ok := bn.RHS.(*influxql.TimeLiteral); ok { // We have a "time" < 'time literal' if n.stopTL == nil { n.stopTL = tl } else { err = errors.New("invalid query, found multiple stop time conditions") } } } } }) if n.startTL == nil { err = errors.New("invalid query, missing start time condition") } if n.stopTL == nil { err = errors.New("invalid query, missing stop time condition") } return n, err }
// normalizeStatement adds a default database and policy to the measurements in statement. func (q *QueryExecutor) normalizeStatement(stmt influxql.Statement, defaultDatabase string) (err error) { // Track prefixes for replacing field names. prefixes := make(map[string]string) // Qualify all measurements. influxql.WalkFunc(stmt, func(n influxql.Node) { if err != nil { return } switch n := n.(type) { case *influxql.Measurement: e := q.normalizeMeasurement(n, defaultDatabase) if e != nil { err = e return } prefixes[n.Name] = n.Name } }) return }
// NewSeriesIterator returns a new instance of SeriesIterator. func NewSeriesIterator(sh *Shard, opt influxql.IteratorOptions) (influxql.Iterator, error) { // Retrieve a list of all measurements. mms := sh.index.Measurements() sort.Sort(mms) // Only equality operators are allowed. var err error influxql.WalkFunc(opt.Condition, func(n influxql.Node) { switch n := n.(type) { case *influxql.BinaryExpr: switch n.Op { case influxql.EQ, influxql.NEQ, influxql.EQREGEX, influxql.NEQREGEX, influxql.OR, influxql.AND: default: err = errors.New("invalid tag comparison operator") } } }) if err != nil { return nil, err } // Generate a list of all series keys. keys := newStringSet() for _, mm := range mms { ids, err := mm.seriesIDsAllOrByExpr(opt.Condition) if err != nil { return nil, err } for _, id := range ids { keys.add(mm.SeriesByID(id).Key) } } return &seriesIterator{ keys: keys.list(), fields: opt.Aux, }, nil }
// Ensure the SELECT statement can have its start and end time set func TestSelectStatement_SetTimeRange(t *testing.T) { q := "SELECT sum(value) from foo where time < now() GROUP BY time(10m)" stmt, err := influxql.NewParser(strings.NewReader(q)).ParseStatement() if err != nil { t.Fatalf("invalid statement: %q: %s", stmt, err) } s := stmt.(*influxql.SelectStatement) start := time.Now().Add(-20 * time.Hour).Round(time.Second).UTC() end := time.Now().Add(10 * time.Hour).Round(time.Second).UTC() s.SetTimeRange(start, end) min, max := MustTimeRange(s.Condition) if min != start { t.Fatalf("start time wasn't set properly.\n exp: %s\n got: %s", start, min) } // the end range is actually one nanosecond before the given one since end is exclusive end = end.Add(-time.Nanosecond) if max != end { t.Fatalf("end time wasn't set properly.\n exp: %s\n got: %s", end, max) } // ensure we can set a time on a select that already has one set start = time.Now().Add(-20 * time.Hour).Round(time.Second).UTC() end = time.Now().Add(10 * time.Hour).Round(time.Second).UTC() q = fmt.Sprintf("SELECT sum(value) from foo WHERE time >= %ds and time <= %ds GROUP BY time(10m)", start.Unix(), end.Unix()) stmt, err = influxql.NewParser(strings.NewReader(q)).ParseStatement() if err != nil { t.Fatalf("invalid statement: %q: %s", stmt, err) } s = stmt.(*influxql.SelectStatement) min, max = MustTimeRange(s.Condition) if start != min || end != max { t.Fatalf("start and end times weren't equal:\n exp: %s\n got: %s\n exp: %s\n got:%s\n", start, min, end, max) } // update and ensure it saves it start = time.Now().Add(-40 * time.Hour).Round(time.Second).UTC() end = time.Now().Add(20 * time.Hour).Round(time.Second).UTC() s.SetTimeRange(start, end) min, max = MustTimeRange(s.Condition) // TODO: right now the SetTimeRange can't override the start time if it's more recent than what they're trying to set it to. // shouldn't matter for our purposes with continuous queries, but fix this later if min != start { t.Fatalf("start time wasn't set properly.\n exp: %s\n got: %s", start, min) } // the end range is actually one nanosecond before the given one since end is exclusive end = end.Add(-time.Nanosecond) if max != end { t.Fatalf("end time wasn't set properly.\n exp: %s\n got: %s", end, max) } // ensure that when we set a time range other where clause conditions are still there q = "SELECT sum(value) from foo WHERE foo = 'bar' and time < now() GROUP BY time(10m)" stmt, err = influxql.NewParser(strings.NewReader(q)).ParseStatement() if err != nil { t.Fatalf("invalid statement: %q: %s", stmt, err) } s = stmt.(*influxql.SelectStatement) // update and ensure it saves it start = time.Now().Add(-40 * time.Hour).Round(time.Second).UTC() end = time.Now().Add(20 * time.Hour).Round(time.Second).UTC() s.SetTimeRange(start, end) min, max = MustTimeRange(s.Condition) if min != start { t.Fatalf("start time wasn't set properly.\n exp: %s\n got: %s", start, min) } // the end range is actually one nanosecond before the given one since end is exclusive end = end.Add(-time.Nanosecond) if max != end { t.Fatalf("end time wasn't set properly.\n exp: %s\n got: %s", end, max) } // ensure the where clause is there hasWhere := false influxql.WalkFunc(s.Condition, func(n influxql.Node) { if ex, ok := n.(*influxql.BinaryExpr); ok { if lhs, ok := ex.LHS.(*influxql.VarRef); ok { if lhs.Val == "foo" { if rhs, ok := ex.RHS.(*influxql.StringLiteral); ok { if rhs.Val == "bar" { hasWhere = true } } } } } }) if !hasWhere { t.Fatal("set time range cleared out the where clause") } }
// influxQueryDuration adds time WHERE clauses to query for the given start and end durations. func influxQueryDuration(now time.Time, query, start, end, groupByInterval string) (string, error) { sd, err := opentsdb.ParseDuration(start) if err != nil { return "", err } ed, err := opentsdb.ParseDuration(end) if end == "" { ed = 0 } else if err != nil { return "", err } st, err := influxql.ParseStatement(query) if err != nil { return "", err } s, ok := st.(*influxql.SelectStatement) if !ok { return "", fmt.Errorf("influx: expected select statement") } isTime := func(n influxql.Node) bool { v, ok := n.(*influxql.VarRef) if !ok { return false } s := strings.ToLower(v.Val) return s == "time" } influxql.WalkFunc(s.Condition, func(n influxql.Node) { b, ok := n.(*influxql.BinaryExpr) if !ok { return } if isTime(b.LHS) || isTime(b.RHS) { err = fmt.Errorf("influx query must not contain time in WHERE") } }) if err != nil { return "", err } //Add New BinaryExpr for time clause startExpr := &influxql.BinaryExpr{ Op: influxql.GTE, LHS: &influxql.VarRef{Val: "time"}, RHS: &influxql.TimeLiteral{Val: now.Add(time.Duration(-sd))}, } stopExpr := &influxql.BinaryExpr{ Op: influxql.LTE, LHS: &influxql.VarRef{Val: "time"}, RHS: &influxql.TimeLiteral{Val: now.Add(time.Duration(-ed))}, } if s.Condition != nil { s.Condition = &influxql.BinaryExpr{ Op: influxql.AND, LHS: s.Condition, RHS: &influxql.BinaryExpr{ Op: influxql.AND, LHS: startExpr, RHS: stopExpr, }, } } else { s.Condition = &influxql.BinaryExpr{ Op: influxql.AND, LHS: startExpr, RHS: stopExpr, } } // parse last argument if len(groupByInterval) > 0 { gbi, err := time.ParseDuration(groupByInterval) if err != nil { return "", err } s.Dimensions = append(s.Dimensions, &influxql.Dimension{Expr: &influxql.Call{ Name: "time", Args: []influxql.Expr{&influxql.DurationLiteral{Val: gbi}}, }, }) } // emtpy aggregate windows should be purged from the result // this default resembles the opentsdb results. if s.Fill == influxql.NullFill { s.Fill = influxql.NoFill s.FillValue = nil } return s.String(), nil }