github.com/influxdata/influxdb/v2@v2.7.6/influxql/query/executor.go (about)

     1  package query
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"runtime/debug"
     9  	"strconv"
    10  	"time"
    11  
    12  	iql "github.com/influxdata/influxdb/v2/influxql"
    13  	"github.com/influxdata/influxdb/v2/influxql/control"
    14  	"github.com/influxdata/influxdb/v2/kit/platform"
    15  	"github.com/influxdata/influxdb/v2/kit/tracing"
    16  	"github.com/influxdata/influxdb/v2/models"
    17  	"github.com/influxdata/influxql"
    18  	"github.com/opentracing/opentracing-go/log"
    19  	"go.uber.org/zap"
    20  )
    21  
    22  var (
    23  	// ErrInvalidQuery is returned when executing an unknown query type.
    24  	ErrInvalidQuery = errors.New("invalid query")
    25  
    26  	// ErrNotExecuted is returned when a statement is not executed in a query.
    27  	// This can occur when a previous statement in the same query has errored.
    28  	ErrNotExecuted = errors.New("not executed")
    29  
    30  	// ErrQueryInterrupted is an error returned when the query is interrupted.
    31  	ErrQueryInterrupted = errors.New("query interrupted")
    32  )
    33  
    34  const (
    35  	// PanicCrashEnv is the environment variable that, when set, will prevent
    36  	// the handler from recovering any panics.
    37  	PanicCrashEnv = "INFLUXDB_PANIC_CRASH"
    38  )
    39  
    40  // ErrDatabaseNotFound returns a database not found error for the given database name.
    41  func ErrDatabaseNotFound(name string) error { return fmt.Errorf("database not found: %s", name) }
    42  
    43  // ErrMaxSelectPointsLimitExceeded is an error when a query hits the maximum number of points.
    44  func ErrMaxSelectPointsLimitExceeded(n, limit int) error {
    45  	return fmt.Errorf("max-select-point limit exceeed: (%d/%d)", n, limit)
    46  }
    47  
    48  // ErrMaxConcurrentQueriesLimitExceeded is an error when a query cannot be run
    49  // because the maximum number of queries has been reached.
    50  func ErrMaxConcurrentQueriesLimitExceeded(n, limit int) error {
    51  	return fmt.Errorf("max-concurrent-queries limit exceeded(%d, %d)", n, limit)
    52  }
    53  
    54  // Authorizer determines if certain operations are authorized.
    55  type Authorizer interface {
    56  	// AuthorizeDatabase indicates whether the given Privilege is authorized on the database with the given name.
    57  	AuthorizeDatabase(p influxql.Privilege, name string) bool
    58  
    59  	// AuthorizeQuery returns an error if the query cannot be executed
    60  	AuthorizeQuery(database string, query *influxql.Query) error
    61  
    62  	// AuthorizeSeriesRead determines if a series is authorized for reading
    63  	AuthorizeSeriesRead(database string, measurement []byte, tags models.Tags) bool
    64  
    65  	// AuthorizeSeriesWrite determines if a series is authorized for writing
    66  	AuthorizeSeriesWrite(database string, measurement []byte, tags models.Tags) bool
    67  }
    68  
    69  // OpenAuthorizer is the Authorizer used when authorization is disabled.
    70  // It allows all operations.
    71  type openAuthorizer struct{}
    72  
    73  // OpenAuthorizer can be shared by all goroutines.
    74  var OpenAuthorizer = openAuthorizer{}
    75  
    76  // AuthorizeDatabase returns true to allow any operation on a database.
    77  func (a openAuthorizer) AuthorizeDatabase(influxql.Privilege, string) bool { return true }
    78  
    79  // AuthorizeSeriesRead allows access to any series.
    80  func (a openAuthorizer) AuthorizeSeriesRead(database string, measurement []byte, tags models.Tags) bool {
    81  	return true
    82  }
    83  
    84  // AuthorizeSeriesWrite allows access to any series.
    85  func (a openAuthorizer) AuthorizeSeriesWrite(database string, measurement []byte, tags models.Tags) bool {
    86  	return true
    87  }
    88  
    89  // AuthorizeSeriesRead allows any query to execute.
    90  func (a openAuthorizer) AuthorizeQuery(_ string, _ *influxql.Query) error { return nil }
    91  
    92  // AuthorizerIsOpen returns true if the provided Authorizer is guaranteed to
    93  // authorize anything. A nil Authorizer returns true for this function, and this
    94  // function should be preferred over directly checking if an Authorizer is nil
    95  // or not.
    96  func AuthorizerIsOpen(a Authorizer) bool {
    97  	if u, ok := a.(interface{ AuthorizeUnrestricted() bool }); ok {
    98  		return u.AuthorizeUnrestricted()
    99  	}
   100  	return a == nil || a == OpenAuthorizer
   101  }
   102  
   103  // ExecutionOptions contains the options for executing a query.
   104  type ExecutionOptions struct {
   105  	// OrgID is the organization for which this query is being executed.
   106  	OrgID platform.ID
   107  
   108  	// The database the query is running against.
   109  	Database string
   110  
   111  	// The retention policy the query is running against.
   112  	RetentionPolicy string
   113  
   114  	// How to determine whether the query is allowed to execute,
   115  	// what resources can be returned in SHOW queries, etc.
   116  	Authorizer Authorizer
   117  
   118  	// The requested maximum number of points to return in each result.
   119  	ChunkSize int
   120  
   121  	// If this query is being executed in a read-only context.
   122  	ReadOnly bool
   123  
   124  	// Node to execute on.
   125  	NodeID uint64
   126  
   127  	// Quiet suppresses non-essential output from the query executor.
   128  	Quiet bool
   129  }
   130  
   131  type (
   132  	iteratorsContextKey struct{}
   133  )
   134  
   135  // NewContextWithIterators returns a new context.Context with the *Iterators slice added.
   136  // The query planner will add instances of AuxIterator to the Iterators slice.
   137  func NewContextWithIterators(ctx context.Context, itr *Iterators) context.Context {
   138  	return context.WithValue(ctx, iteratorsContextKey{}, itr)
   139  }
   140  
   141  // StatementExecutor executes a statement within the Executor.
   142  type StatementExecutor interface {
   143  	// ExecuteStatement executes a statement. Results should be sent to the
   144  	// results channel in the ExecutionContext.
   145  	ExecuteStatement(ctx context.Context, stmt influxql.Statement, ectx *ExecutionContext) error
   146  }
   147  
   148  // StatementNormalizer normalizes a statement before it is executed.
   149  type StatementNormalizer interface {
   150  	// NormalizeStatement adds a default database and policy to the
   151  	// measurements in the statement.
   152  	NormalizeStatement(ctx context.Context, stmt influxql.Statement, database, retentionPolicy string, ectx *ExecutionContext) error
   153  }
   154  
   155  var (
   156  	nullNormalizer StatementNormalizer = &nullNormalizerImpl{}
   157  )
   158  
   159  type nullNormalizerImpl struct{}
   160  
   161  func (n *nullNormalizerImpl) NormalizeStatement(ctx context.Context, stmt influxql.Statement, database, retentionPolicy string, ectx *ExecutionContext) error {
   162  	return nil
   163  }
   164  
   165  // Executor executes every statement in an Query.
   166  type Executor struct {
   167  	// Used for executing a statement in the query.
   168  	StatementExecutor StatementExecutor
   169  
   170  	// StatementNormalizer normalizes a statement before it is executed.
   171  	StatementNormalizer StatementNormalizer
   172  
   173  	Metrics *control.ControllerMetrics
   174  
   175  	log *zap.Logger
   176  }
   177  
   178  // NewExecutor returns a new instance of Executor.
   179  func NewExecutor(logger *zap.Logger, cm *control.ControllerMetrics) *Executor {
   180  	return &Executor{
   181  		StatementNormalizer: nullNormalizer,
   182  		Metrics:             cm,
   183  		log:                 logger.With(zap.String("service", "query")),
   184  	}
   185  }
   186  
   187  // Close kills all running queries and prevents new queries from being attached.
   188  func (e *Executor) Close() error {
   189  	return nil
   190  }
   191  
   192  // ExecuteQuery executes each statement within a query.
   193  func (e *Executor) ExecuteQuery(ctx context.Context, query *influxql.Query, opt ExecutionOptions) (<-chan *Result, *iql.Statistics) {
   194  	results := make(chan *Result)
   195  	statistics := new(iql.Statistics)
   196  	go e.executeQuery(ctx, query, opt, results, statistics)
   197  	return results, statistics
   198  }
   199  
   200  func (e *Executor) executeQuery(ctx context.Context, query *influxql.Query, opt ExecutionOptions, results chan *Result, statistics *iql.Statistics) {
   201  	span, ctx := tracing.StartSpanFromContext(ctx)
   202  	defer func() {
   203  		close(results)
   204  		span.Finish()
   205  	}()
   206  
   207  	defer e.recover(query, results)
   208  
   209  	gatherer := new(iql.StatisticsGatherer)
   210  
   211  	statusLabel := control.LabelSuccess
   212  	defer func(start time.Time) {
   213  		dur := time.Since(start)
   214  		e.Metrics.ExecutingDuration.WithLabelValues(statusLabel).Observe(dur.Seconds())
   215  	}(time.Now())
   216  
   217  	ectx := &ExecutionContext{StatisticsGatherer: gatherer, ExecutionOptions: opt}
   218  
   219  	// Setup the execution context that will be used when executing statements.
   220  	ectx.Results = results
   221  
   222  	var i int
   223  LOOP:
   224  	for ; i < len(query.Statements); i++ {
   225  		ectx.statementID = i
   226  		stmt := query.Statements[i]
   227  
   228  		// If a default database wasn't passed in by the caller, check the statement.
   229  		defaultDB := opt.Database
   230  		if defaultDB == "" {
   231  			if s, ok := stmt.(influxql.HasDefaultDatabase); ok {
   232  				defaultDB = s.DefaultDatabase()
   233  			}
   234  		}
   235  
   236  		// Do not let queries manually use the system measurements. If we find
   237  		// one, return an error. This prevents a person from using the
   238  		// measurement incorrectly and causing a panic.
   239  		if stmt, ok := stmt.(*influxql.SelectStatement); ok {
   240  			for _, s := range stmt.Sources {
   241  				switch s := s.(type) {
   242  				case *influxql.Measurement:
   243  					if influxql.IsSystemName(s.Name) {
   244  						command := "the appropriate meta command"
   245  						switch s.Name {
   246  						case "_fieldKeys":
   247  							command = "SHOW FIELD KEYS"
   248  						case "_measurements":
   249  							command = "SHOW MEASUREMENTS"
   250  						case "_series":
   251  							command = "SHOW SERIES"
   252  						case "_tagKeys":
   253  							command = "SHOW TAG KEYS"
   254  						case "_tags":
   255  							command = "SHOW TAG VALUES"
   256  						}
   257  						_ = ectx.Send(ctx, &Result{
   258  							Err: fmt.Errorf("unable to use system source '%s': use %s instead", s.Name, command),
   259  						})
   260  						break LOOP
   261  					}
   262  				}
   263  			}
   264  		}
   265  
   266  		// Rewrite statements, if necessary.
   267  		// This can occur on meta read statements which convert to SELECT statements.
   268  		newStmt, err := RewriteStatement(stmt)
   269  		if err != nil {
   270  			_ = ectx.Send(ctx, &Result{Err: err})
   271  			break
   272  		}
   273  		stmt = newStmt
   274  
   275  		if err := e.StatementNormalizer.NormalizeStatement(ctx, stmt, defaultDB, opt.RetentionPolicy, ectx); err != nil {
   276  			if err := ectx.Send(ctx, &Result{Err: err}); err != nil {
   277  				return
   278  			}
   279  			break
   280  		}
   281  
   282  		statistics.StatementCount += 1
   283  
   284  		// Log each normalized statement.
   285  		if !ectx.Quiet {
   286  			e.log.Info("Executing query", zap.Stringer("query", stmt))
   287  			span.LogFields(log.String("normalized_query", stmt.String()))
   288  		}
   289  
   290  		gatherer.Reset()
   291  		stmtStart := time.Now()
   292  		// Send any other statements to the underlying statement executor.
   293  		err = tracing.LogError(span, e.StatementExecutor.ExecuteStatement(ctx, stmt, ectx))
   294  		stmtDur := time.Since(stmtStart)
   295  		stmtStats := gatherer.Statistics()
   296  		stmtStats.ExecuteDuration = stmtDur - stmtStats.PlanDuration
   297  		statistics.Add(stmtStats)
   298  
   299  		// Send an error for this result if it failed for some reason.
   300  		if err != nil {
   301  			statusLabel = control.LabelNotExecuted
   302  			e.Metrics.Requests.WithLabelValues(statusLabel).Inc()
   303  			_ = ectx.Send(ctx, &Result{
   304  				StatementID: i,
   305  				Err:         err,
   306  			})
   307  			// Stop after the first error.
   308  			break
   309  		}
   310  
   311  		e.Metrics.Requests.WithLabelValues(statusLabel).Inc()
   312  
   313  		// Check if the query was interrupted during an uninterruptible statement.
   314  		if err := ctx.Err(); err != nil {
   315  			statusLabel = control.LabelInterruptedErr
   316  			e.Metrics.Requests.WithLabelValues(statusLabel).Inc()
   317  			break
   318  		}
   319  	}
   320  
   321  	// Send error results for any statements which were not executed.
   322  	for ; i < len(query.Statements)-1; i++ {
   323  		if err := ectx.Send(ctx, &Result{
   324  			StatementID: i,
   325  			Err:         ErrNotExecuted,
   326  		}); err != nil {
   327  			break
   328  		}
   329  	}
   330  }
   331  
   332  // Determines if the Executor will recover any panics or let them crash
   333  // the server.
   334  var willCrash bool
   335  
   336  func init() {
   337  	var err error
   338  	if willCrash, err = strconv.ParseBool(os.Getenv(PanicCrashEnv)); err != nil {
   339  		willCrash = false
   340  	}
   341  }
   342  
   343  func (e *Executor) recover(query *influxql.Query, results chan *Result) {
   344  	if err := recover(); err != nil {
   345  		e.log.Error(fmt.Sprintf("%s [panic:%s] %s", query.String(), err, debug.Stack()))
   346  		results <- &Result{
   347  			StatementID: -1,
   348  			Err:         fmt.Errorf("%s [panic:%s]", query.String(), err),
   349  		}
   350  
   351  		if willCrash {
   352  			e.log.Error("\n\n=====\nAll goroutines now follow:")
   353  			e.log.Error(string(debug.Stack()))
   354  			os.Exit(1)
   355  		}
   356  	}
   357  }