| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | package sqleng | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 	"container/list" | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 	"database/sql" | 
					
						
							| 
									
										
										
										
											2018-04-25 01:50:14 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 	"math" | 
					
						
							| 
									
										
										
										
											2018-09-13 22:51:00 +08:00
										 |  |  | 	"regexp" | 
					
						
							| 
									
										
										
										
											2018-08-12 16:51:58 +08:00
										 |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2018-03-21 02:40:10 +08:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-20 21:00:28 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/setting" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-13 14:45:54 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/infra/log" | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/tsdb" | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-25 01:50:14 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/components/null" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/components/simplejson" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/models" | 
					
						
							| 
									
										
										
										
											2020-04-01 21:57:21 +08:00
										 |  |  | 	"xorm.io/core" | 
					
						
							|  |  |  | 	"xorm.io/xorm" | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-30 02:06:33 +08:00
										 |  |  | // MetaKeyExecutedQueryString is the key where the executed query should get stored
 | 
					
						
							|  |  |  | const MetaKeyExecutedQueryString = "executedQueryString" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-09 06:04:17 +08:00
										 |  |  | // SqlMacroEngine interpolates macros into sql. It takes in the Query to have access to query context and
 | 
					
						
							|  |  |  | // timeRange to be able to generate queries that use from and to.
 | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | type SqlMacroEngine interface { | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | 	Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-25 02:50:49 +08:00
										 |  |  | // SqlQueryResultTransformer transforms a query result row to RowValues with proper types.
 | 
					
						
							|  |  |  | type SqlQueryResultTransformer interface { | 
					
						
							|  |  |  | 	// TransformQueryResult transforms a query result row to RowValues with proper types.
 | 
					
						
							|  |  |  | 	TransformQueryResult(columnTypes []*sql.ColumnType, rows *core.Rows) (tsdb.RowValues, error) | 
					
						
							|  |  |  | 	// TransformQueryError transforms a query error.
 | 
					
						
							|  |  |  | 	TransformQueryError(err error) error | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type engineCacheType struct { | 
					
						
							|  |  |  | 	cache    map[int64]*xorm.Engine | 
					
						
							|  |  |  | 	versions map[int64]int | 
					
						
							|  |  |  | 	sync.Mutex | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var engineCache = engineCacheType{ | 
					
						
							|  |  |  | 	cache:    make(map[int64]*xorm.Engine), | 
					
						
							|  |  |  | 	versions: make(map[int64]int), | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | var sqlIntervalCalculator = tsdb.NewIntervalCalculator(nil) | 
					
						
							| 
									
										
										
										
											2018-09-13 22:51:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-16 20:39:01 +08:00
										 |  |  | //nolint:gocritic
 | 
					
						
							|  |  |  | // NewXormEngine is an xorm.Engine factory, that can be stubbed by tests.
 | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | var NewXormEngine = func(driverName string, connectionString string) (*xorm.Engine, error) { | 
					
						
							|  |  |  | 	return xorm.NewEngine(driverName, connectionString) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-14 00:25:36 +08:00
										 |  |  | const timeEndColumnName = "timeend" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | type sqlQueryEndpoint struct { | 
					
						
							| 
									
										
										
										
											2019-09-25 02:50:49 +08:00
										 |  |  | 	macroEngine            SqlMacroEngine | 
					
						
							|  |  |  | 	queryResultTransformer SqlQueryResultTransformer | 
					
						
							|  |  |  | 	engine                 *xorm.Engine | 
					
						
							|  |  |  | 	timeColumnNames        []string | 
					
						
							|  |  |  | 	metricColumnTypes      []string | 
					
						
							|  |  |  | 	log                    log.Logger | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type SqlQueryEndpointConfiguration struct { | 
					
						
							|  |  |  | 	DriverName        string | 
					
						
							|  |  |  | 	Datasource        *models.DataSource | 
					
						
							|  |  |  | 	ConnectionString  string | 
					
						
							|  |  |  | 	TimeColumnNames   []string | 
					
						
							|  |  |  | 	MetricColumnTypes []string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-25 02:50:49 +08:00
										 |  |  | var NewSqlQueryEndpoint = func(config *SqlQueryEndpointConfiguration, queryResultTransformer SqlQueryResultTransformer, macroEngine SqlMacroEngine, log log.Logger) (tsdb.TsdbQueryEndpoint, error) { | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 	queryEndpoint := sqlQueryEndpoint{ | 
					
						
							| 
									
										
										
										
											2019-09-25 02:50:49 +08:00
										 |  |  | 		queryResultTransformer: queryResultTransformer, | 
					
						
							|  |  |  | 		macroEngine:            macroEngine, | 
					
						
							|  |  |  | 		timeColumnNames:        []string{"time"}, | 
					
						
							|  |  |  | 		log:                    log, | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if len(config.TimeColumnNames) > 0 { | 
					
						
							|  |  |  | 		queryEndpoint.timeColumnNames = config.TimeColumnNames | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-30 19:50:18 +08:00
										 |  |  | 	if len(config.MetricColumnTypes) > 0 { | 
					
						
							|  |  |  | 		queryEndpoint.metricColumnTypes = config.MetricColumnTypes | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 	engineCache.Lock() | 
					
						
							|  |  |  | 	defer engineCache.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 	if engine, present := engineCache.cache[config.Datasource.Id]; present { | 
					
						
							|  |  |  | 		if version := engineCache.versions[config.Datasource.Id]; version == config.Datasource.Version { | 
					
						
							|  |  |  | 			queryEndpoint.engine = engine | 
					
						
							|  |  |  | 			return &queryEndpoint, nil | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 	engine, err := NewXormEngine(config.DriverName, config.ConnectionString) | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-24 20:35:13 +08:00
										 |  |  | 	maxOpenConns := config.Datasource.JsonData.Get("maxOpenConns").MustInt(0) | 
					
						
							|  |  |  | 	engine.SetMaxOpenConns(maxOpenConns) | 
					
						
							|  |  |  | 	maxIdleConns := config.Datasource.JsonData.Get("maxIdleConns").MustInt(2) | 
					
						
							|  |  |  | 	engine.SetMaxIdleConns(maxIdleConns) | 
					
						
							|  |  |  | 	connMaxLifetime := config.Datasource.JsonData.Get("connMaxLifetime").MustInt(14400) | 
					
						
							|  |  |  | 	engine.SetConnMaxLifetime(time.Duration(connMaxLifetime) * time.Second) | 
					
						
							| 
									
										
										
										
											2017-11-15 18:07:35 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 	engineCache.versions[config.Datasource.Id] = config.Datasource.Version | 
					
						
							|  |  |  | 	engineCache.cache[config.Datasource.Id] = engine | 
					
						
							|  |  |  | 	queryEndpoint.engine = engine | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 	return &queryEndpoint, nil | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 19:21:40 +08:00
										 |  |  | const rowLimit = 1000000 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | // Query is the main function for the SqlQueryEndpoint
 | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | func (e *sqlQueryEndpoint) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) { | 
					
						
							|  |  |  | 	result := &tsdb.Response{ | 
					
						
							|  |  |  | 		Results: make(map[string]*tsdb.QueryResult), | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-24 20:33:45 +08:00
										 |  |  | 	var wg sync.WaitGroup | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	for _, query := range tsdbQuery.Queries { | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 		rawSQL := query.Model.Get("rawSql").MustString() | 
					
						
							|  |  |  | 		if rawSQL == "" { | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | 		queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: query.RefId} | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 		result.Results[query.RefId] = queryResult | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-13 22:51:00 +08:00
										 |  |  | 		// global substitutions
 | 
					
						
							|  |  |  | 		rawSQL, err := Interpolate(query, tsdbQuery.TimeRange, rawSQL) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			queryResult.Error = err | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// datasource specific substitutions
 | 
					
						
							|  |  |  | 		rawSQL, err = e.macroEngine.Interpolate(query, tsdbQuery.TimeRange, rawSQL) | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			queryResult.Error = err | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-30 02:06:33 +08:00
										 |  |  | 		queryResult.Meta.Set(MetaKeyExecutedQueryString, rawSQL) | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-24 20:33:45 +08:00
										 |  |  | 		wg.Add(1) | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | 		go func(rawSQL string, query *tsdb.Query, queryResult *tsdb.QueryResult) { | 
					
						
							| 
									
										
										
										
											2018-09-24 20:33:45 +08:00
										 |  |  | 			defer wg.Done() | 
					
						
							|  |  |  | 			session := e.engine.NewSession() | 
					
						
							|  |  |  | 			defer session.Close() | 
					
						
							|  |  |  | 			db := session.DB() | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-24 20:33:45 +08:00
										 |  |  | 			rows, err := db.Query(rawSQL) | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2019-09-25 02:50:49 +08:00
										 |  |  | 				queryResult.Error = e.queryResultTransformer.TransformQueryError(err) | 
					
						
							| 
									
										
										
										
											2018-09-24 20:33:45 +08:00
										 |  |  | 				return | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-09-24 20:33:45 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			defer rows.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			format := query.Model.Get("format").MustString("time_series") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			switch format { | 
					
						
							|  |  |  | 			case "time_series": | 
					
						
							|  |  |  | 				err := e.transformToTimeSeries(query, rows, queryResult, tsdbQuery) | 
					
						
							|  |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					queryResult.Error = err | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			case "table": | 
					
						
							|  |  |  | 				err := e.transformToTable(query, rows, queryResult, tsdbQuery) | 
					
						
							|  |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					queryResult.Error = err | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-09-24 20:33:45 +08:00
										 |  |  | 		}(rawSQL, query, queryResult) | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-09-24 20:33:45 +08:00
										 |  |  | 	wg.Wait() | 
					
						
							| 
									
										
										
										
											2017-10-10 21:19:14 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return result, nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-03-21 02:40:10 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-13 22:51:00 +08:00
										 |  |  | // global macros/substitutions for all sql datasources
 | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | var Interpolate = func(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) { | 
					
						
							|  |  |  | 	minInterval, err := tsdb.GetIntervalFrom(query.DataSource, query.Model, time.Second*60) | 
					
						
							| 
									
										
										
										
											2018-09-13 22:51:00 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return sql, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	interval := sqlIntervalCalculator.Calculate(timeRange, minInterval) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	sql = strings.Replace(sql, "$__interval_ms", strconv.FormatInt(interval.Milliseconds(), 10), -1) | 
					
						
							|  |  |  | 	sql = strings.Replace(sql, "$__interval", interval.Text, -1) | 
					
						
							| 
									
										
										
										
											2018-10-03 02:19:34 +08:00
										 |  |  | 	sql = strings.Replace(sql, "$__unixEpochFrom()", fmt.Sprintf("%d", timeRange.GetFromAsSecondsEpoch()), -1) | 
					
						
							|  |  |  | 	sql = strings.Replace(sql, "$__unixEpochTo()", fmt.Sprintf("%d", timeRange.GetToAsSecondsEpoch()), -1) | 
					
						
							| 
									
										
										
										
											2018-09-13 22:51:00 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return sql, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | func (e *sqlQueryEndpoint) transformToTable(query *tsdb.Query, rows *core.Rows, result *tsdb.QueryResult, tsdbQuery *tsdb.TsdbQuery) error { | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 	columnNames, err := rows.Columns() | 
					
						
							|  |  |  | 	columnCount := len(columnNames) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rowCount := 0 | 
					
						
							|  |  |  | 	timeIndex := -1 | 
					
						
							| 
									
										
										
										
											2019-12-14 00:25:36 +08:00
										 |  |  | 	timeEndIndex := -1 | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | 	table := &tsdb.Table{ | 
					
						
							|  |  |  | 		Columns: make([]tsdb.TableColumn, columnCount), | 
					
						
							|  |  |  | 		Rows:    make([]tsdb.RowValues, 0), | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i, name := range columnNames { | 
					
						
							|  |  |  | 		table.Columns[i].Text = name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for _, tc := range e.timeColumnNames { | 
					
						
							|  |  |  | 			if name == tc { | 
					
						
							|  |  |  | 				timeIndex = i | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2019-12-14 00:25:36 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			if timeIndex >= 0 && name == timeEndColumnName { | 
					
						
							|  |  |  | 				timeEndIndex = i | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	columnTypes, err := rows.ColumnTypes() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for ; rows.Next(); rowCount++ { | 
					
						
							|  |  |  | 		if rowCount > rowLimit { | 
					
						
							|  |  |  | 			return fmt.Errorf("query row limit exceeded, limit %d", rowLimit) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-25 02:50:49 +08:00
										 |  |  | 		values, err := e.queryResultTransformer.TransformQueryResult(columnTypes, rows) | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-14 00:25:36 +08:00
										 |  |  | 		// converts column named time and timeend to unix timestamp in milliseconds
 | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 		// to make native mssql datetime types and epoch dates work in
 | 
					
						
							|  |  |  | 		// annotation and table queries.
 | 
					
						
							|  |  |  | 		ConvertSqlTimeColumnToEpochMs(values, timeIndex) | 
					
						
							| 
									
										
										
										
											2019-12-14 00:25:36 +08:00
										 |  |  | 		ConvertSqlTimeColumnToEpochMs(values, timeEndIndex) | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 		table.Rows = append(table.Rows, values) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	result.Tables = append(result.Tables, table) | 
					
						
							|  |  |  | 	result.Meta.Set("rowCount", rowCount) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | func newProcessCfg(query *tsdb.Query, tsdbQuery *tsdb.TsdbQuery, rows *core.Rows) (*processCfg, error) { | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 	columnNames, err := rows.Columns() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	columnTypes, err := rows.ColumnTypes() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 	fillMissing := query.Model.Get("fill").MustBool(false) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cfg := &processCfg{ | 
					
						
							|  |  |  | 		rowCount:           0, | 
					
						
							|  |  |  | 		columnTypes:        columnTypes, | 
					
						
							|  |  |  | 		columnNames:        columnNames, | 
					
						
							|  |  |  | 		rows:               rows, | 
					
						
							|  |  |  | 		timeIndex:          -1, | 
					
						
							|  |  |  | 		metricIndex:        -1, | 
					
						
							|  |  |  | 		metricPrefix:       false, | 
					
						
							|  |  |  | 		fillMissing:        fillMissing, | 
					
						
							|  |  |  | 		seriesByQueryOrder: list.New(), | 
					
						
							|  |  |  | 		pointsBySeries:     make(map[string]*tsdb.TimeSeries), | 
					
						
							|  |  |  | 		tsdbQuery:          tsdbQuery, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return cfg, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (e *sqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core.Rows, result *tsdb.QueryResult, | 
					
						
							|  |  |  | 	tsdbQuery *tsdb.TsdbQuery) error { | 
					
						
							|  |  |  | 	cfg, err := newProcessCfg(query, tsdbQuery, rows) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// check columns of resultset: a column named time is mandatory
 | 
					
						
							|  |  |  | 	// the first text column is treated as metric name unless a column named metric is present
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 	for i, col := range cfg.columnNames { | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 		for _, tc := range e.timeColumnNames { | 
					
						
							|  |  |  | 			if col == tc { | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 				cfg.timeIndex = i | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		switch col { | 
					
						
							|  |  |  | 		case "metric": | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 			cfg.metricIndex = i | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 		default: | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 			if cfg.metricIndex == -1 { | 
					
						
							|  |  |  | 				columnType := cfg.columnTypes[i].DatabaseTypeName() | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				for _, mct := range e.metricColumnTypes { | 
					
						
							|  |  |  | 					if columnType == mct { | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 						cfg.metricIndex = i | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 						continue | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-25 01:25:48 +08:00
										 |  |  | 	// use metric column as prefix with multiple value columns
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 	if cfg.metricIndex != -1 && len(cfg.columnNames) > 3 { | 
					
						
							|  |  |  | 		cfg.metricPrefix = true | 
					
						
							| 
									
										
										
										
											2018-07-25 01:25:48 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 	if cfg.timeIndex == -1 { | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 		return fmt.Errorf("Found no column named %s", strings.Join(e.timeColumnNames, " or ")) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 	if cfg.fillMissing { | 
					
						
							|  |  |  | 		cfg.fillInterval = query.Model.Get("fillInterval").MustFloat64() * 1000 | 
					
						
							| 
									
										
										
										
											2018-07-30 17:04:04 +08:00
										 |  |  | 		switch query.Model.Get("fillMode").MustString() { | 
					
						
							|  |  |  | 		case "null": | 
					
						
							| 
									
										
										
										
											2018-08-08 03:01:41 +08:00
										 |  |  | 		case "previous": | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 			cfg.fillPrevious = true | 
					
						
							| 
									
										
										
										
											2018-07-30 17:04:04 +08:00
										 |  |  | 		case "value": | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 			cfg.fillValue.Float64 = query.Model.Get("fillValue").MustFloat64() | 
					
						
							|  |  |  | 			cfg.fillValue.Valid = true | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for rows.Next() { | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 		if err := e.processRow(cfg); err != nil { | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 	for elem := cfg.seriesByQueryOrder.Front(); elem != nil; elem = elem.Next() { | 
					
						
							|  |  |  | 		key := elem.Value.(string) | 
					
						
							|  |  |  | 		result.Series = append(result.Series, cfg.pointsBySeries[key]) | 
					
						
							|  |  |  | 		if !cfg.fillMissing { | 
					
						
							|  |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 		series := cfg.pointsBySeries[key] | 
					
						
							|  |  |  | 		// fill in values from last fetched value till interval end
 | 
					
						
							|  |  |  | 		intervalStart := series.Points[len(series.Points)-1][1].Float64 | 
					
						
							|  |  |  | 		intervalEnd := float64(tsdbQuery.TimeRange.MustGetTo().UnixNano() / 1e6) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if cfg.fillPrevious { | 
					
						
							|  |  |  | 			if len(series.Points) > 0 { | 
					
						
							|  |  |  | 				cfg.fillValue = series.Points[len(series.Points)-1][0] | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 				cfg.fillValue.Valid = false | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 		// align interval start
 | 
					
						
							|  |  |  | 		intervalStart = math.Floor(intervalStart/cfg.fillInterval) * cfg.fillInterval | 
					
						
							|  |  |  | 		for i := intervalStart + cfg.fillInterval; i < intervalEnd; i += cfg.fillInterval { | 
					
						
							|  |  |  | 			series.Points = append(series.Points, tsdb.TimePoint{cfg.fillValue, null.FloatFrom(i)}) | 
					
						
							|  |  |  | 			cfg.rowCount++ | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 	result.Meta.Set("rowCount", cfg.rowCount) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | type processCfg struct { | 
					
						
							|  |  |  | 	rowCount           int | 
					
						
							|  |  |  | 	columnTypes        []*sql.ColumnType | 
					
						
							|  |  |  | 	columnNames        []string | 
					
						
							|  |  |  | 	rows               *core.Rows | 
					
						
							|  |  |  | 	timeIndex          int | 
					
						
							|  |  |  | 	metricIndex        int | 
					
						
							|  |  |  | 	metricPrefix       bool | 
					
						
							|  |  |  | 	metricPrefixValue  string | 
					
						
							|  |  |  | 	fillMissing        bool | 
					
						
							|  |  |  | 	pointsBySeries     map[string]*tsdb.TimeSeries | 
					
						
							|  |  |  | 	seriesByQueryOrder *list.List | 
					
						
							|  |  |  | 	fillValue          null.Float | 
					
						
							|  |  |  | 	tsdbQuery          *tsdb.TsdbQuery | 
					
						
							|  |  |  | 	fillInterval       float64 | 
					
						
							|  |  |  | 	fillPrevious       bool | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | func (e *sqlQueryEndpoint) processRow(cfg *processCfg) error { | 
					
						
							|  |  |  | 	var timestamp float64 | 
					
						
							|  |  |  | 	var value null.Float | 
					
						
							|  |  |  | 	var metric string | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 	if cfg.rowCount > rowLimit { | 
					
						
							|  |  |  | 		return fmt.Errorf("query row limit exceeded, limit %d", rowLimit) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-07-30 17:04:04 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 	values, err := e.queryResultTransformer.TransformQueryResult(cfg.columnTypes, cfg.rows) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 	// converts column named time to unix timestamp in milliseconds to make
 | 
					
						
							|  |  |  | 	// native mysql datetime types and epoch dates work in
 | 
					
						
							|  |  |  | 	// annotation and table queries.
 | 
					
						
							|  |  |  | 	ConvertSqlTimeColumnToEpochMs(values, cfg.timeIndex) | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 	switch columnValue := values[cfg.timeIndex].(type) { | 
					
						
							|  |  |  | 	case int64: | 
					
						
							|  |  |  | 		timestamp = float64(columnValue) | 
					
						
							|  |  |  | 	case float64: | 
					
						
							|  |  |  | 		timestamp = columnValue | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		return fmt.Errorf("invalid type for column time, must be of type timestamp or unix timestamp, got: %T %v", | 
					
						
							|  |  |  | 			columnValue, columnValue) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 	if cfg.metricIndex >= 0 { | 
					
						
							|  |  |  | 		if columnValue, ok := values[cfg.metricIndex].(string); ok { | 
					
						
							|  |  |  | 			if cfg.metricPrefix { | 
					
						
							|  |  |  | 				cfg.metricPrefixValue = columnValue | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				metric = columnValue | 
					
						
							| 
									
										
										
										
											2019-09-20 21:00:28 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 		} else { | 
					
						
							|  |  |  | 			return fmt.Errorf("column metric must be of type %s. metric column name: %s type: %s but datatype is %T", | 
					
						
							|  |  |  | 				strings.Join(e.metricColumnTypes, ", "), cfg.columnNames[cfg.metricIndex], | 
					
						
							|  |  |  | 				cfg.columnTypes[cfg.metricIndex].DatabaseTypeName(), values[cfg.metricIndex]) | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 	for i, col := range cfg.columnNames { | 
					
						
							|  |  |  | 		if i == cfg.timeIndex || i == cfg.metricIndex { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if value, err = ConvertSqlValueColumnToFloat(col, values[i]); err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if cfg.metricIndex == -1 { | 
					
						
							|  |  |  | 			metric = col | 
					
						
							|  |  |  | 		} else if cfg.metricPrefix { | 
					
						
							|  |  |  | 			metric = cfg.metricPrefixValue + " " + col | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 		series, exist := cfg.pointsBySeries[metric] | 
					
						
							|  |  |  | 		if !exist { | 
					
						
							|  |  |  | 			series = &tsdb.TimeSeries{Name: metric} | 
					
						
							|  |  |  | 			cfg.pointsBySeries[metric] = series | 
					
						
							|  |  |  | 			cfg.seriesByQueryOrder.PushBack(metric) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if cfg.fillMissing { | 
					
						
							|  |  |  | 			var intervalStart float64 | 
					
						
							|  |  |  | 			if !exist { | 
					
						
							|  |  |  | 				intervalStart = float64(cfg.tsdbQuery.TimeRange.MustGetFrom().UnixNano() / 1e6) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				intervalStart = series.Points[len(series.Points)-1][1].Float64 + cfg.fillInterval | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 			if cfg.fillPrevious { | 
					
						
							| 
									
										
										
										
											2018-07-30 17:04:04 +08:00
										 |  |  | 				if len(series.Points) > 0 { | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 					cfg.fillValue = series.Points[len(series.Points)-1][0] | 
					
						
							| 
									
										
										
										
											2018-07-30 17:04:04 +08:00
										 |  |  | 				} else { | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 					cfg.fillValue.Valid = false | 
					
						
							| 
									
										
										
										
											2018-07-30 17:04:04 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 			// align interval start
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 			intervalStart = math.Floor(intervalStart/cfg.fillInterval) * cfg.fillInterval | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for i := intervalStart; i < timestamp; i += cfg.fillInterval { | 
					
						
							|  |  |  | 				series.Points = append(series.Points, tsdb.TimePoint{cfg.fillValue, null.FloatFrom(i)}) | 
					
						
							|  |  |  | 				cfg.rowCount++ | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		series.Points = append(series.Points, tsdb.TimePoint{value, null.FloatFrom(timestamp)}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if setting.Env == setting.DEV { | 
					
						
							|  |  |  | 			e.log.Debug("Rows", "metric", metric, "time", timestamp, "value", value) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-07-27 00:09:42 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 16:32:30 +08:00
										 |  |  | // ConvertSqlTimeColumnToEpochMs converts column named time to unix timestamp in milliseconds
 | 
					
						
							| 
									
										
										
										
											2018-03-21 02:40:10 +08:00
										 |  |  | // to make native datetime types and epoch dates work in annotation and table queries.
 | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | func ConvertSqlTimeColumnToEpochMs(values tsdb.RowValues, timeIndex int) { | 
					
						
							| 
									
										
										
										
											2018-05-02 20:06:46 +08:00
										 |  |  | 	if timeIndex >= 0 { | 
					
						
							|  |  |  | 		switch value := values[timeIndex].(type) { | 
					
						
							|  |  |  | 		case time.Time: | 
					
						
							| 
									
										
										
										
											2018-05-28 22:57:51 +08:00
										 |  |  | 			values[timeIndex] = float64(value.UnixNano()) / float64(time.Millisecond) | 
					
						
							| 
									
										
										
										
											2018-05-02 20:06:46 +08:00
										 |  |  | 		case *time.Time: | 
					
						
							|  |  |  | 			if value != nil { | 
					
						
							| 
									
										
										
										
											2020-07-16 20:39:01 +08:00
										 |  |  | 				values[timeIndex] = float64(value.UnixNano()) / float64(time.Millisecond) | 
					
						
							| 
									
										
										
										
											2018-05-02 20:06:46 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		case int64: | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | 			values[timeIndex] = int64(tsdb.EpochPrecisionToMs(float64(value))) | 
					
						
							| 
									
										
										
										
											2018-05-02 20:06:46 +08:00
										 |  |  | 		case *int64: | 
					
						
							|  |  |  | 			if value != nil { | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | 				values[timeIndex] = int64(tsdb.EpochPrecisionToMs(float64(*value))) | 
					
						
							| 
									
										
										
										
											2018-05-02 20:06:46 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		case uint64: | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | 			values[timeIndex] = int64(tsdb.EpochPrecisionToMs(float64(value))) | 
					
						
							| 
									
										
										
										
											2018-05-02 20:06:46 +08:00
										 |  |  | 		case *uint64: | 
					
						
							|  |  |  | 			if value != nil { | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | 				values[timeIndex] = int64(tsdb.EpochPrecisionToMs(float64(*value))) | 
					
						
							| 
									
										
										
										
											2018-05-02 20:06:46 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		case int32: | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | 			values[timeIndex] = int64(tsdb.EpochPrecisionToMs(float64(value))) | 
					
						
							| 
									
										
										
										
											2018-05-02 20:06:46 +08:00
										 |  |  | 		case *int32: | 
					
						
							|  |  |  | 			if value != nil { | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | 				values[timeIndex] = int64(tsdb.EpochPrecisionToMs(float64(*value))) | 
					
						
							| 
									
										
										
										
											2018-05-02 20:06:46 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		case uint32: | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | 			values[timeIndex] = int64(tsdb.EpochPrecisionToMs(float64(value))) | 
					
						
							| 
									
										
										
										
											2018-05-02 20:06:46 +08:00
										 |  |  | 		case *uint32: | 
					
						
							|  |  |  | 			if value != nil { | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | 				values[timeIndex] = int64(tsdb.EpochPrecisionToMs(float64(*value))) | 
					
						
							| 
									
										
										
										
											2018-05-02 20:06:46 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		case float64: | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | 			values[timeIndex] = tsdb.EpochPrecisionToMs(value) | 
					
						
							| 
									
										
										
										
											2018-05-02 20:06:46 +08:00
										 |  |  | 		case *float64: | 
					
						
							|  |  |  | 			if value != nil { | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | 				values[timeIndex] = tsdb.EpochPrecisionToMs(*value) | 
					
						
							| 
									
										
										
										
											2018-05-02 20:06:46 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		case float32: | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | 			values[timeIndex] = tsdb.EpochPrecisionToMs(float64(value)) | 
					
						
							| 
									
										
										
										
											2018-05-02 20:06:46 +08:00
										 |  |  | 		case *float32: | 
					
						
							|  |  |  | 			if value != nil { | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | 				values[timeIndex] = tsdb.EpochPrecisionToMs(float64(*value)) | 
					
						
							| 
									
										
										
										
											2018-05-02 20:06:46 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-03-21 02:40:10 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-04-25 01:50:14 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // ConvertSqlValueColumnToFloat converts timeseries value column to float.
 | 
					
						
							| 
									
										
										
										
											2020-08-18 20:43:18 +08:00
										 |  |  | //nolint: gocyclo
 | 
					
						
							| 
									
										
										
										
											2018-04-25 01:50:14 +08:00
										 |  |  | func ConvertSqlValueColumnToFloat(columnName string, columnValue interface{}) (null.Float, error) { | 
					
						
							|  |  |  | 	var value null.Float | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch typedValue := columnValue.(type) { | 
					
						
							|  |  |  | 	case int: | 
					
						
							|  |  |  | 		value = null.FloatFrom(float64(typedValue)) | 
					
						
							|  |  |  | 	case *int: | 
					
						
							|  |  |  | 		if typedValue == nil { | 
					
						
							|  |  |  | 			value.Valid = false | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			value = null.FloatFrom(float64(*typedValue)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case int64: | 
					
						
							|  |  |  | 		value = null.FloatFrom(float64(typedValue)) | 
					
						
							|  |  |  | 	case *int64: | 
					
						
							|  |  |  | 		if typedValue == nil { | 
					
						
							|  |  |  | 			value.Valid = false | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			value = null.FloatFrom(float64(*typedValue)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case int32: | 
					
						
							|  |  |  | 		value = null.FloatFrom(float64(typedValue)) | 
					
						
							|  |  |  | 	case *int32: | 
					
						
							|  |  |  | 		if typedValue == nil { | 
					
						
							|  |  |  | 			value.Valid = false | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			value = null.FloatFrom(float64(*typedValue)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case int16: | 
					
						
							|  |  |  | 		value = null.FloatFrom(float64(typedValue)) | 
					
						
							|  |  |  | 	case *int16: | 
					
						
							|  |  |  | 		if typedValue == nil { | 
					
						
							|  |  |  | 			value.Valid = false | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			value = null.FloatFrom(float64(*typedValue)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case int8: | 
					
						
							|  |  |  | 		value = null.FloatFrom(float64(typedValue)) | 
					
						
							|  |  |  | 	case *int8: | 
					
						
							|  |  |  | 		if typedValue == nil { | 
					
						
							|  |  |  | 			value.Valid = false | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			value = null.FloatFrom(float64(*typedValue)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case uint: | 
					
						
							|  |  |  | 		value = null.FloatFrom(float64(typedValue)) | 
					
						
							|  |  |  | 	case *uint: | 
					
						
							|  |  |  | 		if typedValue == nil { | 
					
						
							|  |  |  | 			value.Valid = false | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			value = null.FloatFrom(float64(*typedValue)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case uint64: | 
					
						
							|  |  |  | 		value = null.FloatFrom(float64(typedValue)) | 
					
						
							|  |  |  | 	case *uint64: | 
					
						
							|  |  |  | 		if typedValue == nil { | 
					
						
							|  |  |  | 			value.Valid = false | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			value = null.FloatFrom(float64(*typedValue)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case uint32: | 
					
						
							|  |  |  | 		value = null.FloatFrom(float64(typedValue)) | 
					
						
							|  |  |  | 	case *uint32: | 
					
						
							|  |  |  | 		if typedValue == nil { | 
					
						
							|  |  |  | 			value.Valid = false | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			value = null.FloatFrom(float64(*typedValue)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case uint16: | 
					
						
							|  |  |  | 		value = null.FloatFrom(float64(typedValue)) | 
					
						
							|  |  |  | 	case *uint16: | 
					
						
							|  |  |  | 		if typedValue == nil { | 
					
						
							|  |  |  | 			value.Valid = false | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			value = null.FloatFrom(float64(*typedValue)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case uint8: | 
					
						
							|  |  |  | 		value = null.FloatFrom(float64(typedValue)) | 
					
						
							|  |  |  | 	case *uint8: | 
					
						
							|  |  |  | 		if typedValue == nil { | 
					
						
							|  |  |  | 			value.Valid = false | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			value = null.FloatFrom(float64(*typedValue)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case float64: | 
					
						
							|  |  |  | 		value = null.FloatFrom(typedValue) | 
					
						
							|  |  |  | 	case *float64: | 
					
						
							|  |  |  | 		value = null.FloatFromPtr(typedValue) | 
					
						
							|  |  |  | 	case float32: | 
					
						
							|  |  |  | 		value = null.FloatFrom(float64(typedValue)) | 
					
						
							|  |  |  | 	case *float32: | 
					
						
							|  |  |  | 		if typedValue == nil { | 
					
						
							|  |  |  | 			value.Valid = false | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			value = null.FloatFrom(float64(*typedValue)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case nil: | 
					
						
							|  |  |  | 		value.Valid = false | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		return null.NewFloat(0, false), fmt.Errorf("Value column must have numeric datatype, column: %s type: %T value: %v", columnName, typedValue, typedValue) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return value, nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-08-12 16:51:58 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-11 03:50:04 +08:00
										 |  |  | func SetupFillmode(query *tsdb.Query, interval time.Duration, fillmode string) error { | 
					
						
							| 
									
										
										
										
											2018-08-12 16:51:58 +08:00
										 |  |  | 	query.Model.Set("fill", true) | 
					
						
							|  |  |  | 	query.Model.Set("fillInterval", interval.Seconds()) | 
					
						
							|  |  |  | 	switch fillmode { | 
					
						
							|  |  |  | 	case "NULL": | 
					
						
							|  |  |  | 		query.Model.Set("fillMode", "null") | 
					
						
							|  |  |  | 	case "previous": | 
					
						
							|  |  |  | 		query.Model.Set("fillMode", "previous") | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		query.Model.Set("fillMode", "value") | 
					
						
							|  |  |  | 		floatVal, err := strconv.ParseFloat(fillmode, 64) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return fmt.Errorf("error parsing fill value %v", fillmode) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		query.Model.Set("fillValue", floatVal) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-09-13 22:51:00 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | type SqlMacroEngineBase struct{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func NewSqlMacroEngineBase() *SqlMacroEngineBase { | 
					
						
							|  |  |  | 	return &SqlMacroEngineBase{} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (m *SqlMacroEngineBase) ReplaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) string { | 
					
						
							|  |  |  | 	result := "" | 
					
						
							|  |  |  | 	lastIndex := 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, v := range re.FindAllSubmatchIndex([]byte(str), -1) { | 
					
						
							|  |  |  | 		groups := []string{} | 
					
						
							|  |  |  | 		for i := 0; i < len(v); i += 2 { | 
					
						
							|  |  |  | 			groups = append(groups, str[v[i]:v[i+1]]) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		result += str[lastIndex:v[0]] + repl(groups) | 
					
						
							|  |  |  | 		lastIndex = v[1] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return result + str[lastIndex:] | 
					
						
							|  |  |  | } |