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 }