vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/query_engine.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package tabletserver 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/json" 23 "fmt" 24 "net/http" 25 "strings" 26 "sync" 27 "sync/atomic" 28 "time" 29 30 vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" 31 "vitess.io/vitess/go/vt/vterrors" 32 33 "vitess.io/vitess/go/acl" 34 "vitess.io/vitess/go/cache" 35 "vitess.io/vitess/go/mysql" 36 "vitess.io/vitess/go/pools" 37 "vitess.io/vitess/go/stats" 38 "vitess.io/vitess/go/streamlog" 39 "vitess.io/vitess/go/sync2" 40 "vitess.io/vitess/go/trace" 41 "vitess.io/vitess/go/vt/dbconnpool" 42 "vitess.io/vitess/go/vt/log" 43 "vitess.io/vitess/go/vt/logutil" 44 "vitess.io/vitess/go/vt/sqlparser" 45 "vitess.io/vitess/go/vt/tableacl" 46 tacl "vitess.io/vitess/go/vt/tableacl/acl" 47 "vitess.io/vitess/go/vt/vttablet/tabletserver/connpool" 48 "vitess.io/vitess/go/vt/vttablet/tabletserver/planbuilder" 49 "vitess.io/vitess/go/vt/vttablet/tabletserver/rules" 50 "vitess.io/vitess/go/vt/vttablet/tabletserver/schema" 51 "vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv" 52 "vitess.io/vitess/go/vt/vttablet/tabletserver/txserializer" 53 ) 54 55 // _______________________________________________ 56 57 // TabletPlan wraps the planbuilder's exec plan to enforce additional rules 58 // and track stats. 59 type TabletPlan struct { 60 *planbuilder.Plan 61 Original string 62 Rules *rules.Rules 63 Authorized []*tableacl.ACLResult 64 65 QueryCount uint64 66 Time uint64 67 MysqlTime uint64 68 RowsAffected uint64 69 RowsReturned uint64 70 ErrorCount uint64 71 } 72 73 // AddStats updates the stats for the current TabletPlan. 74 func (ep *TabletPlan) AddStats(queryCount uint64, duration, mysqlTime time.Duration, rowsAffected, rowsReturned, errorCount uint64) { 75 atomic.AddUint64(&ep.QueryCount, queryCount) 76 atomic.AddUint64(&ep.Time, uint64(duration)) 77 atomic.AddUint64(&ep.MysqlTime, uint64(mysqlTime)) 78 atomic.AddUint64(&ep.RowsAffected, rowsAffected) 79 atomic.AddUint64(&ep.RowsReturned, rowsReturned) 80 atomic.AddUint64(&ep.ErrorCount, errorCount) 81 } 82 83 // Stats returns the current stats of TabletPlan. 84 func (ep *TabletPlan) Stats() (queryCount uint64, duration, mysqlTime time.Duration, rowsAffected, rowsReturned, errorCount uint64) { 85 queryCount = atomic.LoadUint64(&ep.QueryCount) 86 duration = time.Duration(atomic.LoadUint64(&ep.Time)) 87 mysqlTime = time.Duration(atomic.LoadUint64(&ep.MysqlTime)) 88 rowsAffected = atomic.LoadUint64(&ep.RowsAffected) 89 rowsReturned = atomic.LoadUint64(&ep.RowsReturned) 90 errorCount = atomic.LoadUint64(&ep.ErrorCount) 91 return 92 } 93 94 // buildAuthorized builds 'Authorized', which is the runtime part for 'Permissions'. 95 func (ep *TabletPlan) buildAuthorized() { 96 ep.Authorized = make([]*tableacl.ACLResult, len(ep.Permissions)) 97 for i, perm := range ep.Permissions { 98 ep.Authorized[i] = tableacl.Authorized(perm.TableName, perm.Role) 99 } 100 } 101 102 func (ep *TabletPlan) IsValid(hasReservedCon, hasSysSettings bool) error { 103 if !ep.NeedsReservedConn { 104 return nil 105 } 106 return isValid(ep.PlanID, hasReservedCon, hasSysSettings) 107 } 108 109 func isValid(planType planbuilder.PlanType, hasReservedCon bool, hasSysSettings bool) error { 110 switch planType { 111 case planbuilder.PlanSelectLockFunc, planbuilder.PlanDDL: 112 if hasReservedCon { 113 return nil 114 } 115 case planbuilder.PlanSet: 116 if hasReservedCon || hasSysSettings { 117 return nil 118 } 119 } 120 return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "%s not allowed without reserved connection", planType.String()) 121 } 122 123 // _______________________________________________ 124 125 // QueryEngine implements the core functionality of tabletserver. 126 // It assumes that no requests will be sent to it before Open is 127 // called and succeeds. 128 // Shutdown is done in the following order: 129 // 130 // Close: There should be no more pending queries when this 131 // function is called. 132 type QueryEngine struct { 133 isOpen bool 134 env tabletenv.Env 135 se *schema.Engine 136 137 // mu protects the following fields. 138 mu sync.RWMutex 139 tables map[string]*schema.Table 140 plans cache.Cache 141 queryRuleSources *rules.Map 142 143 // Pools 144 conns *connpool.Pool 145 streamConns *connpool.Pool 146 147 // Services 148 consolidator *sync2.Consolidator 149 streamConsolidator *StreamConsolidator 150 // txSerializer protects vttablet from applications which try to concurrently 151 // UPDATE (or DELETE) a "hot" row (or range of rows). 152 // Such queries would be serialized by MySQL anyway. This serializer prevents 153 // that we start more than one transaction per hot row (range). 154 // For implementation details, please see BeginExecute() in tabletserver.go. 155 txSerializer *txserializer.TxSerializer 156 157 // Vars 158 maxResultSize sync2.AtomicInt64 159 warnResultSize sync2.AtomicInt64 160 streamBufferSize sync2.AtomicInt64 161 // tableaclExemptCount count the number of accesses allowed 162 // based on membership in the superuser ACL 163 tableaclExemptCount sync2.AtomicInt64 164 strictTableACL bool 165 enableTableACLDryRun bool 166 // TODO(sougou) There are two acl packages. Need to rename. 167 exemptACL tacl.ACL 168 169 strictTransTables bool 170 171 consolidatorMode sync2.AtomicString 172 173 // stats 174 queryCounts, queryTimes, queryErrorCounts, queryRowsAffected, queryRowsReturned *stats.CountersWithMultiLabels 175 176 // Loggers 177 accessCheckerLogger *logutil.ThrottledLogger 178 } 179 180 // NewQueryEngine creates a new QueryEngine. 181 // This is a singleton class. 182 // You must call this only once. 183 func NewQueryEngine(env tabletenv.Env, se *schema.Engine) *QueryEngine { 184 config := env.Config() 185 cacheCfg := &cache.Config{ 186 MaxEntries: int64(config.QueryCacheSize), 187 MaxMemoryUsage: config.QueryCacheMemory, 188 LFU: config.QueryCacheLFU, 189 } 190 191 qe := &QueryEngine{ 192 env: env, 193 se: se, 194 tables: make(map[string]*schema.Table), 195 plans: cache.NewDefaultCacheImpl(cacheCfg), 196 queryRuleSources: rules.NewMap(), 197 } 198 199 qe.conns = connpool.NewPool(env, "ConnPool", config.OltpReadPool) 200 qe.streamConns = connpool.NewPool(env, "StreamConnPool", config.OlapReadPool) 201 qe.consolidatorMode.Set(config.Consolidator) 202 qe.consolidator = sync2.NewConsolidator() 203 if config.ConsolidatorStreamTotalSize > 0 && config.ConsolidatorStreamQuerySize > 0 { 204 log.Infof("Stream consolidator is enabled with query size set to %d and total size set to %d.", 205 config.ConsolidatorStreamQuerySize, config.ConsolidatorStreamTotalSize) 206 qe.streamConsolidator = NewStreamConsolidator(config.ConsolidatorStreamTotalSize, config.ConsolidatorStreamQuerySize, returnStreamResult) 207 } else { 208 log.Info("Stream consolidator is not enabled.") 209 } 210 qe.txSerializer = txserializer.New(env) 211 212 qe.strictTableACL = config.StrictTableACL 213 qe.enableTableACLDryRun = config.EnableTableACLDryRun 214 215 qe.strictTransTables = config.EnforceStrictTransTables 216 217 if config.TableACLExemptACL != "" { 218 if f, err := tableacl.GetCurrentACLFactory(); err == nil { 219 if exemptACL, err := f.New([]string{config.TableACLExemptACL}); err == nil { 220 log.Infof("Setting Table ACL exempt rule for %v", config.TableACLExemptACL) 221 qe.exemptACL = exemptACL 222 } else { 223 log.Infof("Cannot build exempt ACL for table ACL: %v", err) 224 } 225 } else { 226 log.Infof("Cannot get current ACL Factory: %v", err) 227 } 228 } 229 230 qe.maxResultSize = sync2.NewAtomicInt64(int64(config.Oltp.MaxRows)) 231 qe.warnResultSize = sync2.NewAtomicInt64(int64(config.Oltp.WarnRows)) 232 qe.streamBufferSize = sync2.NewAtomicInt64(int64(config.StreamBufferSize)) 233 234 planbuilder.PassthroughDMLs = config.PassthroughDML 235 236 qe.accessCheckerLogger = logutil.NewThrottledLogger("accessChecker", 1*time.Second) 237 238 env.Exporter().NewGaugeFunc("MaxResultSize", "Query engine max result size", qe.maxResultSize.Get) 239 env.Exporter().NewGaugeFunc("WarnResultSize", "Query engine warn result size", qe.warnResultSize.Get) 240 env.Exporter().NewGaugeFunc("StreamBufferSize", "Query engine stream buffer size", qe.streamBufferSize.Get) 241 env.Exporter().NewCounterFunc("TableACLExemptCount", "Query engine table ACL exempt count", qe.tableaclExemptCount.Get) 242 243 env.Exporter().NewGaugeFunc("QueryCacheLength", "Query engine query cache length", func() int64 { 244 return int64(qe.plans.Len()) 245 }) 246 env.Exporter().NewGaugeFunc("QueryCacheSize", "Query engine query cache size", qe.plans.UsedCapacity) 247 env.Exporter().NewGaugeFunc("QueryCacheCapacity", "Query engine query cache capacity", qe.plans.MaxCapacity) 248 env.Exporter().NewCounterFunc("QueryCacheEvictions", "Query engine query cache evictions", qe.plans.Evictions) 249 qe.queryCounts = env.Exporter().NewCountersWithMultiLabels("QueryCounts", "query counts", []string{"Table", "Plan"}) 250 qe.queryTimes = env.Exporter().NewCountersWithMultiLabels("QueryTimesNs", "query times in ns", []string{"Table", "Plan"}) 251 qe.queryRowsAffected = env.Exporter().NewCountersWithMultiLabels("QueryRowsAffected", "query rows affected", []string{"Table", "Plan"}) 252 qe.queryRowsReturned = env.Exporter().NewCountersWithMultiLabels("QueryRowsReturned", "query rows returned", []string{"Table", "Plan"}) 253 qe.queryErrorCounts = env.Exporter().NewCountersWithMultiLabels("QueryErrorCounts", "query error counts", []string{"Table", "Plan"}) 254 255 env.Exporter().HandleFunc("/debug/hotrows", qe.txSerializer.ServeHTTP) 256 env.Exporter().HandleFunc("/debug/tablet_plans", qe.handleHTTPQueryPlans) 257 env.Exporter().HandleFunc("/debug/query_stats", qe.handleHTTPQueryStats) 258 env.Exporter().HandleFunc("/debug/query_rules", qe.handleHTTPQueryRules) 259 env.Exporter().HandleFunc("/debug/consolidations", qe.handleHTTPConsolidations) 260 env.Exporter().HandleFunc("/debug/acl", qe.handleHTTPAclJSON) 261 262 return qe 263 } 264 265 // Open must be called before sending requests to QueryEngine. 266 func (qe *QueryEngine) Open() error { 267 if qe.isOpen { 268 return nil 269 } 270 log.Info("Query Engine: opening") 271 272 qe.conns.Open(qe.env.Config().DB.AppWithDB(), qe.env.Config().DB.DbaWithDB(), qe.env.Config().DB.AppDebugWithDB()) 273 274 conn, err := qe.conns.Get(tabletenv.LocalContext(), nil) 275 if err != nil { 276 qe.conns.Close() 277 return err 278 } 279 err = conn.VerifyMode(qe.strictTransTables) 280 // Recycle needs to happen before error check. 281 // Otherwise, qe.conns.Close will hang. 282 conn.Recycle() 283 284 if err != nil { 285 qe.conns.Close() 286 return err 287 } 288 289 qe.streamConns.Open(qe.env.Config().DB.AppWithDB(), qe.env.Config().DB.DbaWithDB(), qe.env.Config().DB.AppDebugWithDB()) 290 qe.se.RegisterNotifier("qe", qe.schemaChanged) 291 qe.isOpen = true 292 return nil 293 } 294 295 // Close must be called to shut down QueryEngine. 296 // You must ensure that no more queries will be sent 297 // before calling Close. 298 func (qe *QueryEngine) Close() { 299 if !qe.isOpen { 300 return 301 } 302 // Close in reverse order of Open. 303 qe.se.UnregisterNotifier("qe") 304 qe.plans.Clear() 305 qe.tables = make(map[string]*schema.Table) 306 qe.streamConns.Close() 307 qe.conns.Close() 308 qe.isOpen = false 309 log.Info("Query Engine: closed") 310 } 311 312 // GetPlan returns the TabletPlan that for the query. Plans are cached in a cache.LRUCache. 313 func (qe *QueryEngine) GetPlan(ctx context.Context, logStats *tabletenv.LogStats, sql string, skipQueryPlanCache bool) (*TabletPlan, error) { 314 span, _ := trace.NewSpan(ctx, "QueryEngine.GetPlan") 315 defer span.Finish() 316 if !skipQueryPlanCache { 317 if plan := qe.getQuery(sql); plan != nil { 318 logStats.CachedPlan = true 319 return plan, nil 320 } 321 } 322 // Obtain read lock to prevent schema from changing while 323 // we build a plan. The read lock allows multiple identical 324 // queries to build the same plan. One of them will win by 325 // updating the query cache and prevent future races. Due to 326 // this, query stats reporting may not be accurate, but it's 327 // acceptable because those numbers are best effort. 328 qe.mu.RLock() 329 defer qe.mu.RUnlock() 330 statement, err := sqlparser.Parse(sql) 331 if err != nil { 332 return nil, err 333 } 334 splan, err := planbuilder.Build(statement, qe.tables, qe.env.Config().DB.DBName, qe.env.Config().EnableViews) 335 if err != nil { 336 return nil, err 337 } 338 plan := &TabletPlan{Plan: splan, Original: sql} 339 plan.Rules = qe.queryRuleSources.FilterByPlan(sql, plan.PlanID, plan.TableNames()...) 340 plan.buildAuthorized() 341 if plan.PlanID == planbuilder.PlanDDL || plan.PlanID == planbuilder.PlanSet { 342 return plan, nil 343 } 344 if !skipQueryPlanCache && !sqlparser.SkipQueryPlanCacheDirective(statement) { 345 qe.plans.Set(sql, plan) 346 } 347 return plan, nil 348 } 349 350 // GetStreamPlan is similar to GetPlan, but doesn't use the cache 351 // and doesn't enforce a limit. It just returns the parsed query. 352 func (qe *QueryEngine) GetStreamPlan(sql string) (*TabletPlan, error) { 353 qe.mu.RLock() 354 defer qe.mu.RUnlock() 355 splan, err := planbuilder.BuildStreaming(sql, qe.tables) 356 if err != nil { 357 return nil, err 358 } 359 plan := &TabletPlan{Plan: splan, Original: sql} 360 plan.Rules = qe.queryRuleSources.FilterByPlan(sql, plan.PlanID, plan.TableName().String()) 361 plan.buildAuthorized() 362 return plan, nil 363 } 364 365 // GetMessageStreamPlan builds a plan for Message streaming. 366 func (qe *QueryEngine) GetMessageStreamPlan(name string) (*TabletPlan, error) { 367 qe.mu.RLock() 368 defer qe.mu.RUnlock() 369 splan, err := planbuilder.BuildMessageStreaming(name, qe.tables) 370 if err != nil { 371 return nil, err 372 } 373 plan := &TabletPlan{Plan: splan} 374 plan.Rules = qe.queryRuleSources.FilterByPlan("stream from "+name, plan.PlanID, plan.TableName().String()) 375 plan.buildAuthorized() 376 return plan, nil 377 } 378 379 // GetConnSetting returns system settings for the connection. 380 func (qe *QueryEngine) GetConnSetting(ctx context.Context, settings []string) (*pools.Setting, error) { 381 span, _ := trace.NewSpan(ctx, "QueryEngine.GetConnSetting") 382 defer span.Finish() 383 384 var keyBuilder strings.Builder 385 for _, q := range settings { 386 keyBuilder.WriteString(q) 387 } 388 389 // try to get the connSetting from the cache 390 cacheKey := keyBuilder.String() 391 if plan := qe.getConnSetting(cacheKey); plan != nil { 392 return plan, nil 393 } 394 395 // build the setting queries 396 query, resetQuery, err := planbuilder.BuildSettingQuery(settings) 397 if err != nil { 398 return nil, err 399 } 400 connSetting := pools.NewSetting(query, resetQuery) 401 402 // store the connSetting in the cache 403 qe.plans.Set(cacheKey, connSetting) 404 405 return connSetting, nil 406 } 407 408 // ClearQueryPlanCache should be called if query plan cache is potentially obsolete 409 func (qe *QueryEngine) ClearQueryPlanCache() { 410 qe.plans.Clear() 411 } 412 413 // IsMySQLReachable returns an error if it cannot connect to MySQL. 414 // This can be called before opening the QueryEngine. 415 func (qe *QueryEngine) IsMySQLReachable() error { 416 conn, err := dbconnpool.NewDBConnection(context.TODO(), qe.env.Config().DB.AppWithDB()) 417 if err != nil { 418 if mysql.IsTooManyConnectionsErr(err) { 419 return nil 420 } 421 return err 422 } 423 conn.Close() 424 return nil 425 } 426 427 func (qe *QueryEngine) schemaChanged(tables map[string]*schema.Table, created, altered, dropped []string) { 428 qe.mu.Lock() 429 defer qe.mu.Unlock() 430 qe.tables = tables 431 if len(altered) != 0 || len(dropped) != 0 { 432 qe.plans.Clear() 433 } 434 } 435 436 // getQuery fetches the plan and makes it the most recent. 437 func (qe *QueryEngine) getQuery(sql string) *TabletPlan { 438 cacheResult, ok := qe.plans.Get(sql) 439 if !ok { 440 return nil 441 } 442 plan, ok := cacheResult.(*TabletPlan) 443 if ok { 444 return plan 445 } 446 return nil 447 } 448 449 func (qe *QueryEngine) getConnSetting(key string) *pools.Setting { 450 cacheResult, ok := qe.plans.Get(key) 451 if !ok { 452 return nil 453 } 454 plan, ok := cacheResult.(*pools.Setting) 455 if ok { 456 return plan 457 } 458 return nil 459 } 460 461 // SetQueryPlanCacheCap sets the query plan cache capacity. 462 func (qe *QueryEngine) SetQueryPlanCacheCap(size int) { 463 if size <= 0 { 464 size = 1 465 } 466 qe.plans.SetCapacity(int64(size)) 467 } 468 469 // QueryPlanCacheCap returns the capacity of the query cache. 470 func (qe *QueryEngine) QueryPlanCacheCap() int { 471 return int(qe.plans.MaxCapacity()) 472 } 473 474 // QueryPlanCacheLen returns the length (size in entries) of the query cache 475 func (qe *QueryEngine) QueryPlanCacheLen() int { 476 qe.plans.Wait() 477 return qe.plans.Len() 478 } 479 480 // AddStats adds the given stats for the planName.tableName 481 func (qe *QueryEngine) AddStats(planType planbuilder.PlanType, tableName string, queryCount int64, duration, mysqlTime time.Duration, rowsAffected, rowsReturned, errorCount int64) { 482 // table names can contain "." characters, replace them! 483 keys := []string{tableName, planType.String()} 484 qe.queryCounts.Add(keys, queryCount) 485 qe.queryTimes.Add(keys, int64(duration)) 486 qe.queryErrorCounts.Add(keys, errorCount) 487 488 // For certain plan types like select, we only want to add their metrics to rows returned 489 // But there are special cases like `SELECT ... INTO OUTFILE ''` which return positive rows affected 490 // So we check if it is positive and add that too. 491 switch planType { 492 case planbuilder.PlanSelect, planbuilder.PlanSelectStream, planbuilder.PlanSelectImpossible, planbuilder.PlanShow, planbuilder.PlanOtherRead: 493 qe.queryRowsReturned.Add(keys, rowsReturned) 494 if rowsAffected > 0 { 495 qe.queryRowsAffected.Add(keys, rowsAffected) 496 } 497 default: 498 qe.queryRowsAffected.Add(keys, rowsAffected) 499 if rowsReturned > 0 { 500 qe.queryRowsReturned.Add(keys, rowsReturned) 501 } 502 } 503 } 504 505 type perQueryStats struct { 506 Query string 507 Table string 508 Plan planbuilder.PlanType 509 QueryCount uint64 510 Time time.Duration 511 MysqlTime time.Duration 512 RowsAffected uint64 513 RowsReturned uint64 514 ErrorCount uint64 515 } 516 517 func (qe *QueryEngine) handleHTTPQueryPlans(response http.ResponseWriter, request *http.Request) { 518 if err := acl.CheckAccessHTTP(request, acl.DEBUGGING); err != nil { 519 acl.SendError(response, err) 520 return 521 } 522 523 response.Header().Set("Content-Type", "text/plain") 524 qe.plans.ForEach(func(value any) bool { 525 plan := value.(*TabletPlan) 526 response.Write([]byte(fmt.Sprintf("%#v\n", sqlparser.TruncateForUI(plan.Original)))) 527 if b, err := json.MarshalIndent(plan.Plan, "", " "); err != nil { 528 response.Write([]byte(err.Error())) 529 } else { 530 response.Write(b) 531 } 532 response.Write(([]byte)("\n\n")) 533 return true 534 }) 535 } 536 537 func (qe *QueryEngine) handleHTTPQueryStats(response http.ResponseWriter, request *http.Request) { 538 if err := acl.CheckAccessHTTP(request, acl.DEBUGGING); err != nil { 539 acl.SendError(response, err) 540 return 541 } 542 response.Header().Set("Content-Type", "application/json; charset=utf-8") 543 var qstats []perQueryStats 544 qe.plans.ForEach(func(value any) bool { 545 plan := value.(*TabletPlan) 546 547 var pqstats perQueryStats 548 pqstats.Query = unicoded(sqlparser.TruncateForUI(plan.Original)) 549 pqstats.Table = plan.TableName().String() 550 pqstats.Plan = plan.PlanID 551 pqstats.QueryCount, pqstats.Time, pqstats.MysqlTime, pqstats.RowsAffected, pqstats.RowsReturned, pqstats.ErrorCount = plan.Stats() 552 553 qstats = append(qstats, pqstats) 554 return true 555 }) 556 if b, err := json.MarshalIndent(qstats, "", " "); err != nil { 557 response.Write([]byte(err.Error())) 558 } else { 559 response.Write(b) 560 } 561 } 562 563 func (qe *QueryEngine) handleHTTPQueryRules(response http.ResponseWriter, request *http.Request) { 564 if err := acl.CheckAccessHTTP(request, acl.DEBUGGING); err != nil { 565 acl.SendError(response, err) 566 return 567 } 568 response.Header().Set("Content-Type", "application/json; charset=utf-8") 569 b, err := json.MarshalIndent(qe.queryRuleSources, "", " ") 570 if err != nil { 571 response.Write([]byte(err.Error())) 572 return 573 } 574 buf := bytes.NewBuffer(nil) 575 json.HTMLEscape(buf, b) 576 response.Write(buf.Bytes()) 577 } 578 579 func (qe *QueryEngine) handleHTTPAclJSON(response http.ResponseWriter, request *http.Request) { 580 if err := acl.CheckAccessHTTP(request, acl.DEBUGGING); err != nil { 581 acl.SendError(response, err) 582 return 583 } 584 aclConfig := tableacl.GetCurrentConfig() 585 if aclConfig == nil { 586 response.WriteHeader(http.StatusNotFound) 587 return 588 } 589 response.Header().Set("Content-Type", "application/json; charset=utf-8") 590 b, err := json.MarshalIndent(aclConfig, "", " ") 591 if err != nil { 592 response.Write([]byte(err.Error())) 593 return 594 } 595 buf := bytes.NewBuffer(nil) 596 json.HTMLEscape(buf, b) 597 response.Write(buf.Bytes()) 598 } 599 600 // ServeHTTP lists the most recent, cached queries and their count. 601 func (qe *QueryEngine) handleHTTPConsolidations(response http.ResponseWriter, request *http.Request) { 602 if err := acl.CheckAccessHTTP(request, acl.DEBUGGING); err != nil { 603 acl.SendError(response, err) 604 return 605 } 606 if err := acl.CheckAccessHTTP(request, acl.DEBUGGING); err != nil { 607 acl.SendError(response, err) 608 return 609 } 610 items := qe.consolidator.Items() 611 response.Header().Set("Content-Type", "text/plain") 612 if items == nil { 613 response.Write([]byte("empty\n")) 614 return 615 } 616 response.Write([]byte(fmt.Sprintf("Length: %d\n", len(items)))) 617 for _, v := range items { 618 var query string 619 if streamlog.GetRedactDebugUIQueries() { 620 query, _ = sqlparser.RedactSQLQuery(v.Query) 621 } else { 622 query = v.Query 623 } 624 response.Write([]byte(fmt.Sprintf("%v: %s\n", v.Count, query))) 625 } 626 } 627 628 // unicoded returns a valid UTF-8 string that json won't reject 629 func unicoded(in string) (out string) { 630 for i, v := range in { 631 if v == 0xFFFD { 632 return in[:i] 633 } 634 } 635 return in 636 }