2014-12-08 05:08:39 +00:00
package influxql
import (
2015-02-23 23:07:01 +00:00
"bytes"
2015-03-09 18:17:36 +00:00
"errors"
2014-12-18 15:44:21 +00:00
"hash/fnv"
2015-03-28 14:17:16 +00:00
"math"
2014-12-18 15:44:21 +00:00
"sort"
2014-12-08 05:08:39 +00:00
"time"
)
2015-01-26 12:19:35 +00:00
// DB represents an interface for creating transactions.
type DB interface {
Begin ( ) ( Tx , error )
}
2015-03-09 23:32:55 +00:00
const (
// Return an error if the user is trying to select more than this number of points in a group by statement.
// Most likely they specified a group by interval without time boundaries.
MaxGroupByPoints = 100000
// Since time is always selected, the column count when selecting only a single other value will be 2
SelectColumnCountWithOneValue = 2
2015-03-29 23:21:53 +00:00
// IgnoredChunkSize is what gets passed into Mapper.Begin for aggregate queries as they don't chunk points out
IgnoredChunkSize = 0
2015-03-09 23:32:55 +00:00
)
2015-03-09 18:17:36 +00:00
2015-01-26 12:19:35 +00:00
// Tx represents a transaction.
// The Tx must be opened before being used.
type Tx interface {
2015-02-23 23:07:01 +00:00
// Create MapReduceJobs for the given select statement. One MRJob will be created per unique tagset that matches the query
2015-03-02 06:37:09 +00:00
CreateMapReduceJobs ( stmt * SelectStatement , tagKeys [ ] string ) ( [ ] * MapReduceJob , error )
2014-12-11 06:32:45 +00:00
}
2015-02-23 23:07:01 +00:00
type MapReduceJob struct {
MeasurementName string
TagSet * TagSet
2015-03-02 06:37:09 +00:00
Mappers [ ] Mapper // the mappers to hit all shards for this MRJob
TMin int64 // minimum time specified in the query
TMax int64 // maximum time specified in the query
key [ ] byte // a key that identifies the MRJob so it can be sorted
interval int64 // the group by interval of the query
stmt * SelectStatement // the select statement this job was created for
2015-03-28 14:17:16 +00:00
chunkSize int // the number of points to buffer in raw queries before returning a chunked response
2014-12-08 05:08:39 +00:00
}
2015-02-23 23:07:01 +00:00
func ( m * MapReduceJob ) Open ( ) error {
2015-03-02 06:37:09 +00:00
for _ , mm := range m . Mappers {
2015-02-23 23:07:01 +00:00
if err := mm . Open ( ) ; err != nil {
m . Close ( )
return err
2014-12-08 05:08:39 +00:00
}
2015-01-27 05:08:36 +00:00
}
2015-03-02 06:37:09 +00:00
return nil
2015-01-27 05:08:36 +00:00
}
2015-02-23 23:07:01 +00:00
func ( m * MapReduceJob ) Close ( ) {
2015-03-02 06:37:09 +00:00
for _ , mm := range m . Mappers {
2015-02-23 23:07:01 +00:00
mm . Close ( )
2015-01-27 05:08:36 +00:00
}
2014-12-08 05:08:39 +00:00
}
2015-02-23 23:07:01 +00:00
func ( m * MapReduceJob ) Key ( ) [ ] byte {
if m . key == nil {
m . key = append ( [ ] byte ( m . MeasurementName ) , m . TagSet . Key ... )
2014-12-20 04:36:52 +00:00
}
2015-02-23 23:07:01 +00:00
return m . key
2014-12-08 05:08:39 +00:00
}
2015-03-07 23:29:57 +00:00
func ( m * MapReduceJob ) Execute ( out chan * Row , filterEmptyResults bool ) {
2015-03-29 23:21:53 +00:00
if err := m . Open ( ) ; err != nil {
out <- & Row { Err : err }
m . Close ( )
return
}
defer m . Close ( )
2015-03-28 14:17:16 +00:00
// if it's a raw query we handle processing differently
if m . stmt . IsRawQuery {
m . processRawQuery ( out , filterEmptyResults )
return
}
// get the aggregates and the associated reduce functions
2015-03-10 00:03:49 +00:00
aggregates := m . stmt . FunctionCalls ( )
2015-03-02 06:37:09 +00:00
reduceFuncs := make ( [ ] ReduceFunc , len ( aggregates ) )
for i , c := range aggregates {
reduceFunc , err := InitializeReduceFunc ( c )
if err != nil {
out <- & Row { Err : err }
return
}
reduceFuncs [ i ] = reduceFunc
}
2014-12-08 05:08:39 +00:00
2015-04-23 17:44:16 +00:00
// we'll have a fixed number of points with times in buckets. Initialize those times and a slice to hold the associated values
2015-03-02 06:37:09 +00:00
var pointCountInResult int
// if the user didn't specify a start time or a group by interval, we're returning a single point that describes the entire range
2015-03-28 14:17:16 +00:00
if m . TMin == 0 || m . interval == 0 {
2015-03-02 06:37:09 +00:00
// they want a single aggregate point for the entire time range
m . interval = m . TMax - m . TMin
pointCountInResult = 1
} else {
2015-03-06 19:23:58 +00:00
intervalTop := m . TMax / m . interval * m . interval + m . interval
2015-03-20 17:22:50 +00:00
intervalBottom := m . TMin / m . interval * m . interval
2015-03-19 15:28:47 +00:00
pointCountInResult = int ( ( intervalTop - intervalBottom ) / m . interval )
2014-12-18 15:44:21 +00:00
}
2014-12-08 05:08:39 +00:00
2015-03-19 15:41:18 +00:00
// For group by time queries, limit the number of data points returned by the limit and offset
// raw query limits are handled elsewhere
2015-03-28 14:17:16 +00:00
if m . stmt . Limit > 0 || m . stmt . Offset > 0 {
2015-03-19 15:41:18 +00:00
// ensure that the offset isn't higher than the number of points we'd get
2015-03-12 02:33:55 +00:00
if m . stmt . Offset > pointCountInResult {
2015-03-10 02:17:03 +00:00
return
2015-03-19 15:41:18 +00:00
}
// take the lesser of either the pre computed number of group by buckets that
// will be in the result or the limit passed in by the user
if m . stmt . Limit < pointCountInResult {
pointCountInResult = m . stmt . Limit
2015-03-10 02:17:03 +00:00
}
2015-03-12 02:33:55 +00:00
}
2015-03-19 00:33:21 +00:00
// If we are exceeding our MaxGroupByPoints and we aren't a raw query, error out
2015-03-28 14:17:16 +00:00
if pointCountInResult > MaxGroupByPoints {
2015-03-19 00:33:21 +00:00
out <- & Row {
2015-03-19 20:33:47 +00:00
Err : errors . New ( "too many points in the group by interval. maybe you forgot to specify a where time clause?" ) ,
2015-03-19 00:33:21 +00:00
}
return
}
2015-03-12 02:33:55 +00:00
// initialize the times of the aggregate points
resultValues := make ( [ ] [ ] interface { } , pointCountInResult )
// ensure that the start time for the results is on the start of the window
2015-05-12 15:25:49 +00:00
startTimeBucket := m . TMin
if m . interval > 0 {
startTimeBucket = startTimeBucket / m . interval * m . interval
}
2015-03-12 02:33:55 +00:00
2015-03-19 20:48:47 +00:00
for i , _ := range resultValues {
2015-03-19 19:31:46 +00:00
var t int64
if m . stmt . Offset > 0 {
t = startTimeBucket + ( int64 ( i + 1 ) * m . interval * int64 ( m . stmt . Offset ) )
} else {
t = startTimeBucket + ( int64 ( i + 1 ) * m . interval ) - m . interval
}
// If we start getting out of our max time range, then truncate values and return
2015-03-28 14:17:16 +00:00
if t > m . TMax {
2015-03-19 19:31:46 +00:00
resultValues = resultValues [ : i ]
break
}
2015-03-19 20:48:47 +00:00
2015-03-12 02:33:55 +00:00
// we always include time so we need one more column than we have aggregates
vals := make ( [ ] interface { } , 0 , len ( aggregates ) + 1 )
resultValues [ i ] = append ( vals , time . Unix ( 0 , t ) . UTC ( ) )
}
2015-03-12 18:39:57 +00:00
// This just makes sure that if they specify a start time less than what the start time would be with the offset,
// we just reset the start time to the later time to avoid going over data that won't show up in the result.
2015-03-28 14:17:16 +00:00
if m . stmt . Offset > 0 {
2015-03-19 20:48:47 +00:00
m . TMin = resultValues [ 0 ] [ 0 ] . ( time . Time ) . UnixNano ( )
2015-03-10 02:17:03 +00:00
}
2015-03-02 06:37:09 +00:00
// now loop through the aggregate functions and populate everything
for i , c := range aggregates {
if err := m . processAggregate ( c , reduceFuncs [ i ] , resultValues ) ; err != nil {
out <- & Row {
Name : m . MeasurementName ,
Tags : m . TagSet . Tags ,
Err : err ,
}
2015-03-06 19:23:58 +00:00
return
2015-03-02 06:37:09 +00:00
}
}
2015-03-07 23:29:57 +00:00
// filter out empty results
if filterEmptyResults && m . resultsEmpty ( resultValues ) {
2015-03-06 19:23:58 +00:00
return
}
2015-03-02 06:37:09 +00:00
// put together the row to return
2015-03-07 23:29:57 +00:00
columnNames := make ( [ ] string , len ( m . stmt . Fields ) + 1 )
2015-03-02 06:37:09 +00:00
columnNames [ 0 ] = "time"
2015-03-07 23:29:57 +00:00
for i , f := range m . stmt . Fields {
columnNames [ i + 1 ] = f . Name ( )
2014-12-18 15:44:21 +00:00
}
2015-03-02 06:37:09 +00:00
2015-03-07 23:29:57 +00:00
// processes the result values if there's any math in there
resultValues = m . processResults ( resultValues )
2015-03-12 01:05:31 +00:00
// handle any fill options
resultValues = m . processFill ( resultValues )
2015-03-02 06:37:09 +00:00
row := & Row {
Name : m . MeasurementName ,
Tags : m . TagSet . Tags ,
Columns : columnNames ,
Values : resultValues ,
2014-12-18 15:44:21 +00:00
}
2015-03-02 06:37:09 +00:00
// and we out
out <- row
2014-12-18 15:44:21 +00:00
}
2015-03-28 14:17:16 +00:00
// processRawQuery will handle running the mappers and then reducing their output
// for queries that pull back raw data values without computing any kind of aggregates.
func ( m * MapReduceJob ) processRawQuery ( out chan * Row , filterEmptyResults bool ) {
// initialize the mappers
for _ , mm := range m . Mappers {
2015-03-29 23:21:53 +00:00
if err := mm . Begin ( nil , m . TMin , m . chunkSize ) ; err != nil {
2015-03-28 14:17:16 +00:00
out <- & Row { Err : err }
return
}
}
mapperOutputs := make ( [ ] [ ] * rawQueryMapOutput , len ( m . Mappers ) )
// markers for which mappers have been completely emptied
mapperComplete := make ( [ ] bool , len ( m . Mappers ) )
// for limit and offset we need to track how many values we've swalloed for the offset and how many we've already set for the limit.
// we track the number set for the limit because they could be getting chunks. For instance if your limit is 10k, but chunk size is 1k
valuesSent := 0
valuesOffset := 0
2015-04-02 21:59:10 +00:00
valuesToReturn := make ( [ ] * rawQueryMapOutput , 0 )
2015-03-28 14:17:16 +00:00
// loop until we've emptied out all the mappers and sent everything out
for {
// collect up to the limit for each mapper
for j , mm := range m . Mappers {
// only pull from mappers that potentially have more data and whose last output has been completely sent out.
if mapperOutputs [ j ] != nil || mapperComplete [ j ] {
continue
}
2015-04-05 15:59:16 +00:00
res , err := mm . NextInterval ( )
2015-03-28 14:17:16 +00:00
if err != nil {
out <- & Row { Err : err }
return
}
if res != nil {
mapperOutputs [ j ] = res . ( [ ] * rawQueryMapOutput )
} else { // if we got a nil from the mapper it means that we've emptied all data from it
mapperComplete [ j ] = true
}
}
// process the mapper outputs. we can send out everything up to the min of the last time in the mappers
min := int64 ( math . MaxInt64 )
for _ , o := range mapperOutputs {
// some of the mappers could empty out before others so ignore them because they'll be nil
if o == nil {
continue
}
// find the min of the last point in each mapper
2015-04-23 17:44:16 +00:00
t := o [ len ( o ) - 1 ] . Time
2015-03-28 14:17:16 +00:00
if t < min {
min = t
}
}
// now empty out all the mapper outputs up to the min time
var values [ ] * rawQueryMapOutput
for j , o := range mapperOutputs {
// find the index of the point up to the min
ind := len ( o )
for i , mo := range o {
2015-04-23 17:44:16 +00:00
if mo . Time > min {
2015-03-28 14:17:16 +00:00
ind = i
break
}
}
// add up to the index to the values
values = append ( values , o [ : ind ] ... )
2015-04-21 16:05:42 +00:00
// clear out previously sent mapper output data
2015-04-20 20:36:26 +00:00
mapperOutputs [ j ] = mapperOutputs [ j ] [ ind : ]
2015-03-28 14:17:16 +00:00
// if we emptied out all the values, set this output to nil so that the mapper will get run again on the next loop
2015-04-20 20:36:26 +00:00
if len ( mapperOutputs [ j ] ) == 0 {
2015-03-28 14:17:16 +00:00
mapperOutputs [ j ] = nil
}
}
// if we didn't pull out any values, we're done here
if values == nil {
2015-04-02 21:59:10 +00:00
break
2015-03-28 14:17:16 +00:00
}
// sort the values by time first so we can then handle offset and limit
sort . Sort ( rawOutputs ( values ) )
// get rid of any points that need to be offset
if valuesOffset < m . stmt . Offset {
offset := m . stmt . Offset - valuesOffset
// if offset is bigger than the number of values we have, move to the next batch from the mappers
if offset > len ( values ) {
valuesOffset += len ( values )
continue
}
values = values [ offset : ]
valuesOffset += offset
}
// ensure we don't send more than the limit
if valuesSent < m . stmt . Limit {
limit := m . stmt . Limit - valuesSent
if len ( values ) > limit {
values = values [ : limit ]
}
valuesSent += len ( values )
}
2015-03-28 14:17:16 +00:00
2015-04-02 21:59:10 +00:00
valuesToReturn = append ( valuesToReturn , values ... )
// hit the chunk size? Send out what has been accumulated, but keep
// processing.
if len ( valuesToReturn ) >= m . chunkSize {
row := m . processRawResults ( valuesToReturn )
// perform post-processing, such as math.
row . Values = m . processResults ( row . Values )
out <- row
valuesToReturn = make ( [ ] * rawQueryMapOutput , 0 )
2015-03-28 14:17:16 +00:00
}
// stop processing if we've hit the limit
if m . stmt . Limit != 0 && valuesSent >= m . stmt . Limit {
2015-04-02 21:59:10 +00:00
break
2015-03-28 14:17:16 +00:00
}
}
2015-04-02 21:59:10 +00:00
if len ( valuesToReturn ) == 0 {
if ! filterEmptyResults {
out <- m . processRawResults ( nil )
}
} else {
row := m . processRawResults ( valuesToReturn )
// perform post-processing, such as math.
row . Values = m . processResults ( row . Values )
out <- row
}
2015-03-28 14:17:16 +00:00
}
// processsResults will apply any math that was specified in the select statement against the passed in results
2015-03-07 23:29:57 +00:00
func ( m * MapReduceJob ) processResults ( results [ ] [ ] interface { } ) [ ] [ ] interface { } {
hasMath := false
for _ , f := range m . stmt . Fields {
if _ , ok := f . Expr . ( * BinaryExpr ) ; ok {
hasMath = true
} else if _ , ok := f . Expr . ( * ParenExpr ) ; ok {
hasMath = true
}
}
if ! hasMath {
return results
}
processors := make ( [ ] processor , len ( m . stmt . Fields ) )
startIndex := 1
for i , f := range m . stmt . Fields {
processors [ i ] , startIndex = getProcessor ( f . Expr , startIndex )
}
mathResults := make ( [ ] [ ] interface { } , len ( results ) )
for i , _ := range mathResults {
mathResults [ i ] = make ( [ ] interface { } , len ( m . stmt . Fields ) + 1 )
// put the time in
mathResults [ i ] [ 0 ] = results [ i ] [ 0 ]
for j , p := range processors {
mathResults [ i ] [ j + 1 ] = p ( results [ i ] )
}
}
return mathResults
}
2015-03-12 01:05:31 +00:00
// processFill will take the results and return new reaults (or the same if no fill modifications are needed) with whatever fill options the query has.
func ( m * MapReduceJob ) processFill ( results [ ] [ ] interface { } ) [ ] [ ] interface { } {
2015-03-28 14:17:16 +00:00
// don't do anything if we're supposed to leave the nulls
if m . stmt . Fill == NullFill {
2015-03-12 01:05:31 +00:00
return results
}
if m . stmt . Fill == NoFill {
// remove any rows that have even one nil value. This one is tricky because they could have multiple
// aggregates, but this option means that any row that has even one nil gets purged.
newResults := make ( [ ] [ ] interface { } , 0 , len ( results ) )
for _ , vals := range results {
hasNil := false
// start at 1 because the first value is always time
for j := 1 ; j < len ( vals ) ; j ++ {
if vals [ j ] == nil {
hasNil = true
break
}
}
if ! hasNil {
newResults = append ( newResults , vals )
}
}
return newResults
}
// they're either filling with previous values or a specific number
for i , vals := range results {
// start at 1 because the first value is always time
for j := 1 ; j < len ( vals ) ; j ++ {
if vals [ j ] == nil {
switch m . stmt . Fill {
case PreviousFill :
if i != 0 {
vals [ j ] = results [ i - 1 ] [ j ]
}
case NumberFill :
vals [ j ] = m . stmt . FillValue
}
}
}
}
return results
}
2015-03-07 23:29:57 +00:00
func getProcessor ( expr Expr , startIndex int ) ( processor , int ) {
switch expr := expr . ( type ) {
case * VarRef :
return newEchoProcessor ( startIndex ) , startIndex + 1
case * Call :
return newEchoProcessor ( startIndex ) , startIndex + 1
case * BinaryExpr :
return getBinaryProcessor ( expr , startIndex )
case * ParenExpr :
return getProcessor ( expr . Expr , startIndex )
case * NumberLiteral :
return newLiteralProcessor ( expr . Val ) , startIndex
case * StringLiteral :
return newLiteralProcessor ( expr . Val ) , startIndex
case * BooleanLiteral :
return newLiteralProcessor ( expr . Val ) , startIndex
case * TimeLiteral :
return newLiteralProcessor ( expr . Val ) , startIndex
case * DurationLiteral :
return newLiteralProcessor ( expr . Val ) , startIndex
}
panic ( "unreachable" )
}
type processor func ( values [ ] interface { } ) interface { }
func newEchoProcessor ( index int ) processor {
return func ( values [ ] interface { } ) interface { } {
return values [ index ]
}
}
func newLiteralProcessor ( val interface { } ) processor {
return func ( values [ ] interface { } ) interface { } {
return val
}
}
func getBinaryProcessor ( expr * BinaryExpr , startIndex int ) ( processor , int ) {
lhs , index := getProcessor ( expr . LHS , startIndex )
rhs , index := getProcessor ( expr . RHS , index )
return newBinaryExprEvaluator ( expr . Op , lhs , rhs ) , index
}
func newBinaryExprEvaluator ( op Token , lhs , rhs processor ) processor {
switch op {
case ADD :
return func ( values [ ] interface { } ) interface { } {
l := lhs ( values )
r := rhs ( values )
if lv , ok := l . ( float64 ) ; ok {
if rv , ok := r . ( float64 ) ; ok {
if rv != 0 {
return lv + rv
}
}
}
return nil
}
case SUB :
return func ( values [ ] interface { } ) interface { } {
l := lhs ( values )
r := rhs ( values )
if lv , ok := l . ( float64 ) ; ok {
if rv , ok := r . ( float64 ) ; ok {
if rv != 0 {
return lv - rv
}
}
}
return nil
}
case MUL :
return func ( values [ ] interface { } ) interface { } {
l := lhs ( values )
r := rhs ( values )
if lv , ok := l . ( float64 ) ; ok {
if rv , ok := r . ( float64 ) ; ok {
if rv != 0 {
return lv * rv
}
}
}
return nil
}
case DIV :
return func ( values [ ] interface { } ) interface { } {
l := lhs ( values )
r := rhs ( values )
if lv , ok := l . ( float64 ) ; ok {
if rv , ok := r . ( float64 ) ; ok {
if rv != 0 {
return lv / rv
}
}
}
return nil
}
default :
// we shouldn't get here, but give them back nils if it goes this way
return func ( values [ ] interface { } ) interface { } {
return nil
}
}
}
2015-03-28 14:17:16 +00:00
// resultsEmpty will return true if the all the result values are empty or contain only nulls
2015-03-07 23:29:57 +00:00
func ( m * MapReduceJob ) resultsEmpty ( resultValues [ ] [ ] interface { } ) bool {
for _ , vals := range resultValues {
2015-03-09 23:32:55 +00:00
// start the loop at 1 because we want to skip over the time value
2015-03-07 23:29:57 +00:00
for i := 1 ; i < len ( vals ) ; i ++ {
if vals [ i ] != nil {
return false
}
}
}
return true
}
2015-03-06 19:23:58 +00:00
// processRawResults will handle converting the reduce results from a raw query into a Row
2015-03-28 14:17:16 +00:00
func ( m * MapReduceJob ) processRawResults ( values [ ] * rawQueryMapOutput ) * Row {
2015-03-06 19:23:58 +00:00
selectNames := m . stmt . NamesInSelect ( )
2015-03-07 23:29:57 +00:00
// ensure that time is in the select names and in the first position
2015-03-06 19:23:58 +00:00
hasTime := false
2015-03-07 23:29:57 +00:00
for i , n := range selectNames {
2015-03-06 19:23:58 +00:00
if n == "time" {
2015-03-07 23:29:57 +00:00
if i != 0 {
tmp := selectNames [ 0 ]
selectNames [ 0 ] = "time"
selectNames [ i ] = tmp
}
2015-03-06 19:23:58 +00:00
hasTime = true
}
}
// time should always be in the list of names they get back
if ! hasTime {
selectNames = append ( [ ] string { "time" } , selectNames ... )
}
// if they've selected only a single value we have to handle things a little differently
2015-03-09 23:32:55 +00:00
singleValue := len ( selectNames ) == SelectColumnCountWithOneValue
2015-03-06 19:23:58 +00:00
row := & Row {
Name : m . MeasurementName ,
Tags : m . TagSet . Tags ,
Columns : selectNames ,
}
// return an empty row if there are no results
2015-03-28 14:17:16 +00:00
if len ( values ) == 0 {
2015-03-06 19:23:58 +00:00
return row
}
2015-03-09 23:32:55 +00:00
// the results will have all of the raw mapper results, convert into the row
2015-03-28 14:17:16 +00:00
for _ , v := range values {
2015-03-06 19:23:58 +00:00
vals := make ( [ ] interface { } , len ( selectNames ) )
if singleValue {
2015-04-23 17:44:16 +00:00
vals [ 0 ] = time . Unix ( 0 , v . Time ) . UTC ( )
2015-03-29 23:21:53 +00:00
vals [ 1 ] = v . Values . ( interface { } )
2015-03-06 19:23:58 +00:00
} else {
2015-03-29 23:21:53 +00:00
fields := v . Values . ( map [ string ] interface { } )
2015-03-07 23:29:57 +00:00
// time is always the first value
2015-04-23 17:44:16 +00:00
vals [ 0 ] = time . Unix ( 0 , v . Time ) . UTC ( )
2015-03-07 23:29:57 +00:00
// populate the other values
for i := 1 ; i < len ( selectNames ) ; i ++ {
vals [ i ] = fields [ selectNames [ i ] ]
2015-03-06 19:23:58 +00:00
}
}
row . Values = append ( row . Values , vals )
}
return row
}
2015-03-02 06:37:09 +00:00
func ( m * MapReduceJob ) processAggregate ( c * Call , reduceFunc ReduceFunc , resultValues [ ] [ ] interface { } ) error {
mapperOutputs := make ( [ ] interface { } , len ( m . Mappers ) )
// intialize the mappers
for _ , mm := range m . Mappers {
2015-04-08 22:51:53 +00:00
// for aggregate queries, we use the chunk size to determine how many times NextInterval should be called.
2015-04-06 20:16:53 +00:00
// This is the number of buckets that we need to fill.
if err := mm . Begin ( c , m . TMin , len ( resultValues ) ) ; err != nil {
2015-03-02 06:37:09 +00:00
return err
}
}
2014-12-18 15:44:21 +00:00
2015-03-02 06:37:09 +00:00
// populate the result values for each interval of time
for i , _ := range resultValues {
// collect the results from each mapper
for j , mm := range m . Mappers {
2015-04-05 15:59:16 +00:00
res , err := mm . NextInterval ( )
2015-03-02 06:37:09 +00:00
if err != nil {
return err
}
mapperOutputs [ j ] = res
}
resultValues [ i ] = append ( resultValues [ i ] , reduceFunc ( mapperOutputs ) )
2015-01-26 12:19:35 +00:00
}
2015-02-07 11:29:04 +00:00
2015-03-02 06:37:09 +00:00
return nil
2014-12-09 15:45:29 +00:00
}
2014-12-08 05:08:39 +00:00
2015-02-23 23:07:01 +00:00
type MapReduceJobs [ ] * MapReduceJob
2014-12-08 05:08:39 +00:00
2015-02-23 23:07:01 +00:00
func ( a MapReduceJobs ) Len ( ) int { return len ( a ) }
func ( a MapReduceJobs ) Less ( i , j int ) bool { return bytes . Compare ( a [ i ] . Key ( ) , a [ j ] . Key ( ) ) == - 1 }
func ( a MapReduceJobs ) Swap ( i , j int ) { a [ i ] , a [ j ] = a [ j ] , a [ i ] }
2015-01-26 12:19:35 +00:00
2015-03-02 06:37:09 +00:00
// Mapper will run through a map function. A single mapper will be created
2015-02-23 23:07:01 +00:00
// for each shard for each tagset that must be hit to satisfy a query.
// Mappers can either point to a local shard or could point to a remote server.
type Mapper interface {
2015-03-02 06:37:09 +00:00
// Open will open the necessary resources to being the map job. Could be connections to remote servers or
// hitting the local bolt store
2015-02-23 23:07:01 +00:00
Open ( ) error
2014-12-08 05:08:39 +00:00
2015-03-02 06:37:09 +00:00
// Close will close the mapper (either the bolt transaction or the request)
Close ( )
2014-12-08 05:08:39 +00:00
2015-03-29 23:21:53 +00:00
// Begin will set up the mapper to run the map function for a given aggregate call starting at the passed in time.
// For raw data queries it will yield to the mapper no more than limit number of points.
Begin ( aggregate * Call , startingTime int64 , limit int ) error
2014-12-09 15:45:29 +00:00
2015-03-02 06:37:09 +00:00
// NextInterval will get the time ordered next interval of the given interval size from the mapper. This is a
// forward only operation from the start time passed into Begin. Will return nil when there is no more data to be read.
2015-04-05 15:59:16 +00:00
// Interval periods can be different based on time boundaries (months, daylight savings, etc) of the query.
NextInterval ( ) ( interface { } , error )
2015-01-26 12:19:35 +00:00
}
2015-02-23 23:07:01 +00:00
type TagSet struct {
Tags map [ string ] string
2015-03-02 06:37:09 +00:00
Filters [ ] Expr
2015-04-08 22:46:56 +00:00
SeriesIDs [ ] uint64
2015-02-23 23:07:01 +00:00
Key [ ] byte
2015-01-27 05:08:36 +00:00
}
2015-04-08 22:46:56 +00:00
func ( t * TagSet ) AddFilter ( id uint64 , filter Expr ) {
2015-02-23 23:07:01 +00:00
t . SeriesIDs = append ( t . SeriesIDs , id )
t . Filters = append ( t . Filters , filter )
2014-12-08 05:08:39 +00:00
}
2015-02-23 23:07:01 +00:00
// Planner represents an object for creating execution plans.
type Planner struct {
DB DB
2015-02-10 23:14:22 +00:00
2015-02-23 23:07:01 +00:00
// Returns the current time. Defaults to time.Now().
Now func ( ) time . Time
2015-02-10 23:14:22 +00:00
}
2015-02-23 23:07:01 +00:00
// NewPlanner returns a new instance of Planner.
func NewPlanner ( db DB ) * Planner {
return & Planner {
DB : db ,
Now : time . Now ,
2015-02-10 23:14:22 +00:00
}
}
2015-02-23 23:07:01 +00:00
// Plan creates an execution plan for the given SelectStatement and returns an Executor.
2015-03-26 12:01:27 +00:00
func ( p * Planner ) Plan ( stmt * SelectStatement , chunkSize int ) ( * Executor , error ) {
2015-03-24 22:24:24 +00:00
now := p . Now ( ) . UTC ( )
2015-02-12 16:39:41 +00:00
2015-02-23 23:07:01 +00:00
// Replace instances of "now()" with the current time.
2015-04-15 15:26:36 +00:00
stmt . Condition = Reduce ( stmt . Condition , & NowValuer { Now : now } )
2015-02-12 16:39:41 +00:00
2015-02-23 23:07:01 +00:00
// Begin an unopened transaction.
tx , err := p . DB . Begin ( )
if err != nil {
return nil , err
2015-02-12 16:39:41 +00:00
}
2015-02-23 23:07:01 +00:00
// Determine group by tag keys.
interval , tags , err := stmt . Dimensions . Normalize ( )
if err != nil {
return nil , err
2015-02-11 19:37:14 +00:00
}
2015-02-11 17:16:46 +00:00
2015-02-23 23:07:01 +00:00
// TODO: hanldle queries that select from multiple measurements. This assumes that we're only selecting from a single one
2015-03-02 06:37:09 +00:00
jobs , err := tx . CreateMapReduceJobs ( stmt , tags )
if err != nil {
return nil , err
}
2015-03-08 00:30:42 +00:00
// LIMIT and OFFSET the unique series
2015-03-10 03:09:47 +00:00
if stmt . SLimit > 0 || stmt . SOffset > 0 {
if stmt . SOffset > len ( jobs ) {
2015-03-08 00:30:42 +00:00
jobs = nil
} else {
2015-03-10 03:09:47 +00:00
if stmt . SOffset + stmt . SLimit > len ( jobs ) {
stmt . SLimit = len ( jobs ) - stmt . SOffset
2015-03-08 00:30:42 +00:00
}
2015-03-10 03:09:47 +00:00
jobs = jobs [ stmt . SOffset : stmt . SOffset + stmt . SLimit ]
2015-03-08 00:30:42 +00:00
}
}
2015-03-02 06:37:09 +00:00
for _ , j := range jobs {
j . interval = interval . Nanoseconds ( )
j . stmt = stmt
2015-03-28 14:17:16 +00:00
j . chunkSize = chunkSize
2015-03-02 06:37:09 +00:00
}
2015-02-11 17:16:46 +00:00
2015-03-02 06:37:09 +00:00
return & Executor { tx : tx , stmt : stmt , jobs : jobs , interval : interval . Nanoseconds ( ) } , nil
2015-02-11 17:16:46 +00:00
}
2015-02-23 23:07:01 +00:00
// Executor represents the implementation of Executor.
// It executes all reducers and combines their result into a row.
type Executor struct {
2015-03-02 06:37:09 +00:00
tx Tx // transaction
stmt * SelectStatement // original statement
jobs [ ] * MapReduceJob // one job per unique tag set that will return in the query
interval int64 // the group by interval of the query in nanoseconds
2015-02-11 22:55:51 +00:00
}
2015-02-23 23:07:01 +00:00
// Execute begins execution of the query and returns a channel to receive rows.
2015-03-29 23:21:53 +00:00
func ( e * Executor ) Execute ( ) <- chan * Row {
2015-02-23 23:07:01 +00:00
// Create output channel and stream data in a separate goroutine.
out := make ( chan * Row , 0 )
go e . execute ( out )
2015-02-11 22:55:51 +00:00
2015-03-29 23:21:53 +00:00
return out
2015-02-11 22:55:51 +00:00
}
2015-02-23 23:07:01 +00:00
func ( e * Executor ) close ( ) {
2015-03-02 06:37:09 +00:00
for _ , j := range e . jobs {
j . Close ( )
2015-02-11 22:55:51 +00:00
}
}
2015-02-23 23:07:01 +00:00
// execute runs in a separate separate goroutine and streams data from processors.
func ( e * Executor ) execute ( out chan * Row ) {
// Ensure the the MRJobs close after execution.
defer e . close ( )
2015-02-11 22:55:51 +00:00
2015-03-07 23:29:57 +00:00
// If we have multiple tag sets we'll want to filter out the empty ones
filterEmptyResults := len ( e . jobs ) > 1
2015-02-23 23:07:01 +00:00
// Execute each MRJob serially
for _ , j := range e . jobs {
2015-03-07 23:29:57 +00:00
j . Execute ( out , filterEmptyResults )
2015-02-11 22:55:51 +00:00
}
2015-01-27 00:42:29 +00:00
2015-02-23 23:07:01 +00:00
// Mark the end of the output channel.
close ( out )
2015-01-27 00:42:29 +00:00
}
2014-12-08 05:08:39 +00:00
// Row represents a single row returned from the execution of a statement.
type Row struct {
2014-12-11 06:32:45 +00:00
Name string ` json:"name,omitempty" `
Tags map [ string ] string ` json:"tags,omitempty" `
Columns [ ] string ` json:"columns" `
Values [ ] [ ] interface { } ` json:"values,omitempty" `
Err error ` json:"err,omitempty" `
2014-12-08 05:08:39 +00:00
}
2014-12-18 15:44:21 +00:00
// tagsHash returns a hash of tag key/value pairs.
func ( r * Row ) tagsHash ( ) uint64 {
h := fnv . New64a ( )
keys := r . tagsKeys ( )
for _ , k := range keys {
h . Write ( [ ] byte ( k ) )
h . Write ( [ ] byte ( r . Tags [ k ] ) )
}
return h . Sum64 ( )
}
// tagKeys returns a sorted list of tag keys.
func ( r * Row ) tagsKeys ( ) [ ] string {
a := make ( [ ] string , len ( r . Tags ) )
for k := range r . Tags {
a = append ( a , k )
}
sort . Strings ( a )
return a
}
// Rows represents a list of rows that can be sorted consistently by name/tag.
type Rows [ ] * Row
func ( p Rows ) Len ( ) int { return len ( p ) }
func ( p Rows ) Less ( i , j int ) bool {
// Sort by name first.
if p [ i ] . Name != p [ j ] . Name {
return p [ i ] . Name < p [ j ] . Name
}
2014-12-21 17:05:15 +00:00
// Sort by tag set hash. Tags don't have a meaningful sort order so we
// just compute a hash and sort by that instead. This allows the tests
// to receive rows in a predictable order every time.
2014-12-18 15:44:21 +00:00
return p [ i ] . tagsHash ( ) < p [ j ] . tagsHash ( )
}
func ( p Rows ) Swap ( i , j int ) { p [ i ] , p [ j ] = p [ j ] , p [ i ] }