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  }