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 }