github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/show_stats.go (about)

     1  // Copyright 2017 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package sql
    12  
    13  import (
    14  	"context"
    15  	encjson "encoding/json"
    16  
    17  	"github.com/cockroachdb/cockroach/pkg/sql/catalog/resolver"
    18  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/stats"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    22  	"github.com/cockroachdb/cockroach/pkg/util/json"
    23  	"github.com/cockroachdb/errors"
    24  )
    25  
    26  var showTableStatsColumns = sqlbase.ResultColumns{
    27  	{Name: "statistics_name", Typ: types.String},
    28  	{Name: "column_names", Typ: types.StringArray},
    29  	{Name: "created", Typ: types.Timestamp},
    30  	{Name: "row_count", Typ: types.Int},
    31  	{Name: "distinct_count", Typ: types.Int},
    32  	{Name: "null_count", Typ: types.Int},
    33  	{Name: "histogram_id", Typ: types.Int},
    34  }
    35  
    36  var showTableStatsJSONColumns = sqlbase.ResultColumns{
    37  	{Name: "statistics", Typ: types.Jsonb},
    38  }
    39  
    40  // ShowTableStats returns a SHOW STATISTICS statement for the specified table.
    41  // Privileges: Any privilege on table.
    42  func (p *planner) ShowTableStats(ctx context.Context, n *tree.ShowTableStats) (planNode, error) {
    43  	// We avoid the cache so that we can observe the stats without
    44  	// taking a lease, like other SHOW commands.
    45  	desc, err := p.ResolveUncachedTableDescriptorEx(ctx, n.Table, true /*required*/, resolver.ResolveRequireTableDesc)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  	if err := p.CheckAnyPrivilege(ctx, desc); err != nil {
    50  		return nil, err
    51  	}
    52  	columns := showTableStatsColumns
    53  	if n.UsingJSON {
    54  		columns = showTableStatsJSONColumns
    55  	}
    56  
    57  	return &delayedNode{
    58  		name:    n.String(),
    59  		columns: columns,
    60  		constructor: func(ctx context.Context, p *planner) (planNode, error) {
    61  			// We need to query the table_statistics and then do some post-processing:
    62  			//  - convert column IDs to column names
    63  			//  - if the statistic has a histogram, we return the statistic ID as a
    64  			//    "handle" which can be used with SHOW HISTOGRAM.
    65  			rows, err := p.ExtendedEvalContext().ExecCfg.InternalExecutor.Query(
    66  				ctx,
    67  				"read-table-stats",
    68  				p.txn,
    69  				`SELECT "statisticID",
    70  					      name,
    71  					      "columnIDs",
    72  					      "createdAt",
    73  					      "rowCount",
    74  					      "distinctCount",
    75  					      "nullCount",
    76  					      histogram
    77  				 FROM system.table_statistics
    78  				 WHERE "tableID" = $1
    79  				 ORDER BY "createdAt"`,
    80  				desc.ID,
    81  			)
    82  			if err != nil {
    83  				return nil, err
    84  			}
    85  
    86  			const (
    87  				statIDIdx = iota
    88  				nameIdx
    89  				columnIDsIdx
    90  				createdAtIdx
    91  				rowCountIdx
    92  				distinctCountIdx
    93  				nullCountIdx
    94  				histogramIdx
    95  				numCols
    96  			)
    97  
    98  			v := p.newContainerValuesNode(columns, 0)
    99  			if n.UsingJSON {
   100  				result := make([]stats.JSONStatistic, len(rows))
   101  				for i, r := range rows {
   102  					result[i].CreatedAt = tree.AsStringWithFlags(r[createdAtIdx], tree.FmtBareStrings)
   103  					result[i].RowCount = (uint64)(*r[rowCountIdx].(*tree.DInt))
   104  					result[i].DistinctCount = (uint64)(*r[distinctCountIdx].(*tree.DInt))
   105  					result[i].NullCount = (uint64)(*r[nullCountIdx].(*tree.DInt))
   106  					if r[nameIdx] != tree.DNull {
   107  						result[i].Name = string(*r[nameIdx].(*tree.DString))
   108  					}
   109  					colIDs := r[columnIDsIdx].(*tree.DArray).Array
   110  					result[i].Columns = make([]string, len(colIDs))
   111  					for j, d := range colIDs {
   112  						result[i].Columns[j] = statColumnString(desc, d)
   113  					}
   114  					if err := result[i].DecodeAndSetHistogram(r[histogramIdx]); err != nil {
   115  						v.Close(ctx)
   116  						return nil, err
   117  					}
   118  				}
   119  				encoded, err := encjson.Marshal(result)
   120  				if err != nil {
   121  					v.Close(ctx)
   122  					return nil, err
   123  				}
   124  				jsonResult, err := json.ParseJSON(string(encoded))
   125  				if err != nil {
   126  					v.Close(ctx)
   127  					return nil, err
   128  				}
   129  				if _, err := v.rows.AddRow(ctx, tree.Datums{tree.NewDJSON(jsonResult)}); err != nil {
   130  					v.Close(ctx)
   131  					return nil, err
   132  				}
   133  				return v, nil
   134  			}
   135  
   136  			for _, r := range rows {
   137  				if len(r) != numCols {
   138  					v.Close(ctx)
   139  					return nil, errors.Errorf("incorrect columns from internal query")
   140  				}
   141  
   142  				colIDs := r[columnIDsIdx].(*tree.DArray).Array
   143  				colNames := tree.NewDArray(types.String)
   144  				colNames.Array = make(tree.Datums, len(colIDs))
   145  				for i, d := range colIDs {
   146  					colNames.Array[i] = tree.NewDString(statColumnString(desc, d))
   147  				}
   148  
   149  				histogramID := tree.DNull
   150  				if r[histogramIdx] != tree.DNull {
   151  					histogramID = r[statIDIdx]
   152  				}
   153  
   154  				res := tree.Datums{
   155  					r[nameIdx],
   156  					colNames,
   157  					r[createdAtIdx],
   158  					r[rowCountIdx],
   159  					r[distinctCountIdx],
   160  					r[nullCountIdx],
   161  					histogramID,
   162  				}
   163  				if _, err := v.rows.AddRow(ctx, res); err != nil {
   164  					v.Close(ctx)
   165  					return nil, err
   166  				}
   167  			}
   168  			return v, nil
   169  		},
   170  	}, nil
   171  }
   172  
   173  func statColumnString(desc *ImmutableTableDescriptor, colID tree.Datum) string {
   174  	id := sqlbase.ColumnID(*colID.(*tree.DInt))
   175  	colDesc, err := desc.FindColumnByID(id)
   176  	if err != nil {
   177  		// This can happen if a column was removed.
   178  		return "<unknown>"
   179  	}
   180  	return colDesc.Name
   181  }