935 lines
26 KiB
Go
935 lines
26 KiB
Go
package tsdb
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/influxdb/influxdb/influxql"
|
|
"github.com/influxdb/influxdb/meta"
|
|
)
|
|
|
|
// QueryExecutor executes every statement in an influxdb Query. It is responsible for
|
|
// coordinating between the local tsdb.Store, the meta.Store, and the other nodes in
|
|
// the cluster to run the query against their local tsdb.Stores. There should be one executor
|
|
// in a running process
|
|
type QueryExecutor struct {
|
|
// The meta store for accessing and updating cluster and schema data.
|
|
MetaStore interface {
|
|
Database(name string) (*meta.DatabaseInfo, error)
|
|
Databases() ([]meta.DatabaseInfo, error)
|
|
User(name string) (*meta.UserInfo, error)
|
|
AdminUserExists() (bool, error)
|
|
Authenticate(username, password string) (*meta.UserInfo, error)
|
|
RetentionPolicy(database, name string) (rpi *meta.RetentionPolicyInfo, err error)
|
|
UserCount() (int, error)
|
|
ShardGroupsByTimeRange(database, policy string, min, max time.Time) (a []meta.ShardGroupInfo, err error)
|
|
NodeID() uint64
|
|
}
|
|
|
|
// Executes statements relating to meta data.
|
|
MetaStatementExecutor interface {
|
|
ExecuteStatement(stmt influxql.Statement) *influxql.Result
|
|
}
|
|
|
|
// Maps shards for queries.
|
|
ShardMapper interface {
|
|
CreateMapper(shard meta.ShardInfo, stmt string, chunkSize int) (Mapper, error)
|
|
}
|
|
|
|
Logger *log.Logger
|
|
|
|
// the local data store
|
|
Store *Store
|
|
}
|
|
|
|
// NewQueryExecutor returns an initialized QueryExecutor
|
|
func NewQueryExecutor(store *Store) *QueryExecutor {
|
|
return &QueryExecutor{
|
|
Store: store,
|
|
Logger: log.New(os.Stderr, "[query] ", log.LstdFlags),
|
|
}
|
|
}
|
|
|
|
// Authorize user u to execute query q on database.
|
|
// database can be "" for queries that do not require a database.
|
|
// If no user is provided it will return an error unless the query's first statement is to create
|
|
// a root user.
|
|
func (q *QueryExecutor) Authorize(u *meta.UserInfo, query *influxql.Query, database string) error {
|
|
// Special case if no users exist.
|
|
if count, err := q.MetaStore.UserCount(); count == 0 && err == nil {
|
|
// Ensure there is at least one statement.
|
|
if len(query.Statements) > 0 {
|
|
// First statement in the query must create a user with admin privilege.
|
|
cu, ok := query.Statements[0].(*influxql.CreateUserStatement)
|
|
if ok && cu.Admin == true {
|
|
return nil
|
|
}
|
|
}
|
|
return NewErrAuthorize(q, query, "", database, "create admin user first or disable authentication")
|
|
}
|
|
|
|
if u == nil {
|
|
return NewErrAuthorize(q, query, "", database, "no user provided")
|
|
}
|
|
|
|
// Admin privilege allows the user to execute all statements.
|
|
if u.Admin {
|
|
return nil
|
|
}
|
|
|
|
// Check each statement in the query.
|
|
for _, stmt := range query.Statements {
|
|
// Get the privileges required to execute the statement.
|
|
privs := stmt.RequiredPrivileges()
|
|
|
|
// Make sure the user has the privileges required to execute
|
|
// each statement.
|
|
for _, p := range privs {
|
|
if p.Admin {
|
|
// Admin privilege already checked so statement requiring admin
|
|
// privilege cannot be run.
|
|
msg := fmt.Sprintf("statement '%s', requires admin privilege", stmt)
|
|
return NewErrAuthorize(q, query, u.Name, database, msg)
|
|
}
|
|
|
|
// Use the db name specified by the statement or the db
|
|
// name passed by the caller if one wasn't specified by
|
|
// the statement.
|
|
db := p.Name
|
|
if db == "" {
|
|
db = database
|
|
}
|
|
if !u.Authorize(p.Privilege, db) {
|
|
msg := fmt.Sprintf("statement '%s', requires %s on %s", stmt, p.Privilege.String(), db)
|
|
return NewErrAuthorize(q, query, u.Name, database, msg)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ExecuteQuery executes an InfluxQL query against the server.
|
|
// It sends results down the passed in chan and closes it when done. It will close the chan
|
|
// on the first statement that throws an error.
|
|
func (q *QueryExecutor) ExecuteQuery(query *influxql.Query, database string, chunkSize int) (<-chan *influxql.Result, error) {
|
|
// Execute each statement. Keep the iterator external so we can
|
|
// track how many of the statements were executed
|
|
results := make(chan *influxql.Result)
|
|
go func() {
|
|
var i int
|
|
var stmt influxql.Statement
|
|
for i, stmt = range query.Statements {
|
|
// If a default database wasn't passed in by the caller, check the statement.
|
|
// Some types of statements have an associated default database, even if it
|
|
// is not explicitly included.
|
|
defaultDB := database
|
|
if defaultDB == "" {
|
|
if s, ok := stmt.(influxql.HasDefaultDatabase); ok {
|
|
defaultDB = s.DefaultDatabase()
|
|
}
|
|
}
|
|
|
|
// Normalize each statement.
|
|
if err := q.normalizeStatement(stmt, defaultDB); err != nil {
|
|
results <- &influxql.Result{Err: err}
|
|
break
|
|
}
|
|
|
|
var res *influxql.Result
|
|
switch stmt := stmt.(type) {
|
|
case *influxql.SelectStatement:
|
|
if err := q.executeSelectStatement(i, stmt, results, chunkSize); err != nil {
|
|
results <- &influxql.Result{Err: err}
|
|
break
|
|
}
|
|
case *influxql.DropSeriesStatement:
|
|
// TODO: handle this in a cluster
|
|
res = q.executeDropSeriesStatement(stmt, database)
|
|
case *influxql.ShowSeriesStatement:
|
|
res = q.executeShowSeriesStatement(stmt, database)
|
|
case *influxql.DropMeasurementStatement:
|
|
// TODO: handle this in a cluster
|
|
res = q.executeDropMeasurementStatement(stmt, database)
|
|
case *influxql.ShowMeasurementsStatement:
|
|
res = q.executeShowMeasurementsStatement(stmt, database)
|
|
case *influxql.ShowTagKeysStatement:
|
|
res = q.executeShowTagKeysStatement(stmt, database)
|
|
case *influxql.ShowTagValuesStatement:
|
|
res = q.executeShowTagValuesStatement(stmt, database)
|
|
case *influxql.ShowFieldKeysStatement:
|
|
res = q.executeShowFieldKeysStatement(stmt, database)
|
|
case *influxql.ShowDiagnosticsStatement:
|
|
res = q.executeShowDiagnosticsStatement(stmt)
|
|
case *influxql.DeleteStatement:
|
|
res = &influxql.Result{Err: ErrInvalidQuery}
|
|
case *influxql.DropDatabaseStatement:
|
|
// TODO: handle this in a cluster
|
|
res = q.executeDropDatabaseStatement(stmt)
|
|
default:
|
|
// Delegate all other meta statements to a separate executor. They don't hit tsdb storage.
|
|
res = q.MetaStatementExecutor.ExecuteStatement(stmt)
|
|
}
|
|
|
|
if res != nil {
|
|
// set the StatementID for the handler on the other side to combine results
|
|
res.StatementID = i
|
|
|
|
// If an error occurs then stop processing remaining statements.
|
|
results <- res
|
|
if res.Err != nil {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// if there was an error send results that the remaining statements weren't executed
|
|
for ; i < len(query.Statements)-1; i++ {
|
|
results <- &influxql.Result{Err: ErrNotExecuted}
|
|
}
|
|
|
|
close(results)
|
|
}()
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// Plan creates an execution plan for the given SelectStatement and returns an Executor.
|
|
func (q *QueryExecutor) Plan(stmt *influxql.SelectStatement, chunkSize int) (*Executor, error) {
|
|
shards := map[uint64]meta.ShardInfo{} // Shards requiring mappers.
|
|
|
|
// Replace instances of "now()" with the current time, and check the resultant times.
|
|
stmt.Condition = influxql.Reduce(stmt.Condition, &influxql.NowValuer{Now: time.Now().UTC()})
|
|
tmin, tmax := influxql.TimeRange(stmt.Condition)
|
|
if tmax.IsZero() {
|
|
tmax = time.Now()
|
|
}
|
|
if tmin.IsZero() {
|
|
tmin = time.Unix(0, 0)
|
|
}
|
|
|
|
for _, src := range stmt.Sources {
|
|
mm, ok := src.(*influxql.Measurement)
|
|
if !ok {
|
|
return nil, fmt.Errorf("invalid source type: %#v", src)
|
|
}
|
|
|
|
// Build the set of target shards. Using shard IDs as keys ensures each shard ID
|
|
// occurs only once.
|
|
shardGroups, err := q.MetaStore.ShardGroupsByTimeRange(mm.Database, mm.RetentionPolicy, tmin, tmax)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, g := range shardGroups {
|
|
for _, sh := range g.Shards {
|
|
shards[sh.ID] = sh
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build the Mappers, one per shard.
|
|
mappers := []Mapper{}
|
|
for _, sh := range shards {
|
|
m, err := q.ShardMapper.CreateMapper(sh, stmt.String(), chunkSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if m == nil {
|
|
// No data for this shard, skip it.
|
|
continue
|
|
}
|
|
mappers = append(mappers, m)
|
|
}
|
|
|
|
executor := NewExecutor(stmt, mappers, chunkSize)
|
|
return executor, nil
|
|
}
|
|
|
|
// executeSelectStatement plans and executes a select statement against a database.
|
|
func (q *QueryExecutor) executeSelectStatement(statementID int, stmt *influxql.SelectStatement, results chan *influxql.Result, chunkSize int) error {
|
|
// Plan statement execution.
|
|
e, err := q.Plan(stmt, chunkSize)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Execute plan.
|
|
ch := e.Execute()
|
|
|
|
// Stream results from the channel. We should send an empty result if nothing comes through.
|
|
resultSent := false
|
|
for row := range ch {
|
|
if row.Err != nil {
|
|
return row.Err
|
|
}
|
|
resultSent = true
|
|
results <- &influxql.Result{StatementID: statementID, Series: []*influxql.Row{row}}
|
|
}
|
|
|
|
if !resultSent {
|
|
results <- &influxql.Result{StatementID: statementID, Series: make([]*influxql.Row, 0)}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// expandSources expands regex sources and removes duplicates.
|
|
// NOTE: sources must be normalized (db and rp set) before calling this function.
|
|
func (q *QueryExecutor) expandSources(sources influxql.Sources) (influxql.Sources, error) {
|
|
// Use a map as a set to prevent duplicates. Two regexes might produce
|
|
// duplicates when expanded.
|
|
set := map[string]influxql.Source{}
|
|
names := []string{}
|
|
|
|
// Iterate all sources, expanding regexes when they're found.
|
|
for _, source := range sources {
|
|
switch src := source.(type) {
|
|
case *influxql.Measurement:
|
|
if src.Regex == nil {
|
|
name := src.String()
|
|
set[name] = src
|
|
names = append(names, name)
|
|
continue
|
|
}
|
|
|
|
// Lookup the database.
|
|
db := q.Store.DatabaseIndex(src.Database)
|
|
if db == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
// Get measurements from the database that match the regex.
|
|
measurements := db.measurementsByRegex(src.Regex.Val)
|
|
|
|
// Add those measurements to the set.
|
|
for _, m := range measurements {
|
|
m2 := &influxql.Measurement{
|
|
Database: src.Database,
|
|
RetentionPolicy: src.RetentionPolicy,
|
|
Name: m.Name,
|
|
}
|
|
|
|
name := m2.String()
|
|
if _, ok := set[name]; !ok {
|
|
set[name] = m2
|
|
names = append(names, name)
|
|
}
|
|
}
|
|
|
|
default:
|
|
return nil, fmt.Errorf("expandSources: unsuported source type: %T", source)
|
|
}
|
|
}
|
|
|
|
// Sort the list of source names.
|
|
sort.Strings(names)
|
|
|
|
// Convert set to a list of Sources.
|
|
expanded := make(influxql.Sources, 0, len(set))
|
|
for _, name := range names {
|
|
expanded = append(expanded, set[name])
|
|
}
|
|
|
|
return expanded, nil
|
|
}
|
|
|
|
// executeDropDatabaseStatement closes all local shards for the database and removes the directory. It then calls to the metastore to remove the database from there.
|
|
// TODO: make this work in a cluster/distributed
|
|
func (q *QueryExecutor) executeDropDatabaseStatement(stmt *influxql.DropDatabaseStatement) *influxql.Result {
|
|
dbi, err := q.MetaStore.Database(stmt.Name)
|
|
if err != nil {
|
|
return &influxql.Result{Err: err}
|
|
} else if dbi == nil {
|
|
return &influxql.Result{Err: ErrDatabaseNotFound(stmt.Name)}
|
|
}
|
|
|
|
var shardIDs []uint64
|
|
for _, rp := range dbi.RetentionPolicies {
|
|
for _, sg := range rp.ShardGroups {
|
|
for _, s := range sg.Shards {
|
|
shardIDs = append(shardIDs, s.ID)
|
|
}
|
|
}
|
|
}
|
|
|
|
err = q.Store.DeleteDatabase(stmt.Name, shardIDs)
|
|
if err != nil {
|
|
return &influxql.Result{Err: err}
|
|
}
|
|
|
|
return q.MetaStatementExecutor.ExecuteStatement(stmt)
|
|
}
|
|
|
|
// executeDropMeasurementStatement removes the measurement and all series data from the local store for the given measurement
|
|
func (q *QueryExecutor) executeDropMeasurementStatement(stmt *influxql.DropMeasurementStatement, database string) *influxql.Result {
|
|
// Find the database.
|
|
db := q.Store.DatabaseIndex(database)
|
|
if db == nil {
|
|
return &influxql.Result{}
|
|
}
|
|
|
|
m := db.Measurement(stmt.Name)
|
|
if m == nil {
|
|
return &influxql.Result{Err: ErrMeasurementNotFound(stmt.Name)}
|
|
}
|
|
|
|
// first remove from the index
|
|
db.DropMeasurement(m.Name)
|
|
|
|
// now drop the raw data
|
|
if err := q.Store.deleteMeasurement(m.Name, m.SeriesKeys()); err != nil {
|
|
return &influxql.Result{Err: err}
|
|
}
|
|
|
|
return &influxql.Result{}
|
|
}
|
|
|
|
// executeDropSeriesStatement removes all series from the local store that match the drop query
|
|
func (q *QueryExecutor) executeDropSeriesStatement(stmt *influxql.DropSeriesStatement, database string) *influxql.Result {
|
|
// Find the database.
|
|
db := q.Store.DatabaseIndex(database)
|
|
if db == nil {
|
|
return &influxql.Result{}
|
|
}
|
|
|
|
// Expand regex expressions in the FROM clause.
|
|
sources, err := q.expandSources(stmt.Sources)
|
|
if err != nil {
|
|
return &influxql.Result{Err: err}
|
|
}
|
|
|
|
measurements, err := measurementsFromSourcesOrDB(db, sources...)
|
|
if err != nil {
|
|
return &influxql.Result{Err: err}
|
|
}
|
|
|
|
var seriesKeys []string
|
|
for _, m := range measurements {
|
|
var ids SeriesIDs
|
|
if stmt.Condition != nil {
|
|
// Get series IDs that match the WHERE clause.
|
|
ids, _, err = m.walkWhereForSeriesIds(stmt.Condition)
|
|
if err != nil {
|
|
return &influxql.Result{Err: err}
|
|
}
|
|
} else {
|
|
// No WHERE clause so get all series IDs for this measurement.
|
|
ids = m.seriesIDs
|
|
}
|
|
|
|
for _, id := range ids {
|
|
seriesKeys = append(seriesKeys, m.seriesByID[id].Key)
|
|
}
|
|
}
|
|
|
|
// delete the raw series data
|
|
if err := q.Store.deleteSeries(seriesKeys); err != nil {
|
|
return &influxql.Result{Err: err}
|
|
}
|
|
// remove them from the index
|
|
db.DropSeries(seriesKeys)
|
|
|
|
return &influxql.Result{}
|
|
}
|
|
|
|
func (q *QueryExecutor) executeShowSeriesStatement(stmt *influxql.ShowSeriesStatement, database string) *influxql.Result {
|
|
// Find the database.
|
|
db := q.Store.DatabaseIndex(database)
|
|
if db == nil {
|
|
return &influxql.Result{}
|
|
}
|
|
|
|
// Expand regex expressions in the FROM clause.
|
|
sources, err := q.expandSources(stmt.Sources)
|
|
if err != nil {
|
|
return &influxql.Result{Err: err}
|
|
}
|
|
|
|
// Get the list of measurements we're interested in.
|
|
measurements, err := measurementsFromSourcesOrDB(db, sources...)
|
|
if err != nil {
|
|
return &influxql.Result{Err: err}
|
|
}
|
|
|
|
// Create result struct that will be populated and returned.
|
|
result := &influxql.Result{
|
|
Series: make(influxql.Rows, 0, len(measurements)),
|
|
}
|
|
|
|
// Loop through measurements to build result. One result row / measurement.
|
|
for _, m := range measurements {
|
|
var ids SeriesIDs
|
|
|
|
if stmt.Condition != nil {
|
|
// Get series IDs that match the WHERE clause.
|
|
ids, _, err = m.walkWhereForSeriesIds(stmt.Condition)
|
|
if err != nil {
|
|
return &influxql.Result{Err: err}
|
|
}
|
|
|
|
// If no series matched, then go to the next measurement.
|
|
if len(ids) == 0 {
|
|
continue
|
|
}
|
|
|
|
// TODO: check return of walkWhereForSeriesIds for fields
|
|
} else {
|
|
// No WHERE clause so get all series IDs for this measurement.
|
|
ids = m.seriesIDs
|
|
}
|
|
|
|
// Make a new row for this measurement.
|
|
r := &influxql.Row{
|
|
Name: m.Name,
|
|
Columns: m.TagKeys(),
|
|
}
|
|
|
|
// Loop through series IDs getting matching tag sets.
|
|
for _, id := range ids {
|
|
if s, ok := m.seriesByID[id]; ok {
|
|
values := make([]interface{}, 0, len(r.Columns))
|
|
|
|
// make the series key the first value
|
|
values = append(values, s.Key)
|
|
|
|
for _, column := range r.Columns {
|
|
values = append(values, s.Tags[column])
|
|
}
|
|
|
|
// Add the tag values to the row.
|
|
r.Values = append(r.Values, values)
|
|
}
|
|
}
|
|
// make the id the first column
|
|
r.Columns = append([]string{"_key"}, r.Columns...)
|
|
|
|
// Append the row to the result.
|
|
result.Series = append(result.Series, r)
|
|
}
|
|
|
|
if stmt.Limit > 0 || stmt.Offset > 0 {
|
|
result.Series = q.filterShowSeriesResult(stmt.Limit, stmt.Offset, result.Series)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// filterShowSeriesResult will limit the number of series returned based on the limit and the offset.
|
|
// Unlike limit and offset on SELECT statements, the limit and offset don't apply to the number of Rows, but
|
|
// to the number of total Values returned, since each Value represents a unique series.
|
|
func (q *QueryExecutor) filterShowSeriesResult(limit, offset int, rows influxql.Rows) influxql.Rows {
|
|
var filteredSeries influxql.Rows
|
|
seriesCount := 0
|
|
for _, r := range rows {
|
|
var currentSeries [][]interface{}
|
|
|
|
// filter the values
|
|
for _, v := range r.Values {
|
|
if seriesCount >= offset && seriesCount-offset < limit {
|
|
currentSeries = append(currentSeries, v)
|
|
}
|
|
seriesCount++
|
|
}
|
|
|
|
// only add the row back in if there are some values in it
|
|
if len(currentSeries) > 0 {
|
|
r.Values = currentSeries
|
|
filteredSeries = append(filteredSeries, r)
|
|
if seriesCount > limit+offset {
|
|
return filteredSeries
|
|
}
|
|
}
|
|
}
|
|
return filteredSeries
|
|
}
|
|
|
|
func (q *QueryExecutor) executeShowMeasurementsStatement(stmt *influxql.ShowMeasurementsStatement, database string) *influxql.Result {
|
|
// Find the database.
|
|
db := q.Store.DatabaseIndex(database)
|
|
if db == nil {
|
|
return &influxql.Result{}
|
|
}
|
|
|
|
var measurements Measurements
|
|
|
|
// If a WHERE clause was specified, filter the measurements.
|
|
if stmt.Condition != nil {
|
|
var err error
|
|
measurements, err = db.measurementsByExpr(stmt.Condition)
|
|
if err != nil {
|
|
return &influxql.Result{Err: err}
|
|
}
|
|
} else {
|
|
// Otherwise, get all measurements from the database.
|
|
measurements = db.Measurements()
|
|
}
|
|
sort.Sort(measurements)
|
|
|
|
offset := stmt.Offset
|
|
limit := stmt.Limit
|
|
|
|
// If OFFSET is past the end of the array, return empty results.
|
|
if offset > len(measurements)-1 {
|
|
return &influxql.Result{}
|
|
}
|
|
|
|
// Calculate last index based on LIMIT.
|
|
end := len(measurements)
|
|
if limit > 0 && offset+limit < end {
|
|
limit = offset + limit
|
|
} else {
|
|
limit = end
|
|
}
|
|
|
|
// Make a result row to hold all measurement names.
|
|
row := &influxql.Row{
|
|
Name: "measurements",
|
|
Columns: []string{"name"},
|
|
}
|
|
|
|
// Add one value to the row for each measurement name.
|
|
for i := offset; i < limit; i++ {
|
|
m := measurements[i]
|
|
v := interface{}(m.Name)
|
|
row.Values = append(row.Values, []interface{}{v})
|
|
}
|
|
|
|
// Make a result.
|
|
result := &influxql.Result{
|
|
Series: []*influxql.Row{row},
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (q *QueryExecutor) executeShowTagKeysStatement(stmt *influxql.ShowTagKeysStatement, database string) *influxql.Result {
|
|
// Find the database.
|
|
db := q.Store.DatabaseIndex(database)
|
|
if db == nil {
|
|
return &influxql.Result{}
|
|
}
|
|
|
|
// Expand regex expressions in the FROM clause.
|
|
sources, err := q.expandSources(stmt.Sources)
|
|
if err != nil {
|
|
return &influxql.Result{Err: err}
|
|
}
|
|
|
|
// Get the list of measurements we're interested in.
|
|
measurements, err := measurementsFromSourcesOrDB(db, sources...)
|
|
if err != nil {
|
|
return &influxql.Result{Err: err}
|
|
}
|
|
|
|
// Make result.
|
|
result := &influxql.Result{
|
|
Series: make(influxql.Rows, 0, len(measurements)),
|
|
}
|
|
|
|
// Add one row per measurement to the result.
|
|
for _, m := range measurements {
|
|
// TODO: filter tag keys by stmt.Condition
|
|
|
|
// Get the tag keys in sorted order.
|
|
keys := m.TagKeys()
|
|
|
|
// Convert keys to an [][]interface{}.
|
|
values := make([][]interface{}, 0, len(m.seriesByTagKeyValue))
|
|
for _, k := range keys {
|
|
v := interface{}(k)
|
|
values = append(values, []interface{}{v})
|
|
}
|
|
|
|
// Make a result row for the measurement.
|
|
r := &influxql.Row{
|
|
Name: m.Name,
|
|
Columns: []string{"tagKey"},
|
|
Values: values,
|
|
}
|
|
|
|
result.Series = append(result.Series, r)
|
|
}
|
|
|
|
// TODO: LIMIT & OFFSET
|
|
|
|
return result
|
|
}
|
|
|
|
func (q *QueryExecutor) executeShowTagValuesStatement(stmt *influxql.ShowTagValuesStatement, database string) *influxql.Result {
|
|
// Find the database.
|
|
db := q.Store.DatabaseIndex(database)
|
|
if db == nil {
|
|
return &influxql.Result{}
|
|
}
|
|
|
|
// Expand regex expressions in the FROM clause.
|
|
sources, err := q.expandSources(stmt.Sources)
|
|
if err != nil {
|
|
return &influxql.Result{Err: err}
|
|
}
|
|
|
|
// Get the list of measurements we're interested in.
|
|
measurements, err := measurementsFromSourcesOrDB(db, sources...)
|
|
if err != nil {
|
|
return &influxql.Result{Err: err}
|
|
}
|
|
|
|
// Make result.
|
|
result := &influxql.Result{
|
|
Series: make(influxql.Rows, 0),
|
|
}
|
|
|
|
tagValues := make(map[string]stringSet)
|
|
for _, m := range measurements {
|
|
var ids SeriesIDs
|
|
|
|
if stmt.Condition != nil {
|
|
// Get series IDs that match the WHERE clause.
|
|
ids, _, err = m.walkWhereForSeriesIds(stmt.Condition)
|
|
if err != nil {
|
|
return &influxql.Result{Err: err}
|
|
}
|
|
|
|
// If no series matched, then go to the next measurement.
|
|
if len(ids) == 0 {
|
|
continue
|
|
}
|
|
|
|
// TODO: check return of walkWhereForSeriesIds for fields
|
|
} else {
|
|
// No WHERE clause so get all series IDs for this measurement.
|
|
ids = m.seriesIDs
|
|
}
|
|
|
|
for k, v := range m.tagValuesByKeyAndSeriesID(stmt.TagKeys, ids) {
|
|
_, ok := tagValues[k]
|
|
if !ok {
|
|
tagValues[k] = v
|
|
}
|
|
tagValues[k] = tagValues[k].union(v)
|
|
}
|
|
}
|
|
|
|
for k, v := range tagValues {
|
|
r := &influxql.Row{
|
|
Name: k + "TagValues",
|
|
Columns: []string{k},
|
|
}
|
|
|
|
vals := v.list()
|
|
sort.Strings(vals)
|
|
|
|
for _, val := range vals {
|
|
v := interface{}(val)
|
|
r.Values = append(r.Values, []interface{}{v})
|
|
}
|
|
|
|
result.Series = append(result.Series, r)
|
|
}
|
|
|
|
sort.Sort(result.Series)
|
|
return result
|
|
}
|
|
|
|
func (q *QueryExecutor) executeShowFieldKeysStatement(stmt *influxql.ShowFieldKeysStatement, database string) *influxql.Result {
|
|
var err error
|
|
|
|
// Find the database.
|
|
db := q.Store.DatabaseIndex(database)
|
|
if db == nil {
|
|
return &influxql.Result{}
|
|
}
|
|
|
|
// Expand regex expressions in the FROM clause.
|
|
sources, err := q.expandSources(stmt.Sources)
|
|
if err != nil {
|
|
return &influxql.Result{Err: err}
|
|
}
|
|
|
|
measurements, err := measurementsFromSourcesOrDB(db, sources...)
|
|
if err != nil {
|
|
return &influxql.Result{Err: err}
|
|
}
|
|
|
|
// Make result.
|
|
result := &influxql.Result{
|
|
Series: make(influxql.Rows, 0, len(measurements)),
|
|
}
|
|
|
|
// Loop through measurements, adding a result row for each.
|
|
for _, m := range measurements {
|
|
// Create a new row.
|
|
r := &influxql.Row{
|
|
Name: m.Name,
|
|
Columns: []string{"fieldKey"},
|
|
}
|
|
|
|
// Get a list of field names from the measurement then sort them.
|
|
names := m.FieldNames()
|
|
sort.Strings(names)
|
|
|
|
// Add the field names to the result row values.
|
|
for _, n := range names {
|
|
v := interface{}(n)
|
|
r.Values = append(r.Values, []interface{}{v})
|
|
}
|
|
|
|
// Append the row to the result.
|
|
result.Series = append(result.Series, r)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// measurementsFromSourcesOrDB returns a list of measurements from the
|
|
// sources passed in or, if sources is empty, a list of all
|
|
// measurement names from the database passed in.
|
|
func measurementsFromSourcesOrDB(db *DatabaseIndex, sources ...influxql.Source) (Measurements, error) {
|
|
var measurements Measurements
|
|
if len(sources) > 0 {
|
|
for _, source := range sources {
|
|
if m, ok := source.(*influxql.Measurement); ok {
|
|
measurement := db.measurements[m.Name]
|
|
if measurement == nil {
|
|
return nil, ErrMeasurementNotFound(m.Name)
|
|
}
|
|
|
|
measurements = append(measurements, measurement)
|
|
} else {
|
|
return nil, errors.New("identifiers in FROM clause must be measurement names")
|
|
}
|
|
}
|
|
} else {
|
|
// No measurements specified in FROM clause so get all measurements that have series.
|
|
for _, m := range db.Measurements() {
|
|
if m.HasSeries() {
|
|
measurements = append(measurements, m)
|
|
}
|
|
}
|
|
}
|
|
sort.Sort(measurements)
|
|
|
|
return measurements, nil
|
|
}
|
|
|
|
// normalizeStatement adds a default database and policy to the measurements in statement.
|
|
func (q *QueryExecutor) normalizeStatement(stmt influxql.Statement, defaultDatabase string) (err error) {
|
|
// Track prefixes for replacing field names.
|
|
prefixes := make(map[string]string)
|
|
|
|
// Qualify all measurements.
|
|
influxql.WalkFunc(stmt, func(n influxql.Node) {
|
|
if err != nil {
|
|
return
|
|
}
|
|
switch n := n.(type) {
|
|
case *influxql.Measurement:
|
|
e := q.normalizeMeasurement(n, defaultDatabase)
|
|
if e != nil {
|
|
err = e
|
|
return
|
|
}
|
|
prefixes[n.Name] = n.Name
|
|
}
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Replace all variable references that used measurement prefixes.
|
|
influxql.WalkFunc(stmt, func(n influxql.Node) {
|
|
switch n := n.(type) {
|
|
case *influxql.VarRef:
|
|
for k, v := range prefixes {
|
|
if strings.HasPrefix(n.Val, k+".") {
|
|
n.Val = v + "." + influxql.QuoteIdent(n.Val[len(k)+1:])
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
// normalizeMeasurement inserts the default database or policy into all measurement names,
|
|
// if required.
|
|
func (q *QueryExecutor) normalizeMeasurement(m *influxql.Measurement, defaultDatabase string) error {
|
|
if m.Name == "" && m.Regex == nil {
|
|
return errors.New("invalid measurement")
|
|
}
|
|
|
|
// Measurement does not have an explicit database? Insert default.
|
|
if m.Database == "" {
|
|
m.Database = defaultDatabase
|
|
}
|
|
|
|
// The database must now be specified by this point.
|
|
if m.Database == "" {
|
|
return errors.New("database name required")
|
|
}
|
|
|
|
// Find database.
|
|
di, err := q.MetaStore.Database(m.Database)
|
|
if err != nil {
|
|
return err
|
|
} else if di == nil {
|
|
return ErrDatabaseNotFound(m.Database)
|
|
}
|
|
|
|
// If no retention policy was specified, use the default.
|
|
if m.RetentionPolicy == "" {
|
|
if di.DefaultRetentionPolicy == "" {
|
|
return fmt.Errorf("default retention policy not set for: %s", di.Name)
|
|
}
|
|
m.RetentionPolicy = di.DefaultRetentionPolicy
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (q *QueryExecutor) executeShowDiagnosticsStatement(stmt *influxql.ShowDiagnosticsStatement) *influxql.Result {
|
|
return &influxql.Result{Err: fmt.Errorf("SHOW DIAGNOSTICS is not implemented yet")}
|
|
}
|
|
|
|
// ErrAuthorize represents an authorization error.
|
|
type ErrAuthorize struct {
|
|
q *QueryExecutor
|
|
query *influxql.Query
|
|
user string
|
|
database string
|
|
message string
|
|
}
|
|
|
|
const authErrLogFmt string = "unauthorized request | user: %q | query: %q | database %q\n"
|
|
|
|
// newAuthorizationError returns a new instance of AuthorizationError.
|
|
func NewErrAuthorize(qe *QueryExecutor, q *influxql.Query, u, db, m string) *ErrAuthorize {
|
|
return &ErrAuthorize{q: qe, query: q, user: u, database: db, message: m}
|
|
}
|
|
|
|
// Error returns the text of the error.
|
|
func (e ErrAuthorize) Error() string {
|
|
e.q.Logger.Printf(authErrLogFmt, e.user, e.query.String(), e.database)
|
|
if e.user == "" {
|
|
return fmt.Sprint(e.message)
|
|
}
|
|
return fmt.Sprintf("%s not authorized to execute %s", e.user, e.message)
|
|
}
|
|
|
|
var (
|
|
// ErrInvalidQuery is returned when executing an unknown query type.
|
|
ErrInvalidQuery = errors.New("invalid query")
|
|
|
|
// ErrNotExecuted is returned when a statement is not executed in a query.
|
|
// This can occur when a previous statement in the same query has errored.
|
|
ErrNotExecuted = errors.New("not executed")
|
|
)
|
|
|
|
func ErrDatabaseNotFound(name string) error { return fmt.Errorf("database not found: %s", name) }
|
|
|
|
func ErrMeasurementNotFound(name string) error { return fmt.Errorf("measurement not found: %s", name) }
|