// Ensure an identifier's segments can be quoted. func TestQuoteIdent(t *testing.T) { for i, tt := range []struct { ident []string s string }{ {[]string{``}, `""`}, {[]string{`foo`, `bar`}, `"foo"."bar"`}, {[]string{`foo bar`, `baz`}, `"foo bar"."baz"`}, {[]string{`foo.bar`, `baz`}, `"foo.bar"."baz"`}, } { if s := influxql.QuoteIdent(tt.ident); tt.s != s { t.Errorf("%d. %s: mismatch: %s != %s", i, tt.ident, tt.s, s) } } }
// 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 } }) if err != nil { return err } // Replace all variable references that used measurement prefixes. influxql.WalkFunc(stmt, func(n influxql.Node) { switch n := n.(type) { case *influxql.VarRef: for k, v := range prefixes { if strings.HasPrefix(n.Val, k+".") { n.Val = v + "." + influxql.QuoteIdent(n.Val[len(k)+1:]) } } } }) return }
// CreateMappers will create a set of mappers that need to be run to execute the map phase of a MapReduceJob. func (tx *tx) CreateMapReduceJobs(stmt *influxql.SelectStatement, tagKeys []string) ([]*influxql.MapReduceJob, error) { jobs := []*influxql.MapReduceJob{} for _, src := range stmt.Sources { mm, ok := src.(*influxql.Measurement) if !ok { return nil, fmt.Errorf("invalid source type: %#v", src) } // get the index and the retention policy rp, err := tx.meta.RetentionPolicy(mm.Database, mm.RetentionPolicy) if err != nil { return nil, err } m := tx.store.Measurement(mm.Database, mm.Name) if m == nil { return nil, ErrMeasurementNotFound(influxql.QuoteIdent([]string{mm.Database, "", mm.Name}...)) } tx.measurement = m // Validate the fields and tags asked for exist and keep track of which are in the select vs the where var selectFields []string var whereFields []string var selectTags []string for _, n := range stmt.NamesInSelect() { if m.HasField(n) { selectFields = append(selectFields, n) continue } if !m.HasTagKey(n) { return nil, fmt.Errorf("unknown field or tag name in select clause: %s", n) } selectTags = append(selectTags, n) tagKeys = append(tagKeys, n) } for _, n := range stmt.NamesInWhere() { if n == "time" { continue } if m.HasField(n) { whereFields = append(whereFields, n) continue } if !m.HasTagKey(n) { return nil, fmt.Errorf("unknown field or tag name in where clause: %s", n) } } if len(selectFields) == 0 && len(stmt.FunctionCalls()) == 0 { return nil, fmt.Errorf("select statement must include at least one field or function call") } // Validate that group by is not a field for _, d := range stmt.Dimensions { switch e := d.Expr.(type) { case *influxql.VarRef: if !m.HasTagKey(e.Val) { return nil, fmt.Errorf("can not use field in group by clause: %s", e.Val) } } } // Grab time range from statement. tmin, tmax := influxql.TimeRange(stmt.Condition) if tmax.IsZero() { tmax = tx.now } if tmin.IsZero() { tmin = time.Unix(0, 0) } // Find shard groups within time range. var shardGroups []*meta.ShardGroupInfo for _, group := range rp.ShardGroups { if group.Overlaps(tmin, tmax) { g := group shardGroups = append(shardGroups, &g) } } if len(shardGroups) == 0 { return nil, nil } // get the group by interval, if there is one var interval int64 if d, err := stmt.GroupByInterval(); err != nil { return nil, err } else { interval = d.Nanoseconds() } // get the sorted unique tag sets for this query. tagSets, err := m.TagSets(stmt, tagKeys) if err != nil { return nil, err } for _, t := range tagSets { // make a job for each tagset job := &influxql.MapReduceJob{ MeasurementName: m.Name, TagSet: t, TMin: tmin.UnixNano(), TMax: tmax.UnixNano(), } // make a mapper for each shard that must be hit. We may need to hit multiple shards within a shard group var mappers []influxql.Mapper // create mappers for each shard we need to hit for _, sg := range shardGroups { // TODO: implement distributed queries if len(sg.Shards) != 1 { return nil, fmt.Errorf("distributed queries aren't supported yet. You have a replication policy with RF < # of servers in cluster") } shard := tx.store.Shard(sg.Shards[0].ID) if shard == nil { // the store returned nil which means we haven't written any data into this shard yet, so ignore it continue } // get the codec for this measuremnt. If this is nil it just means this measurement was // never written into this shard, so we can skip it and continue. codec := shard.FieldCodec(m.Name) if codec == nil { continue } var mapper influxql.Mapper mapper = &LocalMapper{ seriesKeys: t.SeriesKeys, shard: shard, db: shard.DB(), job: job, decoder: codec, filters: t.Filters, whereFields: whereFields, selectFields: selectFields, selectTags: selectTags, tmin: tmin.UnixNano(), tmax: tmax.UnixNano(), interval: interval, // multiple mappers may need to be merged together to get the results // for a raw query. So each mapper will have to read at least the // limit plus the offset in data points to ensure we've hit our mark limit: uint64(stmt.Limit) + uint64(stmt.Offset), } mappers = append(mappers, mapper) } job.Mappers = mappers jobs = append(jobs, job) } } // always return them in sorted order so the results from running the jobs are returned in a deterministic order sort.Sort(influxql.MapReduceJobs(jobs)) return jobs, nil }